Unable to subscribe to web3js event due to provider - websocket

I'm trying to subscribe to an event so I can have it appear in my terminal.
Firstly, I do this to have the deployed contract stored in the app variable.
ChainList.deployed().then(function(instance){app=instance})
Then I do,
app.LogSellArticle({filter:{}, fromBlock:0})
.on('data', event => console.log(event))
.on('changed', changed => console.log(changed))
.on('error', err => console.log(err))
.on('connected', str => console.log(str))
which gives me this output,
EventEmitter {
_events: [Object: null prototype] {
data: [Function (anonymous)],
changed: [Function (anonymous)],
error: [Function (anonymous)],
connected: [Function (anonymous)]
},
_eventsCount: 4,
_maxListeners: undefined,
[Symbol(kCapture)]: false
}
truffle(ganache)> Error: The current provider doesn't support subscriptions: HttpProvider
at Timeout._onTimeout (C:\Users\Yiannis\AppData\Roaming\npm\node_modules\truffle\build\webpack:\node_modules\web3-eth\node_modules\web3-core-subscriptions\lib\subscription.js:176:1)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7)
I'm not using HttpProvider, I'm using websockets and I also have them enabled in my truffle-config.js. I am using Ganache as a test node.
truffle-config.js:
networks: {
ganache: {
host: "localhost", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
websocket: true // Enable EventEmitter interface for web3 (default: false)
},
},
// Set default mocha options here, use special reporters etc.
mocha: {
// timeout: 100000
},
// Configure your compilers
compilers: {
solc: {
version: "0.8.11", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
settings: { // See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: true,
runs: 200
},
// evmVersion: "byzantium"
}
}
},
};
ChainList.sol
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.11;
contract ChainList{
//state vars
address seller;
string name;
string description;
uint256 price;
//events
event LogSellArticle(
address indexed _seller,
string _name,
uint256 _price
);
//sell an article
function sellArticle(string memory _name, string memory _desc, uint256 _price) public{
seller = msg.sender;
name = _name;
description = _desc;
price = _price;
emit LogSellArticle(seller, name, price);
}
//get an article
function getArticle() public view returns (
address _seller,
string memory _name,
string memory _desc,
uint256 _price
){
return(seller, name, description, price);
}
}
app.js
App = {
web3Provider: null,
contracts: {},
account: 0x0,
init: function() {
return App.initWeb3();
},
initWeb3: function() {
//initialise web3
if(typeof web3 != 'undefined'){
App.web3Provider = web3.currentProvider;
}else{
//create a new provider and plug it directly into our local node
App.web3Provider = new Web3.providers.WebsocketProvider('ws://localhost:7545');
}
web3 = new Web3(App.web3Provider);
App.displayAccountInfo();
return App.initContract();
},
displayAccountInfo: function(){
web3.eth.getCoinbase(function(err, account){
if(err == null){
App.account = account;
$('#account').text(account);
web3.eth.getBalance(account, function(err, balance){
if(err == null){
$('#accountBalance').text(web3.fromWei(balance, "ether") + ' ETH');
}
})
}
})
},
initContract: function() {
$.getJSON('ChainList.json', function(chainListArtifact){
//get the contract artifact file and use it to instantiate a truffle contract abstraction
App.contracts.ChainList = TruffleContract(chainListArtifact);
//set the provider for our contract
App.contracts.ChainList.setProvider(App.web3Provider);
//retrieve article from contract
return App.reloadArticles();
})
},
reloadArticles: function(){
//refresh account information because the balance might have changed
App.displayAccountInfo();
//retrieve article placeholder and clear it
$('#articlesRow').empty();
App.contracts.ChainList.deployed().then(function(instance){
return instance.getArticle();
}).then(function(article){
if(article[0] == 0x0){
//no article to display
return;
}
//retrieve the article template and fill it with data
var articleTemplate = $('#articleTemplate');
articleTemplate.find('.panel-title').text(article[1]);
articleTemplate.find('.article-description').text(article[2]);
articleTemplate.find('.article-price').text(web3.fromWei(article[3] , "ether"));
var seller = article[0];
if(seller == App.account){
seller = "You";
}
articleTemplate.find('.article-seller').text(seller);
//add this article
$('#articlesRow').append(articleTemplate.html());
}).catch(function(err){
console.error(err.message);
})
},
sellArticle: function(){
//retrieve detail of article
var _article_name = $('#article_name').val();
var _description = $('#article_description').val();
var _price = web3.toWei(parseFloat($('#article_price').val() || 0,), "ether" );
if((_article_name.trim() == '') || (_price == 0)){
//nothing to sell
return false;
}
App.contracts.ChainList.deployed().then(function(instance){
return instance.sellArticle(_article_name,_description,_price, {
from: App.account,
gas: 500000
}).then(function(result){
App.reloadArticles();
}).catch(function(err){
console.error(err);
})
})
}
};
$(function() {
$(window).load(function() {
App.init();
});
});

Related

Unable to store timestamp in Dynamo DB using Nodejs 16 version Lambda

I am unable to store timestamp in Dynamo DB using Node JS Lambda. i am new to Node JS.
while run the test case, it was stored. but while trigger this lambda function getting error.
i was try to insert below timestamp
LastUpdateTimestamp
The date and time this contact was last updated, in UTC time.
Type: String (yyyy-mm-ddThh:mm:ssZ)
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB();
exports.handler = async (event) => {
var surveyResults = {};
var data = event.Details.ContactData.Attributes; // extract the contact attributes from the Amazon Connect event
Object.keys(data).forEach(element => {
if (element.startsWith("survey_result_")) {
surveyResults[element] = { S: data[element] };
}
});
var params = {
TableName: process.env.TABLE,
Item: {
contactId: { S: event.Details.ContactData.ContactId},
date: { S: event.Details.ContactData.LastUpdateTimestamp},
surveyId: { S: event.Details.ContactData.Attributes.surveyId },
...surveyResults
}
}
try {
await ddb.putItem(params).promise(); // write the object to the DynamoDB table
} catch (err) {
console.log(err);
}
const response = {
statusCode: 200,
body: JSON.stringify('OK'),
};
return response;
};
Test Data:
{
"Name": "ContactFlowEvent",
"Details": {
"ContactData": {
"Attributes": {
"surveyId": "123456",
"survey_result_1": "4",
"survey_result_2": "5"
},
"Channel": "VOICE",
"ContactId": "5ca32fbd-8f92-46af-92a5-6b0f970f0efe",
"LastUpdateTimestamp": "2023-02-14T06:52:29Z",
"CustomerEndpoint": {
"Address": "+11234567890",
"Type": "TELEPHONE_NUMBER"
},
"InitialContactId": "5ca32fbd-8f92-46af-92a5-6b0f970f0efe",
"InitiationMethod": "API",
"InstanceARN": "arn:aws:connect:us-east-1:123456789012:instance/9308c2a1-9bc6-4cea-8290-6c0b4a6d38fa",
"MediaStreams": {
"Customer": {
"Audio": {
"StartFragmentNumber": "91343852333181432392682062622220590765191907586",
"StartTimestamp": "1565781909613",
"StreamARN": "arn:aws:kinesisvideo:us-east-1:123456789012:stream/connect-contact-a3d73b84-ce0e-479a-a9dc-5637c9d30ac9/1565272947806"
}
}
},
"PreviousContactId": "5ca32fbd-8f92-46af-92a5-6b0f970f0efe",
"Queue": null,
"SystemEndpoint": {
"Address": "+11234567890",
"Type": "TELEPHONE_NUMBER"
}
},
"Parameters": {}
}
}
Error:
2023-02-15T14:10:26.426Z 94fad25e-bcdb-443b-95c0-47640bfaba34 INFO ValidationException: Supplied AttributeValue is empty, must contain exactly one of the supported datatypes
at Request.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/json.js:52:27)
at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
at Request.emit (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
at Request.emit (/var/runtime/node_modules/aws-sdk/lib/request.js:686:14)
at Request.transition (/var/runtime/node_modules/aws-sdk/lib/request.js:22:10)
at AcceptorStateMachine.runTo (/var/runtime/node_modules/aws-sdk/lib/state_machine.js:14:12)
at /var/runtime/node_modules/aws-sdk/lib/state_machine.js:26:10
at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:38:9)
at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:688:12)
at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:116:18) {
code: 'ValidationException',
time: 2023-02-15T14:10:26.328Z,
requestId: 'F5JJOCF20D507JCFRO7FEMAH6VVV4KQNSO5AEMVJF66Q9ASUAAJG',
statusCode: 400,
retryable: false,
retryDelay: 15.458319111471575
}
You state that ...surveyResults contains the following:
"survey_result_1": "4",
"survey_result_2": "5"
This is not DynamoDB JSON. You need to modify the values to suit the correct format as you have done with the other values.
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB();
exports.handler = async (event) => {
var surveyResults = {};
var data = event.Details.ContactData.Attributes; // extract the contact attributes from the Amazon Connect event
Object.keys(data).forEach(element => {
if (element.startsWith("survey_result_")) {
surveyResults[element] = { S: data[element] };
}
});
var contact_params = {
ContactId: event.Details.ContactData.InitialContactId, /* required */
InstanceId: 'place your instance id' /* required */
};
var connect = new AWS.Connect();
console.log('describeContact');
/*connect.describeContact(contact_params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});*/
const attributes = await connect.describeContact(contact_params).promise();
console.log(attributes)
console.log(typeof attributes.Contact.LastUpdateTimestamp)
var agent_params = {
InstanceId: 'place your instance id', /* required */
UserId: attributes.Contact.AgentInfo.Id /* required */
};
const agent = await connect.describeUser(agent_params).promise();
let agent_username = "";
if(agent_username != null){
agent_username = agent.User.Username;
}
console.log(agent)
var params = {
TableName: process.env.TABLE,
Item: {
contactId: { S: event.Details.ContactData.ContactId},
surveyId: { S: event.Details.ContactData.Attributes.surveyId },
date: { S: attributes.Contact.LastUpdateTimestamp.toLocaleDateString()},
time: { S: attributes.Contact.LastUpdateTimestamp.toLocaleTimeString()},
agentname: {S: agent_username},
...surveyResults
}
}
try {
await ddb.putItem(params).promise(); // write the object to the DynamoDB table
} catch (err) {
console.log(err);
}
const response = {
statusCode: 200,
body: JSON.stringify('OK'),
};
return response;
};

how do I decode an anonymous event?

I'm trying to decode a simple anonymous event I wrote in my contract and instead of result I'm getting an empty array, does anyone know what I'm doing wrong?
I'm using hardhat.
contract:
pragma solidity ^0.8.9;
contract Lock {
event DataStored(address admin, uint256 indexed data) anonymous;
uint256 data;
function storeData(uint256 data) external {
data = data;
emit DataStored(msg.sender, data);
}
}
deploy.js
const hre = require("hardhat");
const contract = require("../artifacts/contracts/Lock.sol/Lock.json");
const abiDecoder = require('abi-decoder');
async function main() {
const Lock = await hre.ethers.getContractFactory("Lock");
const lock = await Lock.deploy();
await lock.deployed();
console.log(
` deployed to ${lock.address}`
);
tx = await lock.storeData(10);
const transactionReceipt = await tx.wait()
abiDecoder.addABI(contract.abi);
const decodedLogs = abiDecoder.decodeLogs(transactionReceipt.events);
console.log(transactionReceipt.events)
console.log(decodedLogs);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
and the result I'm getting from running the deploy.js
deployed to 0x21dF544947ba3E8b3c32561399E88B52Dc8b2823
[]
So I think you should try to learn the different between non-anonymous and anonymous event by this example
smart contract:
pragma solidity ^0.8.9;
contract Lock {
event DataStoredNonAnonymous(address admin, uint256 indexed data);
event DataStoredAnonymous(address admin, uint256 indexed data) anonymous;
uint256 x;
uint256 y;
function storeDataNonAnonymous(uint256 _data) external {
x = _data;
emit DataStoredNonAnonymous(msg.sender, _data);
}
function storeDataAnonymous(uint256 _data) external {
y = _data;
emit DataStoredAnonymous(msg.sender, _data);
}
}
deploy.js or deploy.ts depending on your preference
import { ethers } from "hardhat";
async function main() {
const Lock = await ethers.getContractFactory("Lock");
const lock = await Lock.deploy();
await lock.deployed();
console.log(`deployed to ${lock.address}`);
const tx1 = await lock.storeDataNonAnonymous(10);
const txReceipt1 = await tx1.wait()
console.log("event non-anonymous",txReceipt1.events)
const tx2 = await lock.storeDataAnonymous(12345);
const txReceipt2 = await tx2.wait()
console.log("event anonymous",txReceipt2.events)
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The result after deployed on the terminal the topic[0] which is reserved for specify the event signature will not showing up
Compiled 1 Solidity file successfully
deployed to 0x5FbDB2315678afecb367f032d93F642f64180aa3
event non-anonymous [
{
transactionIndex: 0,
blockNumber: 2,
transactionHash: '0x22e2b2c6274a83e0c6ff9e2733e7875940e038f3faf5641d77ede41958657fa0',
address: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
topics: [
'0xe70463dc16bf49899544f11b2caa7874683dbf886102edb6cbc82d728dc425d4',
'0x000000000000000000000000000000000000000000000000000000000000000a'
],
data: '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266',
logIndex: 0,
blockHash: '0x1491b08df4abb9ac8aaeb3be796ab0635957668224470944fd215400a02ce276',
args: [
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
BigNumber { value: "10" },
admin: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
data: BigNumber { value: "10" }
],
decode: [Function (anonymous)],
event: 'DataStoredNonAnonymous',
eventSignature: 'DataStoredNonAnonymous(address,uint256)',
removeListener: [Function (anonymous)],
getBlock: [Function (anonymous)],
getTransaction: [Function (anonymous)],
getTransactionReceipt: [Function (anonymous)]
}
]
event anonymous [
{
transactionIndex: 0,
blockNumber: 3,
transactionHash: '0x76ec1eef5693031441fd0446ab681d3f703cca3a016167b343ad8d75fdfd478f',
address: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
topics: [
'0x0000000000000000000000000000000000000000000000000000000000003039'
],
data: '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266',
logIndex: 0,
blockHash: '0xd4f64019225eb982522f8f41a1cc2d6869a7ac41f5d93e5b6f7fb39607914e78',
removeListener: [Function (anonymous)],
getBlock: [Function (anonymous)],
getTransaction: [Function (anonymous)],
getTransactionReceipt: [Function (anonymous)]
}
]

The bot name is already registered to another bot application

I had previously tried to deploy this bot and had issues. I attempted to delete the resource group and try again from scratch and am getting this error.
Built with: Bot Framework Composer (v1.1.1)
Deploying with: provisionComposer.js
> Deploying Azure services (this could take a while)...
✖
{
"error": {
"code": "InvalidBotData",
"message": "Bot is not valid. Errors: The bot name is already registered to another bot application.. See https://aka.ms/bot-requirements for detailed requirements."
}
}
** Provision failed **
The link in the error message doesn't mention 'bot name' or 'name'.
Does a bot name have to be unique to the subscription, tenant, etc?
Is there a place I need to go to 'un-register' the bot name so that it can be registered to another application? Was deleting the resource group not enough?
Thanks in advance for the assistance.
Best Regards,
Josh
I ran into the same issue. After some modifications to the script I was able to complete the provisioning of resources. Issue was the name used to create some of the resources did not match because of the environment variable appended.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const chalk = require("chalk");
const fs = require("fs-extra");
const msRestNodeAuth = require("#azure/ms-rest-nodeauth");
const argv = require("minimist")(process.argv.slice(2));
const path = require("path");
const rp = require("request-promise");
const { promisify } = require("util");
const { GraphRbacManagementClient } = require("#azure/graph");
const {
ApplicationInsightsManagementClient,
} = require("#azure/arm-appinsights");
const { AzureBotService } = require("#azure/arm-botservice");
const { ResourceManagementClient } = require("#azure/arm-resources");
const readFile = promisify(fs.readFile);
const ora = require("ora");
const logger = (msg) => {
if (msg.status === BotProjectDeployLoggerType.PROVISION_ERROR) {
console.log(chalk.red(msg.message));
} else if (
msg.status === BotProjectDeployLoggerType.PROVISION_ERROR_DETAILS
) {
console.log(chalk.white(msg.message));
} else {
console.log(chalk.green(msg.message));
}
};
const usage = () => {
const options = [
["subscriptionId", "Azure Subscription Id"],
["name", "Project Name"],
["appPassword", "16 character password"],
["environment", "Environment name (Defaults to dev)"],
["location", "Azure Region (Defaults to westus)"],
["appId", "Microsoft App ID (Will create if absent)"],
[
"tenantId",
"ID of your tenant if required (will choose first in list by default)",
],
["createLuisResource", "Create a LUIS resource? Default true"],
[
"createLuisAuthoringResource",
"Create a LUIS authoring resource? Default true",
],
["createCosmosDb", "Create a CosmosDB? Default true"],
["createStorage", "Create a storage account? Default true"],
["createAppInsights", "Create an AppInsights resource? Default true"],
["createQnAResource", "Create a QnA resource? Default true"],
[
"customArmTemplate",
"Path to runtime ARM template. By default it will use an Azure WebApp template. Pass `DeploymentTemplates/function-template-with-preexisting-rg.json` for Azure Functions or your own template for a custom deployment.",
],
];
const instructions = [
``,
chalk.bold(
"Provision Azure resources for use with Bot Framework Composer bots"
),
`* This script will create a new resource group and the necessary Azure resources needed to operate a Bot Framework bot in the cloud.`,
`* Use this to create a publishing profile used in Composer's "Publish" toolbar.`,
``,
chalk.bold(`Basic Usage:`),
chalk.greenBright(`node provisionComposer --subscriptionId=`) +
chalk.yellow("<Azure Subscription Id>") +
chalk.greenBright(" --name=") +
chalk.yellow("<Name for your environment>") +
chalk.greenBright(" --appPassword=") +
chalk.yellow("<16 character password>"),
``,
chalk.bold(`All options:`),
...options.map((option) => {
return (
chalk.greenBright("--" + option[0]) + "\t" + chalk.yellow(option[1])
);
}),
];
console.log(instructions.join("\n"));
};
// check for required parameters
if (Object.keys(argv).length === 0) {
return usage();
}
if (!argv.name || !argv.subscriptionId || !argv.appPassword) {
return usage();
}
// Get required fields from the arguments
const subId = argv.subscriptionId;
const name = argv.name.toString();
const appPassword = argv.appPassword;
// Get optional fields from the arguments
const environment = argv.environment || "dev";
const location = argv.location || "westus";
const appId = argv.appId; // MicrosoftAppId - generated if left blank
// Get option flags
const createLuisResource = argv.createLuisResource == "false" ? false : true;
const createLuisAuthoringResource =
argv.createLuisAuthoringResource == "false" ? false : true;
const createCosmosDb = argv.createCosmosDb == "false" ? false : true;
const createStorage = argv.createStorage == "false" ? false : true;
const createAppInsights = argv.createAppInsights == "false" ? false : true;
const createQnAResource = argv.createQnAResource == "false" ? false : true;
var tenantId = argv.tenantId ? argv.tenantId : "";
const templatePath =
argv.customArmTemplate ||
path.join(
__dirname,
"DeploymentTemplates",
"template-with-preexisting-rg.json"
);
const BotProjectDeployLoggerType = {
// Logger Type for Provision
PROVISION_INFO: "PROVISION_INFO",
PROVISION_ERROR: "PROVISION_ERROR",
PROVISION_WARNING: "PROVISION_WARNING",
PROVISION_SUCCESS: "PROVISION_SUCCESS",
PROVISION_ERROR_DETAILS: "PROVISION_ERROR_DETAILS",
};
/**
* Create a Bot Framework registration
* #param {} graphClient
* #param {*} displayName
* #param {*} appPassword
*/
const createApp = async (graphClient, displayName, appPassword) => {
try {
const createRes = await graphClient.applications.create({
displayName: displayName,
passwordCredentials: [
{
value: appPassword,
startDate: new Date(),
endDate: new Date(
new Date().setFullYear(new Date().getFullYear() + 2)
),
},
],
availableToOtherTenants: true,
replyUrls: ["https://token.botframework.com/.auth/web/redirect"],
});
return createRes;
} catch (err) {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: err.body.message,
});
return false;
}
};
/**
* Create an Azure resources group
* #param {} client
* #param {*} location
* #param {*} resourceGroupName
*/
const createResourceGroup = async (client, location, resourceGroupName) => {
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Creating resource group ...`,
});
const param = {
location: location,
};
return await client.resourceGroups.createOrUpdate(resourceGroupName, param);
};
/**
* Format parameters
* #param {} scope
*/
const pack = (scope) => {
return {
value: scope,
};
};
const unpackObject = (output) => {
const unpacked = {};
for (const key in output) {
const objValue = output[key];
if (objValue.value) {
unpacked[key] = objValue.value;
}
}
return unpacked;
};
/**
* For more information about this api, please refer to this doc: https://learn.microsoft.com/en-us/rest/api/resources/Tenants/List
* #param {*} accessToken
*/
const getTenantId = async (accessToken) => {
if (!accessToken) {
throw new Error(
"Error: Missing access token. Please provide a non-expired Azure access token. Tokens can be obtained by running az account get-access-token"
);
}
if (!subId) {
throw new Error(
`Error: Missing subscription Id. Please provide a valid Azure subscription id.`
);
}
try {
const tenantUrl = `https://management.azure.com/subscriptions/${subId}?api-version=2020-01-01`;
const options = {
headers: { Authorization: `Bearer ${accessToken}` },
};
const response = await rp.get(tenantUrl, options);
const jsonRes = JSON.parse(response);
if (jsonRes.tenantId === undefined) {
throw new Error(`No tenants found in the account.`);
}
return jsonRes.tenantId;
} catch (err) {
throw new Error(`Get Tenant Id Failed, details: ${getErrorMesssage(err)}`);
}
};
const getDeploymentTemplateParam = (
appId,
appPwd,
location,
name,
shouldCreateAuthoringResource,
shouldCreateLuisResource,
shouldCreateQnAResource,
useAppInsights,
useCosmosDb,
useStorage
) => {
return {
appId: pack(appId),
appSecret: pack(appPwd),
appServicePlanLocation: pack(location),
botId: pack(name),
shouldCreateAuthoringResource: pack(shouldCreateAuthoringResource),
shouldCreateLuisResource: pack(shouldCreateLuisResource),
shouldCreateQnAResource: pack(shouldCreateQnAResource),
useAppInsights: pack(useAppInsights),
useCosmosDb: pack(useCosmosDb),
useStorage: pack(useStorage),
};
};
/**
* Validate the deployment using the Azure API
*/
const validateDeployment = async (
client,
resourceGroupName,
deployName,
templateParam
) => {
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: "> Validating Azure deployment ...",
});
const templateFile = await readFile(templatePath, { encoding: "utf-8" });
const deployParam = {
properties: {
template: JSON.parse(templateFile),
parameters: templateParam,
mode: "Incremental",
},
};
return await client.deployments.validate(
resourceGroupName,
deployName,
deployParam
);
};
/**
* Using an ARM template, provision a bunch of resources
*/
const createDeployment = async (
client,
resourceGroupName,
deployName,
templateParam
) => {
const templateFile = await readFile(templatePath, { encoding: "utf-8" });
const deployParam = {
properties: {
template: JSON.parse(templateFile),
parameters: templateParam,
mode: "Incremental",
},
};
return await client.deployments.createOrUpdate(
resourceGroupName,
deployName,
deployParam
);
};
/**
* Format the results into the expected shape
*/
const updateDeploymentJsonFile = async (
client,
resourceGroupName,
deployName,
appId,
appPwd
) => {
const outputs = await client.deployments.get(resourceGroupName, deployName);
if (outputs && outputs.properties && outputs.properties.outputs) {
const outputResult = outputs.properties.outputs;
const applicationResult = {
MicrosoftAppId: appId,
MicrosoftAppPassword: appPwd,
};
const outputObj = unpackObject(outputResult);
if (!createAppInsights) {
delete outputObj.applicationInsights;
}
if (!createCosmosDb) {
delete outputObj.cosmosDb;
}
if (!createLuisAuthoringResource && !createLuisResource) {
delete outputObj.luis;
}
if (!createStorage) {
delete outputObj.blobStorage;
}
const result = {};
Object.assign(result, outputObj, applicationResult);
return result;
} else {
return null;
}
};
const provisionFailed = (msg) => {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: chalk.bold("** Provision failed **"),
});
};
const getErrorMesssage = (err) => {
if (err.body) {
if (err.body.error) {
if (err.body.error.details) {
const details = err.body.error.details;
let errMsg = "";
for (let detail of details) {
errMsg += detail.message;
}
return errMsg;
} else {
return err.body.error.message;
}
} else {
return JSON.stringify(err.body, null, 2);
}
} else {
return JSON.stringify(err, null, 2);
}
};
/**
* Provision a set of Azure resources for use with a bot
*/
const create = async (
creds,
subId,
name,
location,
environment,
appId,
appPassword,
createLuisResource = true,
createLuisAuthoringResource = true,
createQnAResource = true,
createCosmosDb = true,
createStorage = true,
createAppInsights = true
) => {
// If tenantId is empty string, get tenanId from API
if (!tenantId) {
const token = await creds.getToken();
const accessToken = token.accessToken;
// the returned access token will almost surely have a tenantId.
// use this as the default if one isn't specified.
if (token.tenantId) {
tenantId = token.tenantId;
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Using Tenant ID: ${tenantId}`,
});
} else {
tenantId = await getTenantId(accessToken);
}
}
const graphCreds = new msRestNodeAuth.DeviceTokenCredentials(
creds.clientId,
tenantId,
creds.username,
"graph",
creds.environment,
creds.tokenCache
);
const graphClient = new GraphRbacManagementClient(graphCreds, tenantId, {
baseUri: "https://graph.windows.net",
});
// If the appId is not specified, create one
if (!appId) {
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: "> Creating App Registration ...",
});
// create the app registration
const appCreated = await createApp(
graphClient,
`${name}-${environment}`,
appPassword
);
if (appCreated === false) {
return provisionFailed();
}
// use the newly created app
appId = appCreated.appId;
}
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Create App Id Success! ID: ${appId}`,
});
const resourceGroupName = `${name}-${environment}`;
const deployName = `${name}-${environment}-deploy`;
// timestamp will be used as deployment name
const timeStamp = new Date().getTime().toString();
const client = new ResourceManagementClient(creds, subId);
// Create a resource group to contain the new resources
try {
const rpres = await createResourceGroup(
client,
location,
resourceGroupName
);
} catch (err) {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: getErrorMesssage(err),
});
return provisionFailed();
}
// Caste the parameters into the right format
const deploymentTemplateParam = getDeploymentTemplateParam(
appId,
appPassword,
location,
`${name}-${environment}`,
createLuisAuthoringResource,
createQnAResource,
createLuisResource,
createAppInsights,
createCosmosDb,
createStorage
);
// Validate the deployment using the Azure API
const validation = await validateDeployment(
client,
resourceGroupName,
deployName,
deploymentTemplateParam
);
// Handle validation errors
if (validation.error) {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Error: ${validation.error.message}`,
});
if (validation.error.details) {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR_DETAILS,
message: JSON.stringify(validation.error.details, null, 2),
});
}
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `+ To delete this resource group, run 'az group delete -g ${resourceGroupName} --no-wait'`,
});
return provisionFailed();
}
// Create the entire stack of resources inside the new resource group
// this is controlled by an ARM template identified in templatePath
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Deploying Azure services (this could take a while)...`,
});
const spinner = ora().start();
try {
const deployment = await createDeployment(
client,
resourceGroupName,
deployName,
deploymentTemplateParam
);
// Handle errors
if (deployment._response.status != 200) {
spinner.fail();
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Template is not valid with provided parameters. Review the log for more information.`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Error: ${validation.error}`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `+ To delete this resource group, run 'az group delete -g ${resourceGroupName} --no-wait'`,
});
return provisionFailed();
}
} catch (err) {
spinner.fail();
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: getErrorMesssage(err),
});
return provisionFailed();
}
// If application insights created, update the application insights settings in azure bot service
if (createAppInsights) {
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Linking Application Insights settings to Bot Service ...`,
});
const appinsightsClient = new ApplicationInsightsManagementClient(
creds,
subId
);
const appComponents = await appinsightsClient.components.get(
resourceGroupName,
resourceGroupName
);
const appinsightsId = appComponents.appId;
const appinsightsInstrumentationKey = appComponents.instrumentationKey;
const apiKeyOptions = {
name: `${resourceGroupName}-provision-${timeStamp}`,
linkedReadProperties: [
`/subscriptions/${subId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/components/${resourceGroupName}/api`,
`/subscriptions/${subId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/components/${resourceGroupName}/agentconfig`,
],
linkedWriteProperties: [
`/subscriptions/${subId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/components/${resourceGroupName}/annotations`,
],
};
const appinsightsApiKeyResponse = await appinsightsClient.aPIKeys.create(
resourceGroupName,
resourceGroupName,
apiKeyOptions
);
const appinsightsApiKey = appinsightsApiKeyResponse.apiKey;
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> AppInsights AppId: ${appinsightsId} ...`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> AppInsights InstrumentationKey: ${appinsightsInstrumentationKey} ...`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> AppInsights ApiKey: ${appinsightsApiKey} ...`,
});
if (appinsightsId && appinsightsInstrumentationKey && appinsightsApiKey) {
const botServiceClient = new AzureBotService(creds, subId);
const botCreated = await botServiceClient.bots.get(
resourceGroupName,
`${name}-${environment}`
);
if (botCreated.properties) {
botCreated.properties.developerAppInsightKey = appinsightsInstrumentationKey;
botCreated.properties.developerAppInsightsApiKey = appinsightsApiKey;
botCreated.properties.developerAppInsightsApplicationId = appinsightsId;
const botUpdateResult = await botServiceClient.bots.update(
resourceGroupName,
`${name}-${environment}`,
botCreated
);
if (botUpdateResult._response.status != 200) {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Something went wrong while trying to link Application Insights settings to Bot Service Result: ${JSON.stringify(
botUpdateResult
)}`,
});
throw new Error(`Linking Application Insights Failed.`);
}
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Linking Application Insights settings to Bot Service Success!`,
});
} else {
logger({
status: BotProjectDeployLoggerType.PROVISION_WARNING,
message: `! The Bot doesn't have a keys properties to update.`,
});
}
}
}
spinner.succeed("Success!");
// Validate that everything was successfully created.
// Then, update the settings file with information about the new resources
const updateResult = await updateDeploymentJsonFile(
client,
resourceGroupName,
deployName,
appId,
appPassword
);
// Handle errors
if (!updateResult) {
const operations = await client.deploymentOperations.list(
resourceGroupName,
deployName
);
if (operations) {
const failedOperations = operations.filter(
(value) =>
value &&
value.properties &&
value.properties.statusMessage.error !== null
);
if (failedOperations) {
failedOperations.forEach((operation) => {
switch (
operation &&
operation.properties &&
operation.properties.statusMessage.error.code &&
operation.properties.targetResource
) {
case "MissingRegistrationForLocation":
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Deployment failed for resource of type ${operation.properties.targetResource.resourceType}. This resource is not avaliable in the location provided.`,
});
break;
default:
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Deployment failed for resource of type ${operation.properties.targetResource.resourceType}.`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Code: ${operation.properties.statusMessage.error.code}.`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Message: ${operation.properties.statusMessage.error.message}.`,
});
break;
}
});
}
} else {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Deployment failed. Please refer to the log file for more information.`,
});
}
}
return updateResult;
};
console.log(chalk.bold("Login to Azure:"));
msRestNodeAuth
.interactiveLogin({ domain: tenantId })
.then(async (creds) => {
const createResult = await create(
creds,
subId,
name,
location,
environment,
appId,
appPassword,
createLuisResource,
createLuisAuthoringResource,
createQnAResource,
createCosmosDb,
createStorage,
createAppInsights
);
if (createResult) {
console.log("");
console.log(
chalk.bold(
`Your Azure hosting environment has been created! Copy paste the following configuration into a new profile in Composer's Publishing tab.`
)
);
console.log("");
const token = await creds.getToken();
const profile = {
accessToken: token.accessToken,
name: `${name}-${environment}`,
environment: environment,
hostname: `${name}-${environment}`,
luisResource: `${name}-${environment}-luis`,
settings: createResult,
};
console.log(chalk.white(JSON.stringify(profile, null, 2)));
console.log("");
}
})
.catch((err) => {
console.error(err);
});
Hope it works for you as well!

Make an ajax call inside of a .map()

I am upgrading jquery and saw that the "async: false" option has been deprecated. This makes sense and in 99.9% of cases I agree with the rationale, but I have a case where I think I really need it and I cannot for the life of me figure out how to make this work with a purely async ajax call no matter how I use promises or async/await.
My use case is in a Vue component and I have an array of contacts. What I need to do is map over the contacts and validate them. One such validation requires a quick check of email validity via a "check_email" ajax endpoint.
Once I validate (or not) the list, I then submit the list (if valid) or show error messages (if invalid).
My code is something like this
sendContacts: function() {
valid = this.validateContacts()
if (valid) {
// send the contacts
} else {
return // will display error messages on contacts objects
}
},
validateContacts: function() {
this.contacts = this.contacts.map((contact) => {
if (!contact.name) {
contact.validDetails.name = false
contact.valid = false
return contact
}
if (!contact.email) {
contact.validDetails.emailExists = false
contact.valid = false
return contact
}
if (!check_email(email)) { // THIS IS ASYNC NOW WHAT DO I DO
contact.valid = false
contact.validDetails.emailFormat = false
}
return contact
}
var validData = this.contacts.map(c => {
return c.valid
})
return !validData.includes(false)
}
function check_email(email) {
const url = `/api/v1/users/check-email?email=${email}`
let valid = false
$.ajax({
url: url,
type: 'POST',
async: false, // I can't do this anymore
headers: {
'X-CSRFToken': csrfToken
},
success: resp => {
valid = true
},
error: err => {
}
})
return valid
}
my data function:
data: function() {
return {
contacts: [this.initContact()],
showThanks: false,
emailError: false,
blankEmail: false,
blankName: false
}
},
methods: {
initContact: function() {
return {
name: null,
email: null,
title: null,
validDetails: this.initValidDetails(),
valid: true,
}
},
initValidDetails: function() {
return {
emailDomain: true,
emailExists: true,
emailFormat: true,
name: true
}
}
}
Again, I have tried async/await in every place I could think of and I cannot get this to validate properly and then perform correct logic regarding whether the send contacts function part of the function should fire. Please help!
Once any part of your validation is asynchronous, you must treat the entire thing as asynchronous. This includes when calling validateContacts in sendContacts.
First, you should change check_email to return Promise<bool>. It's usually a bad idea to include jQuery in a Vue project so let's use fetch instead (Axios being another popular alternative).
async function check_email(email) {
const params = new URLSearchParams({ email })
const res = await fetch(`/api/v1/users/check-email?${params}`, {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken
}
})
return res.ok
}
As for your async validation logic, it's best to map your contacts to an array of promises and wait for them all with Promise.all.
async validateContacts () {
const validationPromises = this.contacts.map(async contact => {
if (!contact.name) {
return {
...contact,
valid: false,
validDetails: {
...contact.validDetails,
name: false
}
}
}
if (!contact.email) {
return {
...contact,
valid: false,
validDetails: {
...contact.validDetails,
emailExists: false
}
}
}
if (await check_email(contact.email)) { // await here
return {
...contact,
valid: false,
validDetails: {
...contact.validDetails,
emailFormat: false
}
}
}
return { ...contact, valid: true }
})
// now wait for all promises to resolve and check for any "false" values
this.contacts = await Promise.all(validationPromises)
return this.contacts.every(({ valid }) => valid)
}
As mentioned, now you need to treat this asynchronously in sendContacts
async sendContacts () {
if (await this.validateContacts()) {
// send the contacts
}
}

sendResponse in Port.postmessage()

I have the following code
browser.runtime.onConnect.addListener(function (externalPort) {
externalPort.onMessage.addListener((message, sender, sendResponse) => {
sendResponse(42);
}
});
However, it seems that listeners for Port.onMessage do not get called with a sendResponse as listeners for browser.runtime.onMessage.
Any idea how to send responses for messages to ports?
Port-based messaging doesn't use sendResponse. Simply post another message to the port.
Here's a very simplified example of a port-based messaging system. It doesn't transfer errors or exceptions, doesn't have a timeout. The idea is to pass an id, save the callback for the id in a map, and use the same id in the response to call that saved callback.
Unlike browser.runtime.sendMessage that creates a new port each time (a relatively expensive operation in case you send a lot of messages), we reuse the same port.
sender:
const port = browser.runtime.connect({name: 'foo'});
const portMap = new Map();
let portMessageId = 0;
port.onMessage.addListener(msg => {
const {id, data} = msg;
const resolve = portMap.get(id);
portMap.delete(id);
resolve(data);
});
function send(data) {
return new Promise(resolve => {
const id = ++portMessageId;
portMap.set(id, resolve);
port.postMessage({id, data});
});
}
usage:
(async () => {
const response = await send({foo: 'whatever'});
console.log(response);
})();
receiver:
/** #param {chrome.runtime.Port} port */
browser.runtime.onConnect.addListener(port => {
if (port.name === 'foo') {
port.onMessage.addListener(msg => {
const {id, data} = msg;
port.postMessage({id, data: processMessage(data)});
});
}
});
The Port.postMessage() is a push-only messaging method, so you need to use regular runtime.sendMessage() method in parallel. Here is an example:
manifest.json:
{
"name": "panel example",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "panel",
"default_popup": "panel.html"
},
"permissions": [
"tabs"
]
}
background.js:
browser.runtime.onConnect.addListener(port => {
let tabId;
const listenerForPort = (message, sender) => {
if (message &&
typeof message == 'object' &&
message.portName == port.name) {
switch (message.type) {
case 'get-tabId':
return Promise.resolve(tabId);
}
}
};
browser.runtime.onMessage.addListener(listenerForPort);
port.onMessage.addListener(message => {
if (message &&
typeof message == 'object' &&
message.tabId)
tabId = message.tabId;
});
port.onDisconnect.addListener(port => {
browser.runtime.onMessage.removeListener(listenerForPort);
if (tabId)
browser.tabs.remove(tabId);
});
});
panel.html:
<!DOCTYPE html>
<script type="application/javascript" src="panel.js"></script>
<button id="button">Click Me</button>
panel.js:
browser.windows.getCurrent({ populate: true }).then(win => {
const portName = `port for window ${win.id}`;
const activeTab = win.tabs.find(tab => tab.active);
const port = browser.runtime.connect({
name: portName
});
port.postMessage({ tabId: activeTab.id });
const button = document.getElementById('button');
button.addEventListener('click', async event => {
const tabIdFromBackground = await browser.runtime.sendMessage({
type: 'get-tabId',
portName
});
button.textContent = tabIdFromBackground;
});
});
In this example, there is a listener corresponding to a connection and it is designed to respond only to messages sent with the corresponding port name.

Resources