How to get session object in Microsoft azure bot sdk 4.0 in node.js? - botframework

Attaching the code snippet below. UniversalBot and ChatConnector has been deprecated in botbuilder 4.1.5.
var bot;
try {
bot = new BasicBot(conversationState, userState, botConfig);
} catch (err) {
console.error(`[botInitializationError]: ${ err }`);
process.exit();
}
// Create HTTP server
// let server = restify.createServer();
let server = express();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }`);
console.log(`\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator`);
console.log(`\nTo talk to your bot, open basic-bot.bot file in the Emulator`);
});
// Listen for incoming activities and route them to your bot main dialog.
server.post('/api/messages', (req, res) => {
// Route received a request to adapter for processing
adapter.processActivity(req, res, async (turnContext) => {
// route to bot activity handler.
await bot.onTurn(turnContext);
});
});

Your question is fairly general.
The session object from 3.x has been removed. Instead acccessors are used. You will want to do following in the bot class:
public onTurn = async (turnContext: TurnContext) => {
const userProfile = await this.userProfile.get(turnContext, new UserProfile());
const conversationData = await this.dialogStateAccessor.get(turnContext, { dialogStack: undefined });
// set vars in cache
userProfile.yourUserVarProp = "userValue";
conversationData.yourConversationVarProp = "conversationValue";
// persist userVars through dialog turn
await this.userProfile.set(turnContext, userProfile);
// persist conversationVars through dialog turn
await this.dialogStateAccessor.set(turnContext, conversationData);
//
// -> your dialogs here (await dc.beginDialog("dialogname");)
//
// save uservars to db at end of a turn
await this.userState.saveChanges(turnContext);
// save conversationVars to db at end of a turn
await this.conversationState.saveChanges(turnContext);
}
But there is some additional constructor stuff
#param {ConversationState} conversationState A ConversationState object used to store the dialog state.
#param {UserState} userState A UserState object used to store values specific to the user.
... and creating the userProfile and dialogStateAccessor itself.
For the whole picture have better a look at https://github.com/Microsoft/BotBuilder-Samples/tree/master/samples/javascript_nodejs .
Or try the generator: https://learn.microsoft.com/en-us/azure/bot-service/javascript/bot-builder-javascript-quickstart?view=azure-bot-service-4.0.

Related

Shopify App Rejected Due To Redirecting To A Different Page When Attempting To Install Your App in incognito

I have created a Shopify public app using node with Shopify CLI, So after i have submitted the app for the reviewing in the Shopify app store.
So the review team rejected the app mentioning the app is not directed to the oAuth page when we install the app ( in Incognito ) window in normal browser their is not mush issue.
The redirecting page in incognito
The issue that point out buy the ShopifyThe replay mail from the review team
The issue only occur in the incognito tab, ie when we log in to our partner dashboard from the Incognito and select the app and choose the "test on development store" option then the issue of select the account to continue will appear.
In normal mode of the browser it will directed to the oAuth page.
because of this reason the review team rejected the app.
Server.js
import "#babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "#shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "#shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
dev,
});
const handle = app.getRequestHandler();
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\/|\/$/g, ""),
API_VERSION: ApiVersion.October20,
IS_EMBEDDED_APP: true,
// This should be replaced with your preferred storage strategy
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};
app.prepare().then(async () => {
const server = new Koa();
const router = new Router();
server.keys = [Shopify.Context.API_SECRET_KEY];
server.use(
createShopifyAuth({
async afterAuth(ctx) {
// Access token and shop available in ctx.state.shopify
const { shop, accessToken, scope } = ctx.state.shopify;
const host = ctx.query.host;
ACTIVE_SHOPIFY_SHOPS[shop] = scope;
const response = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: "/webhooks",
topic: "APP_UNINSTALLED",
webhookHandler: async (topic, shop, body) =>
delete ACTIVE_SHOPIFY_SHOPS[shop],
});
if (!response.success) {
console.log(
`Failed to register APP_UNINSTALLED webhook: ${response.result}`
);
}
// Redirect to app with shop parameter upon auth
ctx.redirect(`/?shop=${shop}&host=${host}`);
},
})
);
const handleRequest = async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
};
router.post("/webhooks", async (ctx) => {
try {
await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
console.log(`Webhook processed, returned status code 200`);
} catch (error) {
console.log(`Failed to process webhook: ${error}`);
}
});
router.post(
"/graphql",
verifyRequest({ returnHeader: true }),
async (ctx, next) => {
await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
}
);
router.get("(/_next/static/.*)", handleRequest); // Static content is clear
router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
router.get("(.*)", async (ctx) => {
const shop = ctx.query.shop;
// This shop hasn't been seen yet, go through OAuth to create a session
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
// const redirectUri = process.env.HOST + '/auth/callback';
// ctx.redirect(`https://${shop}/admin/oauth/authorize? //client_id=${process.env.SHOPIFY_API_KEY}&scope=${process.env.SCOPES}&state=$//{nonce}&redirect_uri=${redirectUri}`);
ctx.redirect(`/auth?shop=${shop}`);
} else {
await handleRequest(ctx);
}
});
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
If you guys can share your thoughts on this it will be very helpful.
Thanks.

Messaging: The notification permission was not granted and blocked instead

Firebase messaging error in https server.
An error occurred while retrieving token. FirebaseError: Messaging: The notification permission was not granted and blocked instead. (messaging/permission-blocked).
What should I do to get my token?
On localhost, it's working.
Here is my code:
firebase-messaging-sw.js
// Import and configure the Firebase SDK
// These scripts are made available when the app is served or deployed on Firebase Hosting
// If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup
importScripts("https://www.gstatic.com/firebasejs/7.8.1/firebase-app.js")
importScripts("https://www.gstatic.com/firebasejs/7.8.1/firebase-messaging.js")
// Your web app's Firebase configuration
var firebaseConfig = {
apiKey: apiKey,
authDomain: authDomain,
databaseURL: databaseURL,
projectId: projectId,
storageBucket: storageBucket,
messagingSenderId: messagingSenderId,
appId: appId,
measurementId: measurementId
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();
/**
* Here is is the code snippet to initialize Firebase Messaging in the Service
* Worker when your app is not hosted on Firebase Hosting.
// [START initialize_firebase_in_sw]
// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js');
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': 'YOUR-SENDER-ID'
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();
// [END initialize_firebase_in_sw]
**/
// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START background_handler]
// [END background_handler]
messaging.setBackgroundMessageHandler(function(payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
var notificationTitle = 'Background Message Title';
var notificationOptions = {
body: 'Background Message body.',
icon: '/firebase-logo.png'
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
footer.blade.php
in //script// section:
// Your web app's Firebase configuration
var firebaseConfig = {
apiKey: apiKey,
authDomain: authDomain,
databaseURL: databaseURL,
projectId: projectId,
storageBucket: storageBucket,
messagingSenderId: messagingSenderId,
appId: appId,
measurementId: measurementId
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const tokenDivId = 'token_div';
const permissionDivId = 'permission_div';
const messaging = firebase.messaging();
messaging.usePublicVapidKey('BKotWNDl7JOuYb-UeusSlSl47onAFH9sWJ_M1WDivsjWq0AZWah5LjVfBAxbcS8T8Yo10HEw_xPX68kMnzTQC2k');
// Get Instance ID token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
messaging.getToken().then((currentToken) => {
if (currentToken) {
sendTokenToServer(currentToken);
updateUIForPushEnabled(currentToken);
} else {
// Show permission request.
console.log('No Instance ID token available. Request permission to generate one.');
// Show permission UI.
updateUIForPushPermissionRequired();
setTokenSentToServer(false);
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
showToken('Error retrieving Instance ID token. ', err);
setTokenSentToServer(false);
});
// Callback fired if Instance ID token is updated.
messaging.onTokenRefresh(() => {
messaging.getToken().then((refreshedToken) => {
console.log('Token refreshed.');
// Indicate that the new Instance ID token has not yet been sent to the
// app server.
setTokenSentToServer(false);
// Send Instance ID token to app server.
sendTokenToServer(refreshedToken);
// ...
}).catch((err) => {
console.log('Unable to retrieve refreshed token ', err);
showToken('Unable to retrieve refreshed token ', err);
});
});
function resetUI() {
clearMessages();
showToken('loading...');
// [START get_token]
// Get Instance ID token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
messaging.getToken().then(function(currentToken) {
if (currentToken) {
sendTokenToServer(currentToken);
updateUIForPushEnabled(currentToken);
} else {
// Show permission request.
console.log('No Instance ID token available. Request permission to generate one.');
// Show permission UI.
updateUIForPushPermissionRequired();
setTokenSentToServer(false);
}
}).catch(function(err) {
console.log('An error occurred while retrieving token. ', err);
showToken('Error retrieving Instance ID token. ', err);
setTokenSentToServer(false);
});
// [END get_token]
}
function showToken(currentToken) {
// Show token in console and UI.
var tokenElement = document.querySelector('#token');
tokenElement.textContent = currentToken;
}
// Send the Instance ID token your application server, so that it can:
// - send messages back to this app
// - subscribe/unsubscribe the token from topics
function sendTokenToServer(currentToken) {
if (!isTokenSentToServer()) {
console.log('Sending token to server...');
// TODO(developer): Send the current token to your server.
setTokenSentToServer(true);
} else {
console.log('Token already sent to server so won\'t send it again ' +
'unless it changes');
}
}
function isTokenSentToServer() {
return window.localStorage.getItem('sentToServer') === '1';
}
function setTokenSentToServer(sent) {
window.localStorage.setItem('sentToServer', sent ? '1' : '0');
}
function showHideDiv(divId, show) {
const div = document.querySelector('#' + divId);
}
function requestPermission() {
console.log('Requesting permission...');
// [START request_permission]
messaging.requestPermission().then(function() {
console.log('Notification permission granted.');
// TODO(developer): Retrieve an Instance ID token for use with FCM.
// [START_EXCLUDE]
// In many cases once an app has been granted notification permission, it
// should update its UI reflecting this.
resetUI();
// [END_EXCLUDE]
}).catch(function(err) {
console.log('Unable to get permission to notify.', err);
});
// [END request_permission]
}
function deleteToken() {
// Delete Instance ID token.
// [START delete_token]
messaging.getToken().then(function(currentToken) {
messaging.deleteToken(currentToken).then(function() {
console.log('Token deleted.');
setTokenSentToServer(false);
// [START_EXCLUDE]
// Once token is deleted update UI.
resetUI();
// [END_EXCLUDE]
}).catch(function(err) {
console.log('Unable to delete token. ', err);
});
// [END delete_token]
}).catch(function(err) {
console.log('Error retrieving Instance ID token. ', err);
showToken('Error retrieving Instance ID token. ', err);
});
}
// Add a message to the messages element.
function appendMessage(payload) {
const messagesElement = document.querySelector('#messages');
const dataHeaderELement = document.createElement('h5');
const dataElement = document.createElement('pre');
dataElement.style = 'overflow-x:hidden;';
dataHeaderELement.textContent = 'Received message:';
dataElement.textContent = JSON.stringify(payload, null, 2);
messagesElement.appendChild(dataHeaderELement);
messagesElement.appendChild(dataElement);
}
// Clear the messages element of all children.
function clearMessages() {
const messagesElement = document.querySelector('#messages');
while (messagesElement.hasChildNodes()) {
messagesElement.removeChild(messagesElement.lastChild);
}
}
function updateUIForPushEnabled(currentToken) {
showHideDiv(tokenDivId, true);
showHideDiv(permissionDivId, false);
showToken(currentToken);
}
function updateUIForPushPermissionRequired() {
showHideDiv(tokenDivId, false);
showHideDiv(permissionDivId, true);
}
This indicates that you have blocked the push notifications permission on the deployed website. It's messaging.getToken() that is likely erroring out (see the docs for more).
If you're using Chrome, you should be able to click the lock to the left of the URL and go to "Site Settings" where you'll see a bell icon with the notification settings for the site:
This may be set to "Block" and you would need to change it to "Allow" instead.
Make sure you're not in an incognito tab - in that case the permissions dialog (Allow/Block) won't show up (in Chrome at least)

Send param with POST to Botframework (and different channel)

I'm working on bot project, the bot are going to work on different channel (web/messenger and probably other)
I'm actually at the proactive message, we want to send dynamic message to user, for example "You don't talk me from XXX time"
So I've made a new route in bot, for sending message with conversation references, it's work good on emulator/messenger for the moment, but we trying to add parameter to this request but we don't found any way to get param in bot.
server.post('/api/notify/:conversationID', async (req, res) => {
console.log(req)
if (req.params.conversationID){
console.log(req.params.conversationID)
}
for (let conversationReference of Object.values(conversationReferences)) {
if (typeof conversationReferences[req.params.conversationID] !== "undefined"){
await adapter.continueConversation(conversationReferences[req.params.conversationID], async turnContext => {
await turnContext.sendActivity(req.params.message);
});
}else {
await adapter.continueConversation(conversationReference, async turnContext => {
await turnContext.sendActivity(req.params.message);
});
}
}
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.write('<html><body><h1>Test send.</h1></body></html>');
res.end();
});
I also tried with GET, and sending parameter in URL like /api/notify/CONVID/MESSAGEtoUSER
But if the message length are more than 70 character, the bot return automatically method don't exist, it's like when the length are 'big' so but understand it like route and not like parameter...
Anyone have idea how can we get the param?
Thank!
EDIT :
Finally I found a way to pass param as POST call.
You need to enable bodyParser of restify, add this line :
server.use(restify.plugins.bodyParser())
in index.js
You can now get the body of POST route call !
:-)
You can achieve this by passing any params in an empty activity via the channelData property. Because the activity includes an empty string in the text property, the activity will not display when passed to the bot.
In this example, the proactive message is initiated from the browser.
server.get('/api/notify/:userId', async (req, res) => {
const { userId } = req.params;
for (const conversationReference of Object.values(conversationReferences)) {
await adapter.continueConversation(conversationReference, async turnContext => {
var reply = { type: ActivityTypes.Message };
reply.channelData = { userId };
reply.text = '';
await turnContext.sendActivity(reply);
});
}
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.write('<html><body><h1>Proactive messages have been sent.</h1></body></html>');
res.end();
});
Proactive message sending the userId via channelData
userId is received by the bot via activity.channelData
Testing Web Chat also shows userId in the activity.channelData
Hope of help!

How do I instantiate a turnContext using the adapter and request as parameters

I would like to instantiate a turnContext to be used in integration testing. How would I be able to instantiate one without calling on the processActivity() method of the adapter?
I am looking at the documentation but it shows that I would need the request of the post call as the parameter. I would like my testing to be independant of the post call. I would then assume that I would need to instantiate the request? How would I go about doing so?
Image of documentation
This is a bit hard to answer without knowing how you are planning to use the code. That being said, it's not that hard to create a new turnContext and also bypass the processActivity(). Given how you are referencing turnContext and processActivity(), I'm assuming you are using the Node SDK. Implementing in C# wouldn't be too different.
Here are two options, both utilizing the creation of a new adapter, however you can also pass in an already established turnContext, if desired:
Use .createContext in server.post in the index.js file, or
Maintain the processActivity() method in the server.post. This calls a new "onTurn" method in the bot.js file. In doing so, this allows you to control when and how the new "onTurn" is accessed.
Option 1: In the index.js file, you will want to create a new adapter or make a copy of the first depending on your needs:
const adapter = new BotFrameworkAdapter({
appId: endpointConfig.appId || process.env.MicrosoftAppId,
appPassword: endpointConfig.appPassword || process.env.MicrosoftAppPassword
});
const newAdapter = adapter;
or
const adapter = new BotFrameworkAdapter({
appId: endpointConfig.appId || process.env.MicrosoftAppId,
appPassword: endpointConfig.appPassword || process.env.MicrosoftAppPassword
});
const newAdapter = new BotFrameworkAdapter({
appId: endpointConfig.appId || process.env.MicrosoftAppId,
appPassword: endpointConfig.appPassword || process.env.MicrosoftAppPassword
});
Include the onTurnError code to catch errors:
// Catch-all for errors.
adapter.onTurnError = async (context, error) => {
console.error(`\n [onTurnError]: ${ error }`);
await context.sendActivity(`Oops. Something went wrong!`);
};
// Catch-all for errors.
newAdapter.onTurnError = async (context, error) => {
console.error(`\n [onTurnError]: ${ error }`);
await context.sendActivity(`Oops. Something went wrong!`);
};
Then, set the new adapters and create the new turnContext:
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (turnContext) => {
await bot.onTurn(turnContext);
});
newAdapter.createContext(req, res);
});
Options 2: In the index.js file, building off of the above code, set the adapters to await the individual "onTurn" methods:
// Listen for incoming requests.
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (turnContext) => {
await bot.onTurn(turnContext);
});
newAdapter.processActivity(req, res, async (turnContext) => {
await bot.newOnTurn(turnContext);
});
});
In the bot.js file, you will have your two "onTurn" methods. In this example, the different "onTurn" methods are called based on whether a message is sent or I am deleting user data (I am sending this event via the Emulator => Conversation menu item). What you decide to match on is up to you.
async newOnTurn(turnContext) {
if (turnContext.activity.type === ActivityTypes.DeleteUserData) {
const dc = await this.dialogs.createContext(turnContext);
await dc.context.sendActivity(`Looks like you deleted some user data.`);
}
}
async onTurn(turnContext) {
if (turnContext.activity.type === ActivityTypes.Message) {
const dc = await this.dialogs.createContext(turnContext);
await dc.context.sendActivity(`Looks like you sent a message.`);
}
}
Hope of help!

Why do we await next when using koa routers?

Why do we do this
router.get('/data', async (ctx, next) => {
ctx.body = dummyjson.parse(data);
await next();
});
router.get('/data/:x', async (ctx, next) => {
const newData = dataRepeat.replace('%(x)', ctx.params.x);
ctx.body = dummyjson.parse(newData);
await next();
});
What is the use of await next()
It would work just fine without that. Similar thing was expected with koa 1. yield next was added at the end of the router.
I'll try to explain it using a very simple example:
const Koa = require('koa');
const app = new Koa();
// middleware
app.use(async function (ctx, next) {
console.log(1)
await next();
console.log(3)
});
// response
app.use(ctx => {
console.log(2)
});
app.listen(3000);
If you call localhost:3000 in your browser, the following will happen in your app:
The first app.use that you fired here was the middleware. So the request flow goes into that one first, logs 1to the console.
Then, when you see this await next(), it downstreams to the next use.
Here we just log 2 to the console. When this is finished (and no further await next is seen in the second use) the flow goes back to the first one which actually waited till the second one was finished.
Here we then continue with logging 3 to the console.
Hope this makes it a little more clear.
No, It is not necessary. It is depend on your requirement.
you use next() function when you call next middleware.
Check your router module and its version. I have use koa-router module and its version is 7.2.0 for routing. It self handle await next.
'use strict';
const Koa = require('koa'),
router = require('koa-router'),
app = new Koa();
let pubRouter = new router();
let securedRouter = new router();
let mapper = require('./mapper'),
// assign router to corresponding function
mapper(pubRouter, securedRouter);
app .use(logger(config.app.log))
.use(bodyParser())
.use(pubRouter.routes()).use(pubRouter.allowedMethods())
.use(jwt({
secret: publicKey,
algorithms: ['RS256']
}))
.use(async(ctx, next) => {
console.log('\n\n\n\n\n', ctx.state.user);
await next();
})
.use(securedRouter.routes()).use(securedRouter.allowedMethods())
.use(async(ctx, next) => {
ctx.body = 'Invalid URL!!!';
});
app.listen(port, () => console.log(`Server listening on port: ${port}`));

Resources