Bottender - Enabling persistent_menu and get_started button on facebook - node.js

I am using NodeJS & Bottender to run my facebook messenger webhook.
So I want to have a get started button and a persistent menu. I checked & patterned my config on the examples provided on the repo like this one but I can't get it to be present on messenger yet.
here's my config.js
require('dotenv').config();
const {
FB_ACCESS_TOKEN,
FB_VERIFY_TOKEN,
APP_ID,
APP_SECRET,
} = process.env;
const { bots } = require('./global-session');
const profile = {
get_started: {
payload: '/start',
},
persistent_menu: [
{
locale: 'default',
composer_input_disabled: false,
call_to_actions: [
{
type: 'postback',
title: 'Menu',
payload: '/menu',
},
],
},
],
};
/* istanbul ignore next */
module.exports = {
messenger: {
bot: {
accessToken: FB_ACCESS_TOKEN,
appId: APP_ID,
appSecret: APP_SECRET,
mapPageToAccessToken: bots.getTokenByPageById,
profile,
},
server: {
verifyToken: FB_VERIFY_TOKEN,
path: '/messenger',
profile,
},
},
};
and here's how I use it
const { MessengerBot } = require('bottender');
const express = require('express');
const bodyParser = require('body-parser');
const { registerRoutes } = require('bottender/express');
const handler = require('./handlers');
const logger = require('./utils/logger');
const { APP_PORT, NODE_ENV } = process.env;
const server = express();
/* istanbul ignore next */
const verify = (req, res, buf) => {
req.rawBody = buf.toString();
};
server.use(bodyParser.json({ verify }));
server.use(require('morgan')('short', { stream: logger.logStream }));
const { messenger } = require('./config');
const bots = {
messenger: new MessengerBot(messenger.bot).onEvent(handler.messenger.execute),
// Define other platform bots here!
};
const initialize = async () => {
try {
registerRoutes(server, bots.messenger, messenger.server);
// Start server
server.listen(APP_PORT, () => logger.info(`ENV[${NODE_ENV}] - server is listening on port ${APP_PORT}...`));
} catch (e) {
logger.error('unable to start server!');
logger.error(e);
throw Error();
}
};
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'test') {
initialize();
}
module.exports = initialize;
Background on my facebook app
- It is already published to public and approved(at least for pages_messaging)
Anything I missed to do? Any help would be appreciated!

We leave messenger profile control to developers, because we can't assume when developers want to set, update or delete bots' messenger profile.
For example, you can have a script like this:
const { MessengerClient } = require('messaging-api-messenger');
const config = require('./bottender.config');
const client = MessengerClient.connect();
const tokens = [ /* */ ]; // get tokens from somewhere
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
client.setMessengerProfile(config.messenger.profile, {
access_token: token, // important! pass token to client api
});
}
Or you can call setMessengerProfile when you register your token to your database:
async function registerPage(page, token) {
// ....put page and token to database
await client.setMessengerProfile(config.messenger.profile, {
access_token: token, // important! pass token to client api
});
}

Related

How to solve the problem of getting req.cookies being undefined in production environment?

//back end sign in controller
const signinController= async (req,res)=>
{
const{email,password}=req.body
try {
const user= await User.findOne({email:email})
if(!user)
{
res.status(400).json({errorMessage:"This email is not registered, please signup"})
}
else
{
let status= await bcrypt.compare(password,user.password)
if(!status)
{
res.status(400).json({errorMessage:"Incorrect password"})
}
else
{
const payload={
user:{
_id:user._id
}
}
jwt.sign(payload,jwtSecret,{expiresIn:jwtExpire},(err,token)=>
{
const{_id,username,email,role}=user
if(err)
{
console.log("jwterror : ",err)
}
else
{
res.status(200).json({token,user:{_id,username,email,role}})
}
})
}
}
} catch (error) {
if(error)
{
console.log(error)
res.status(400).json({errorMessage:"server error"})
}
}
}
// front end part
const handleSubmit=(e)=>
{
e.preventDefault()
if(isEmpty(email.trim())||isEmpty(password.trim()))
{
setFormData({...formData,errorMsg:"Fields are empty"})
}
else if(!isEmail(email))
{
setFormData({...formData,errorMsg:"Invalid email"})
}
else
{
setFormData({...formData,loading:true})
signinAuth({email,password}).then((res)=>
{
setFormData({...formData,loading:false,errorMsg:false})
setAuthentication(res.data.token,res.data.user)
if(isAuthenticated() && isAuthenticated().role===1)
{
history.push("/admin/dashboard")
}
else
{
history.push("/user/dashboard")
}
}).catch((err)=>
{
setFormData({...formData,loading:false,errorMsg:err.response.data.errorMessage})
})
}
}
export const signinAuth= async(data)=>
{
const config={
headers:{"Content-Type":"application/json"}
}
let response= await axios.post(`${api}/api/auth/signin`,data,config)
return response
}
export const setAuthentication=(token,user)=>
{
setCookies("token",token)
setLocalStorage("user",user)
}
export const setCookies=(key,value)=>
{
cookies.set(key,value,{expires:1})
}
I am unable to get req.cookies to verify JWT in production environment, but locally the code is working and I am able to get the req.cookies.
//jwt verification middleware
const jwt = require("jsonwebtoken")
const {jwtSecret}= require("../Config/Keys")
exports.jwtAuthenticator=(req,res,next)=>
{
const token = req.cookies.token
if(!token)
{
res.status(400).json({"errorMessage" :"authorisation denied because no token"})
}
try {
const decoded=jwt.verify(token,jwtSecret)
req.user=decoded.user
next()
} catch (error) {
console.log("jwt error: ",error)
res.status(401).json({"errorMessage" :"authorisation denied"})
}
// app.js
const express = require("express");
const mongoose=require("./database/db")
const cors= require("cors")
const authRouter= require("./Routes/Auth")
const categoryRoute= require("./Routes/Category")
const productRoute= require("./Routes/Product")
const cartRoute= require("./Routes/Cart")
const orderRoute=require("./Routes/Order")
const cookieParser = require("cookie-parser");
const morgan = require("morgan");
const dotenv= require("dotenv")
dotenv.config();
const app= express();
const PORT=process.env.PORT||5000
app.use(express.json())
app.use(express.urlencoded({ extended: true }));
const corsOptions ={
origin:'http://localhost:3000',
credentials:true //access-control-allow-credentials:true
}
app.use(cors(corsOptions))
app.use(morgan("dev"))
app.use(cookieParser())
app.use("/api/auth",authRouter)
app.use("/api/category",categoryRoute)
app.use("/api/product",productRoute)
app.use("/api/cart",cartRoute)
app.use("/api/order",orderRoute)
mongoose.connectDB()
app.get("/",(req,res)=>
{
res.send("server")
})
app.listen(PORT,()=>
{
console.log(`server connected to ${PORT}`)
})
I am unable to get req.cookies to verify JWT in production environment(render platform). However, I am able to fetch cookies locally.
I have tried these JWT middleware,
App.js code locally and it is working. However, after deploying, I couldn't able to get req.cookies.
Update2: give a try with this setting on the cookie if you want to set the cookie on the frontend, (i am not familiar though on the front)
export const setAuthentication=(token,user)=> {
setCookies(token)
setLocalStorage("user",user)
}
export const setCookies=(value)=> {
cookies.set("token", value, {
sameSite: "none" //none for cross domain cookie, If omitted, it's set to 'no_restriction'.
secure: true, //required for sameSite=none attribute, set to false by default in local dev
expires: 1,
})
}
Maybe have a look on the cookies.set() documentation https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/cookies/set
By default httpOnly is indeed false, that's why you can set cookie on the frontend.
2nd Solution: in case you just can't make it work, another common approch would be storing the jwt token in the localstorage just like how you do with the user, and retrieve it when you need by attaching it on your header, but again in my opinion setting this in localstorage or even manipulating cookie on the front is not the best practice, i believe setting all this stuff on the backend with httpOnly and secure cookie flags, or storing jwt in memory for direct use (some limitation) is still the best way to go.
export const setAuthentication=(token,user)=> {
setLocalStorage("token", token) // you have to make a function to store the token like how you do on the user.
setLocalStorage("user",user)
}
// addtoCard endpoint
const addToCart=async (productId)=> {
let user= localStorage.getItem("user")
user= JSON.parse(user)
let userId=user._id
let data={
userId,
productId
}
const token = localStorage.getItem("token");
const config={
headers:{
"Content-Type":"application/json",
Authorization: `Bearer ${token}`
},
"withCredentials": true
}
await axios.post(`${api}/api/cart/addtocart`,data,config).then((res)=> {
console.log(res.data.message)
})
}
than you just need to change the condition checking on the header instead of cookie and change the process in the middleware jwtAuthenticator:
exports.jwtAuthenticator=(req,res,next)=> {
const authHeader = req.headers['authorization'];
console.log(authHeader)
if(!authHeader) {
return res.status(400).json({"errorMessage" :"authorisation denied because no token"})
}
try {
const token = authHeader.split('')[1];
const decoded = jwt.verify(token, jwtSecret)
if(decoded) {
req.user = decoded.user
next();
}
} catch (error) {
console.log("jwt error: ",error)
res.status(403).json({"errorMessage": "forbidden token"})
}
};
//in signin i didn't need to use the jwt middleware. during signin I am only setting the generated token in cookie. I am using JWT middleware for verification is in route that used to add products to cart. and codes are mentioned below
//signin route
const express= require("express")
const {signupValidator,validatorResult}= require("../Middlewares/Validator")
const {signupController,signinController} =require("../Controllers/Auth")
const router= express.Router()
router.post("/signup",signupValidator,validatorResult,signupController)
router.post("/signin",signinController)
module.exports=router
//add to cart route
const express= require("express");
const { jwtAuthenticator } = require("../Middlewares/Authenticator");
const {AddToCart,GetCart,IncQuantity,DecQuantity,RemoveProduct,GetExactCart,RemoveCart} = require("../Controllers/Cart")
const router= express.Router()
router.post("/addtocart",jwtAuthenticator,AddToCart)
router.get("/getcart/:userId",GetCart)
router.get("/getexactcart/:userId",GetExactCart)
router.patch("/incquantity",jwtAuthenticator,IncQuantity)
router.patch("/decquantity",jwtAuthenticator,DecQuantity)
router.patch("/removeProduct",jwtAuthenticator,RemoveProduct)
router.patch("/removeCart/:userId",RemoveCart)
module.exports= router;
//front end part of calling this addtocart api
const addToCart=async (productId)=>
{
let user= localStorage.getItem("user")
user= JSON.parse(user)
let userId=user._id
let data={
userId,
productId
}
const config={
headers:{"Content-Type":"application/json"},
"withCredentials":true
}
await axios.post(`${api}/api/cart/addtocart`,data,config).then((res)=>
{
console.log(res.data.message)
})
}

Clickjacking Issue In Shopify Nextjs App?

I am facing an issue regarding clickjacking prevention. I have implemented authentication and custom headers that contain headers that are requested by Shopify.
ctx.set('Content-Security-Policy', `frame-ancestors https://${ctx.query.shop} https://admin.shopify.com`);
ctx.res.setHeader(
"Content-Security-Policy",
`frame-ancestors https://${ctx.query.shop} https://admin.shopify.com;`
)
local environment headers:
[1]: https://i.stack.imgur.com/XBkW4.png
Production Environment response headers:
[2]: https://i.stack.imgur.com/YkfnA.png
It is working fine in my development environment but is not working in production.
my server.js file
/* eslint-disable #typescript-eslint/no-var-requires */
const dotenv = require('dotenv')
const Koa = require('koa')
const next = require('next')
const { default: createShopifyAuth } = require('#shopify/koa-shopify-auth')
const { verifyRequest } = require('#shopify/koa-shopify-auth')
const { default: Shopify, ApiVersion } = require('#shopify/shopify-api')
const { withSentry } = require('#sentry/nextjs')
const proxy = require('koa-proxy')
const Router = require('#koa/router')
const { checkForNewThemeSupport } = require('./checkfordawntheme')
dotenv.config()
const { PORT } = process.env
const { NODE_ENV } = process.env
const port = parseInt(PORT, 10) || 3000
const dev = NODE_ENV !== 'production'
const app = next({ dev })
const handle = withSentry(app.getRequestHandler())
if (!process.env.NEXT_PUBLIC_SHOPIFY_API_KEY || !process.env.SHOPIFY_API_SECRET_KEY) {
console.error('Missing api keys')
}
const SCOPES = [
'read_products',
'write_products',
'unauthenticated_read_product_listings',
'read_orders',
'read_script_tags',
'write_script_tags',
'read_themes'
]
Shopify.Context.initialize({
API_KEY: process.env.NEXT_PUBLIC_SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET_KEY,
SCOPES,
HOST_NAME: process.env.SHOPIFY_APP_URL.replace(/https:\/\//, ''),
API_VERSION: ApiVersion.October20,
IS_EMBEDDED_APP: true,
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage()
})
// TODO replace this with something serious
const ACTIVE_SHOPIFY_SHOPS = {}
const handleRequest = async (ctx) => {
await handle(ctx.req, ctx.res)
ctx.set('Content-Security-Policy', `frame-ancestors https://${ctx.query.shop} https://admin.shopify.com`);
ctx.res.setHeader(
"Content-Security-Policy",
`frame-ancestors https://${ctx.query.shop} https://admin.shopify.com;`
)
console.log("************* frame ancestor ********* ")
ctx.respond = false
ctx.res.statusCode = 200
}
app.prepare().then(() => {
const server = new Koa()
const router = new Router()
server.keys = [Shopify.Context.API_SECRET_KEY]
// online auth for app/user request
server.use(createShopifyAuth({
accessMode: 'online',
afterAuth(ctx) {
// Online access mode access token and shop available in ctx.state.shopify
const { shop } = ctx.state.shopify
const { host } = ctx.query
// Redirect to app with shop parameter upon auth
ctx.redirect(`/?shop=${shop}&host=${host}`)
}
}))
// offline auth for background tasks
server.use(createShopifyAuth({
accessMode: 'offline',
prefix: '/offline',
async afterAuth(ctx) {
const { shop, accessToken } = ctx.state.shopify
ACTIVE_SHOPIFY_SHOPS[shop] = true
// APP_UNINSTALLED webhook to make sure merchants go through OAuth if they reinstall it
const response = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: '/webhooks',
topic: 'APP_UNINSTALLED',
webhookHandler: async (topic, shop) => delete ACTIVE_SHOPIFY_SHOPS[shop]
})
if (!response.success) {
console.error(`Failed to register APP_UNINSTALLED webhook: ${response.result}`)
}
ctx.redirect(`/auth?shop=${shop}`)
}
}))
router.get('/', async (ctx) => {
const { shop } = ctx.query
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
ctx.redirect(`/offline/auth?shop=${shop}`)
} else {
await handleRequest(ctx)
}
})
router.get('(/_next/static/.*)', handleRequest)
router.get('/_next/webpack-hmr', handleRequest)
router.get('/(.*).js', handleRequest)
router.get('/login', verifyRequest(), handleRequest)
router.get('/register', verifyRequest(), handleRequest)
router.post(
'/webhooks',
async (ctx) => {
try {
await Shopify.Webhooks.Registry.process(ctx.req, ctx.res)
} catch (error) {
console.error(`Failed to process webhook: ${error}`)
}
}
)
router.post(
'/graphql',
verifyRequest({ returnHeader: true }),
async (ctx) => {
await Shopify.Utils.graphqlProxy(ctx.req, ctx.res)
}
)
router.get(
'/checkfor20',
verifyRequest(),
async (ctx) => {
try {
const hasCartAppBlock = await checkForNewThemeSupport(ctx)
ctx.body = JSON.stringify({ hasCartAppBlock })
ctx.status = 200
} catch (error) {
console.error(`Failed to check for theme: ${error}`)
}
}
)
server.use(router.allowedMethods())
server.use(router.routes())
server.use(proxy({
host: process.env.NEXT_PUBLIC_TREEPOINTS_API_URL,
match: /^\/********-api\/\w*/,
map: (path) => path?.split(process.env.NEXT_PUBLIC_TREEPOINTS_API_PROXY_URL)?.[1] || path
}))
// eslint-disable-next-line no-console
server.listen(port, () => console.log(`> Ready on http://localhost:${port}`))
})
App development framework: nextjs
deployment server: Heroku
Any help would be appreciated.

Autocomplete slash command appear only one admin

Description
I created slash command bot with bot, applications.commands scopes. And create slash command for guild, and command not showed so I create commands as application scope. And still not showing
I tried to kick out my bot and re enter url but not worked...
What is the solution plz! Thank you
Steps to Reproduce
const serverless = require("serverless-http");
const express = require("express");
const app = express();
const { CreateRateUseCase } = require("./core/usecase/createRateUseCase");
const nacl = require("tweetnacl");
const getRawBody = require("raw-body");
const { DiscordBot } = require("./discordBot");
// const { verifyKeyMiddleware } = require("discord-interactions");
require("dotenv").config({ path: `./.env.${process.env.NODE_ENV}` });
app.post(
"/interactions",
// verifyKeyMiddleware(process.env.DISCORD_PUBLIC_KEY),
async (req, res, next) => {
try {
console.log(`req : `);
console.log(req);
console.log(`body ${req.body} ${JSON.stringify(req.body)}`);
const rawBody = await getRawBody(req);
console.log(`rawBody ${rawBody} ${typeof rawBody}`);
const body = JSON.parse(rawBody);
if (
process.env.NODE_ENV === "dev" ||
process.env.NODE_ENV === "production"
) {
const signature = req.get("X-Signature-Ed25519");
console.log(`signature ${signature}`);
const timestamp = req.get("X-Signature-Timestamp");
console.log(`timestamp ${timestamp}`);
const isVerified = nacl.sign.detached.verify(
Buffer.from(timestamp + rawBody),
Buffer.from(signature, "hex"),
Buffer.from(process.env.DISCORD_PUBLIC_KEY, "hex")
);
console.log(`isVerified ${isVerified}`);
if (!isVerified) {
console.log("Failed verification");
return res.status(401).end("invalid request signature");
}
if (body.type === 1) {
console.log("Handling validation test request");
return res.status(200).send({ type: 1 });
}
}
if (body.type === 2) {
if (
body.channel_id !== process.env.DISCORD_CHANNEL_ID_KOR &&
body.channel_id !== process.env.DISCORD_CHANNEL_ID_EN
) {
console.log(`channel id ${body.channel_id}`);
console.log(
"This command is only available in the COMMUNITY category"
);
res.status(200).send({
type: 4,
data: {
content: `This command is only available in the 'COMMUNITY' category. 😒`,
},
});
return;
}
const discordBot = new DiscordBot();
const result = await discordBot.execute(body);
console.log(`result ${JSON.stringify(result)}`);
res.status(200).send(result);
console.log("reply done");
}
return;
} catch (e) {
console.error(e.message);
return res.send("Error handling verification");
}
}
);
deploy server on aws lambda
OAuth2 -> URL Generator, check bot, applications.commands and enter url then select server.
check SERVER MEMBERS INTENT, MESSAGE CONTENT INTENT
enter api gateway url to INTERACTIONS ENDPOINT URL as https://xyz.amazonaws.com/interactions/
create slash commands
const { Client, Intents } = require("discord.js");
const client = new Client({
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES],
});
console.log(`NODE_ENV ${process.env.NODE_ENV}`);
require("dotenv").config({ path: `./.env.${process.env.NODE_ENV}` });
const korCommandList = {
γ……γ…‡γ„Ή: {
description: "예치 수읡λ₯  응닡",
},
수읡λ₯ : {
description: "예치 수읡λ₯  응닡",
},
예치수읡λ₯ : {
description: "예치 수읡λ₯  응닡",
},
};
const enCommandList = {
depositapy: {
description: "response deposit yield",
},
yield: {
description: "response deposit yield",
},
apy: {
description: "response deposit yield",
},
deposityield: {
description: "response deposit yield",
},
};
client.on("ready", async () => {
const promises = [];
const objs = Object.assign(korCommandList, enCommandList);
console.log(
`process.env.DISCORD_BOT_CLIENT_ID ${process.env.DISCORD_BOT_CLIENT_ID}`
);
console.log(`process.env.DISCORD_GUILD_ID ${process.env.DISCORD_GUILD_ID}`);
for (const key in objs) {
const p = await client.api
.applications(process.env.DISCORD_BOT_CLIENT_ID)
// .guilds(process.env.DISCORD_GUILD_ID)
.commands.post({
data: {
name: key,
description: objs[key].description,
},
});
promises.push(p);
}
const result = await Promise.all(promises);
console.log(result);
client.destroy();
});
client.login(process.env.DISCORD_BOT_TOKEN);
after hours, or day type / on discord app. normal user can not see commad list, but admin can see.
Expected Behavior
Every user on server with bot can see slash command list.
Current Behavior
Admin can see slash command lists, but normal user can not see.
Screenshots/Videos
text box by server admin
text box by server normal user, slash commands not displayed
Client and System Information
browser admin : chrome on mac
user : discord app on mac
lib

How to resolve 'system:error:invalid-token' In Nexmo Vonage SDK changing to a new app in the same account

I am using "#vonage/server-sdk": "2.10.7-beta-2" package on server to create users in Vonage.
To create the user, I used this API
const Vonage = require('#vonage/server-sdk');
const v = new Vonage({
apiKey: config.voipConfig.key,
apiSecret: config.voipConfig.secret,
applicationId: config.voipConfig.appId,
privateKey: config.voipConfig.privateKeyPath
};
v.users.create({
"name": payload.username,
"display_name": payload.displayName
}, (error: any, result: any) => {
});
Everything was working fine. But when I created a new application in vonage account and used new configs, it started to throw the following error
{
description: 'You did not provide a valid token. Please provide a valid token.',
code: 'system:error:invalid-token'
}
I have checked the details multiple times and not sure what is wrong.
The new credentials are from completely new account.
Any help would be much appreciated.
Looks like you are using the Vonage Conversations API to create a user. In good practice, we usually use dotenv to store our .env variables. If you are using a public github repo, remember to add *.env to your .gitignore.
npm install dotenv
// .env
API_KEY=
API_SECRET=
APPLICATION_ID=
APPLICATION_PRIVATE_KEY_PATH=
TO_NUMBER=<YOUR CELL NUMBER>
VIRTUAL_NUMBER=<VONAGE VIRTUAL NUMBER>
NGROK_URL=https://<URL>.ngrok.io
For testing purposes. Can you make an outbound call with the snippet below? That'll test your credentials.
Make sure to store the private.key in same directory as .env and outbound-call.js
// outbound-call.js
require("dotenv").config();
const API_KEY = process.env.API_KEY;
const API_SECRET = process.env.API_SECRET;
const APPLICATION_ID = process.env.APPLICATION_ID;
const APPLICATION_PRIVATE_KEY_PATH = process.env.APPLICATION_PRIVATE_KEY_PATH;
const TO_NUMBER = process.env.TO_NUMBER;
const VIRTUAL_NUMBER = process.env.VIRTUAL_NUMBER;
if (!API_KEY || !API_SECRET) {
console.log("πŸ”₯ API_KEY or API_SECRET missing");
process.exit(1);
}
if (!APPLICATION_ID || !APPLICATION_PRIVATE_KEY_PATH) {
console.log("πŸ”₯ APPLICATION_ID or APPLICATION_PRIVATE_KEY_PATH missing");
process.exit(1);
}
if (!TO_NUMBER || !VIRTUAL_NUMBER) {
console.log("πŸ”₯ TO_NUMBER or VIRTUAL_NUMBER missing");
process.exit(1);
}
const Vonage = require("#vonage/server-sdk");
const vonage = new Vonage({
apiKey: API_KEY,
apiSecret: API_SECRET,
applicationId: APPLICATION_ID,
privateKey: APPLICATION_PRIVATE_KEY_PATH,
});
vonage.calls.create({
to: [
{
type: "phone",
number: process.env.TO_NUMBER,
},
],
from: {
type: "phone",
number: process.env.VIRTUAL_NUMBER,
},
ncco: [
{
action: "talk",
text: "This is a text to speech call from Vonage",
},
],
});
To test the Conversations API, you will need to enable Voice for your Vonage Application and set the answer Webhook. I use NGROK as well, so it should look like this. https://<URL>.ngrok.io/webhooks/answer
When working with Conversations API, I usually do:
Create a Conversation
Create a User
Create a Member
and put that into the /webhooks/answer
//server.js
require('dotenv').config();
let express = require('express');
let cookieParser = require('cookie-parser');
let logger = require('morgan');
let app = express();
let port = 5001;
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static('public'));
const NGROK_URL = process.env.NGROK_URL;
const Vonage = require('#vonage/server-sdk');
const vonage = new Vonage({
apiKey: process.env.API_KEY,
apiSecret: process.env.API_SECRET,
applicationId: process.env.APPLICATION_ID,
privateKey: process.env.APPLICATION_PRIVATE_KEY_PATH
});
app.post('/webhooks/answer', async (req, res) => {
console.log('🚚 answer', req.body);
let result = req.body;
const createCallConversationPromise = (ConvName, ConvDisplayName) => new Promise((resolve, reject) => {
vonage.conversations.create({
"name": ConvName,
"display_name": ConvDisplayName,
}, (error, result) => error ? reject(error) : resolve(result));
});
const createCallUserPromise = (Name, DisplayName) => new Promise((resolve, reject) => {
vonage.users.create({
name: Name,
display_name: DisplayName,
}, (error, result) => error ? reject(error) : resolve(result));
});
const createCallMemberPromise = (ConvId, UserId) => {
vonage.conversations.members.create(ConvId, {
"action": "join",
"user_id": UserId,
"channel": {
"type": "app"
}
}, (error, result) => {
if(error) {
console.log('\nπŸ”₯ Error Creating Member', error);
}
else {
console.log('\nβœ… Created Member with ConvId', ConvId, 'and UserId',UserId)
console.log(result);
}
})
};
try {
let ConvName = result.from + result.to + result.conversation_uuid;
console.log('nβœ… ConvName', ConvName);
let ConvDisplayName = result.from + result.to + result.conversation_uuid;
const conversation = await createCallConversationPromise(ConvName, ConvDisplayName);
process.env.CONVERSATION_ID = conversation.id;
console.log('\nβœ… CONVERSATION_ID', process.env.CONVERSATION_ID);
let Name = result.from + result.conversation_uuid;
let DisplayName = result.from;
const callUser = await createCallUserPromise(Name, DisplayName);
console.log('\nβœ… UserId', callUser.id)
let ConvId = conversation.id;
let UserId = callUser.id;
const memberUser = await createCallMemberPromise(ConvId, UserId);
let ncco = [
{
action: "talk",
text: "<speak><lang xml:lang='en-GB'>Welcome to Vonage Development inbound call testing</lang></speak>",
voiceName: "Emma"
},
];
res.json(ncco);
} catch (error) {
console.log("πŸ”₯ Error", error);
let ncco = [
{
"action": "talk",
"text": "<speak><lang xml:lang='en-GB'>Error on Process Answer</lang></speak>",
"voiceName": "Emma"
}
];
res.json(ncco);
}
});

Unable to get SHOP name

In the previous version I used to get the current shop name is like this:
router.get("/api/app", async (ctx) => {
let shop = ctx.session.shop;
});
but, in the new version, i can't get the current shop name using ctx.session.shop, i don't see any object on the log named name, and also the session token, i do see session token and shop name on the reffer object, but i think there is another way where i can access those directly.
so, how do i get the current shop name ?
here is my code:
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 session from "koa-session";
import next from "next";
import Router from "koa-router";
import koaBody from "koa-body";
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:\/\//, ""),
API_VERSION: ApiVersion.October20,
IS_EMBEDDED_APP: true,
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 = {};
const server = new Koa();
const router = new Router();
router.get("/api/test", async (ctx) => {
return (ctx.body = ctx.session);
});
app.prepare().then(async () => {
server.keys = [Shopify.Context.API_SECRET_KEY];
server.use(
session(
{
sameSite: "none",
secure: true,
},
server
)
);
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.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) {
ctx.redirect(`/auth?shop=${shop}`);
} else {
await handleRequest(ctx);
}
});
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("(.*)", verifyRequest(), handleRequest); // Everything else must have sessions
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
Thanks in advance.
Here's how to do it inside your server.js file:
For your index route i.e router.get("/") set the cookie for shop name that can be fetched as shown below. Once the cookie is set when the shop owners opens the app from the admin, now when you open the app from your redirect link which is set in partners portal usually ngrock then the shop name will be set automatically from the cookie set earlier
Full Code:
router.get("/", async ctx => {
const shop = ctx.query.shop
if (shop) {
console.log("setting cookie");
ctx.cookies.set("shop_name", shop, {
secure: true,
sameSite: 'none',
httpOnly: false
})
}
if (ctx.request.header.cookie) {
var cookies_fetched = parseCookie(ctx.request.header.cookie)
// This shop hasn't been seen yet, go through OAuth to create a session
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
ctx.redirect(`/auth?shop=${cookies_fetched.shop_name}`)
} else {
await handleRequest(ctx)
}
} else {
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
ctx.redirect(`/auth?shop=${shop}`)
} else {
await handleRequest(ctx)
}
}
})

Resources