dialogflow-fulfillment-library and express, what to res on? - node.js

I'm trying to use the dialog-fulfillment-library with express, without firebase functions. I'm having trouble finding how to res on the agent though.
const { WebhookClient, Card, Suggestion } = require('dialogflow-fulfillment');
module.exports = function () {
let self = {};
self.create = function (req, res, next) {
const agent = new WebhookClient({request: req, response: res});
agent.add(new Suggestion(`Quick Reply`));
agent.add(new Card({
title: `Title: this is a card title`,
imageUrl: 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png',
text: `This is the body text of a card. You can even use line\n breaks and emoji! 💁`,
buttonText: 'This is a button',
buttonUrl: 'https://assistant.google.com/'
})
);
res.json(agent);
};
return self;
};
I get a TypeError: Converting circular structure to JSON
I've tried decycling the agent but then it doesn't work on the dialogflow side.
using:
res.send(JSON.stringify(agent, decycle()));
returns: Webhook call failed. Error: Failed to parse webhook JSON response: Cannot find field: request_ in message google.cloud.dialogflow.v2.WebhookResponse.
Has anyone used it in this way or is it not possible?

I have submitted a Pull Request for the same.
Following code works for me.
package.json
{
"name": "Test_Agent",
"version": "0.0.1",
"description": "Test Agent webhook",
"main": "server.js",
"author": "Abhinav Tyagi, New Delhi, India",
"dependencies": {
"dialogflow-fulfillment": "^0.4.1",
"body-parser": "^1.18.3",
"express": "^4.16.3",
"actions-on-google": "^2.2.0"
}
}
server.js
'use strict';
const {WebhookClient} = require('dialogflow-fulfillment');
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
function welcome (agent) {
agent.add(`Welcome to Express.JS webhook!`);
}
function fallback (agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
function WebhookProcessing(req, res) {
const agent = new WebhookClient({request: req, response: res});
console.info(`agent set`);
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
// intentMap.set('<INTENT_NAME_HERE>', yourFunctionHandler);
agent.handleRequest(intentMap);
res.status(200).send(agent);
}
// Webhook
app.post('/', function (req, res) {
console.info(`\n\n>>>>>>> S E R V E R H I T <<<<<<<`);
WebhookProcessing(req, res);
});
app.listen(8080, function () {
console.info(`Webhook listening on port 8080!`)
});
Make sure to use both action-on-google and dialogflow-fulfillment modules.
The intentMap keys is the intent.displayName.

Related

Invalid or unknown request type (not a Dialogflow v1 or v2 webhook request)

I am working with dialogflow for a chatbot app.
But when I run NodeJS always response with error
Invalid or unknown request type (not a Dialogflow v1 or v2 webhook request)
in the line where I declare the agent.
This is the original code. What is wrong?? :(
'use strict';
const express = require("express");
bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
const { WebhookClient } = require("dialogflow-fulfillment");
var port = process.env.PORT || 3000;
app.post("/order", express.json(), function (req, res) {
// Error on next line
const agent = new WebhookClient({ request: req, response: res });
console.log('Dialogflow Request headers: ' + JSON.stringify(req.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(req.body));
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
agent.handleRequest(intentMap);
})
app.get('/hello', function (req, res) {
res.send ('Hello World');
})
app.listen(port, () => {
console.log("Listening port: " + port);
})

Websocket error that is only occuring on live server

Bizarre situation going on here with my websocket. It's giving me the error
Error during WebSocket handshake: Unexpected response code: 200
Which for the life of me I cannot figure out why.
I've set up a very similar server with the exact same code with the exact same servers & settings. The only difference is one server has a .com TLD while the other has a .sg TLD.
I've reduced it down to the simplest form, which is the below and the error is still happening. It's on the api side for sure and not the frontend as the frontend can connect to the .com TLD.
Below is all the code that I believe is related to the problem. If you think there might be other areas please ask and I will post other areas. It's hosted on AWS Elastic Beanstalk. I've also set the SSL cert to domain.com & *.domain.com
Does anybody know why this might be happening?
The bizarre thing to me is I literally set up a server with these exact settings and it's working perfectly fine.
server.js (start point in package.json)
'use strict';
(async function() {
// Server Setup
const WSServer = require('ws').Server;
const app = await require('./app.js');
const server = require('http').createServer(app);
const port = 3075;
// Create web socket server on top of a regular http server
const wss = new WSServer({
server: server
});
// Also mount the app here
// server.on('request', app);
let sendMessage = {
"connected":"connected to web socket",
}
wss.on('connection', function connection(ws) {
ws.send(JSON.stringify(sendMessage));
ws.on('message', async function incoming(message) {
let interval = setInterval(async () => {
console.log("ping");
ws.send(message);
}, 500);
});
ws.on('close', function close() {
console.log('/socket connection Closed');
});
});
server.listen(process.env.PORT || port, function() {
console.log(`AppName https/wss is listening on port ${process.env.PORT || port}`);
});
})();
app.js (removed much of the code that is irrelevant to this question)
module.exports = (async function() {
const {Config,Environments} = await require("./common/config");
const packageJson = require('./package.json');
// Handler
const AuthFunc = await require("./funcs/user/auth");
const BillingFunc = await require("./funcs/billing/billing");
const ObjectUtil = require("./utils/object");
const AccountStatusEnum = require("./enums/account-status").accountStatusEnum;
const sleep = require('util').promisify(setTimeout);
const {t} = require('./translations/i18n').i18n;
const {availableLanguages} = require('./translations/all');
// Simulate real API calls
const delayResponse = 250;// milliseconds
// ==============================================
// Base setup
// ==============================================
process.env.TZ = "Etc/GMT"
const express = require('express');
const app = express();
var expressWs = require('express-ws')(app);
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(require('express-useragent').express());
// Cors
app.use(async function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, auth-id, auth-token, x-csrf-token, _csrf");
res.header('Access-Control-Allow-Methods', 'PATCH, POST, GET, DELETE, OPTIONS');
next();
});
// ==============================================
// Auth
// ==============================================
const normRoute = (req, res, next) => {
req.body = (req.body != undefined && req.body != null) ? ObjectUtil.toCamelCaseKeys(req.body) : req.body;
response(req,res,next(req));
}
// Does not need to be logged in but passes user info if logged in
const passRoute = async (req, res, next) => {
authRoute(req, res, next, AccountStatusEnum.any, true);
}
const authRoute = async (req, res, next, minimumStatus, passRoute) => {
req.body = (req.body != undefined && req.body != null) ? ObjectUtil.toCamelCaseKeys(req.body) : req.body;
let authId = (req.headers['auth-id'] !== undefined) ? req.headers['auth-id'] :"";
let authToken = (req.headers['auth-token'] !== undefined) ? req.headers['auth-token'] : "";
let r = await AuthFunc.authUser(
req,
req.ip,
req.useragent,
authId,
authToken,
minimumStatus,
);
if(r.err.code){
if(r.err.authError && passRoute){
response(req,res,next(req,null));
return false;
}else{
response(req,res,r);
return false;
}
}
let user = r.res.user;
r = await BillingFunc.updateSubStatus(req,user);
if(r.err.code){ response(req,res,r); return false; }
if(r.res.userStatus !== undefined){
user.status = r.res.userStatus;
}
response(req,res,next(req,user));
}
// ===============================================================
// Routes
// ===============================================================
app.get('/', function(req, res) {
res.send(
'<html>'+
'<head></head>'+
'<body>'+
'API is running <br>'+
'App: '+Config.FrontEnd.AppName+'<br>'+
'Env: '+Config.Env+'<br>'+
'Version: '+packageJson.version+'<br>'+
'</body>'+
'</html>'
);
});
// ==============================================
// Response type
// ==============================================
const response = async (req,res,obj) => {
await obj;
Promise.resolve(obj).then(function(val) {
if(delayResponse >= 1 && (Config.Env === Environments.Local)){
setTimeout(function(){
resume(req,res,val);
}, delayResponse);
return true;
}
resume(req,res,val);
});
}
const resume = (req,res,obj) => {
obj = (obj === undefined) ? {} : obj;
var status = (obj.status !== undefined) ? obj.status : 200;
// Let status override settings
if(obj.status === undefined){
if((obj.err.code)){
status = 400;
}else if(obj.res === undefined || ObjectUtil.isEmpty(obj.res)){
// status = 204;
}
}
let json = {};
json.err = obj.err;
json.res = obj.res;
json = ObjectUtil.toCamelCaseKeys(json);
res.status(status).json(json);
}
// ==============================================
// Return the app
// ==============================================
return app;
})();
package.json
{
"name": "name-api",
"version": "1.17.0",
"description": "name-api",
"main": "server.js",
"dependencies": {
"axios": "^0.21.1",
"cookie-parser": "^1.4.4",
"express": "^4.17.1",
"express-session": "^1.17.1",
"express-useragent": "^1.0.13",
"express-ws": "^4.0.0",
"googleapis": "^50.0.0",
"mocha": "^8.0.1",
"mysql": "github:mysqljs/mysql",
"nodemailer": "^6.4.6",
"query-string": "^6.12.1",
"stripe": "^8.60.0",
"ws": "^7.4.3"
},
"devDependencies": {},
"scripts": {
"test": "mocha",
"start": "node server.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/..."
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/..."
},
"homepage": "https://github.com/..."
}
Problem solved.
It was a load balancer issue. Apparently this doesn't work well with Classic Load Balancer. I believe it's due to the way it's requested.
Changing over to a Application Load balancer fixed the issue for me.

Using sessions to share data between different files and routes

Relatively new to node.js, I am wondering if anyone could help me please.
My MERN stack is working fine and authentication is working.
I am integrating Google's Dialogflow into a MERN stack and I would like to pass a user's username to the fulfillmentRoutes.js file.
The fulfillmentRoutes.js is as follows (I removed unnecessary code, it is just the bare bones);
fulFillmentRoutes.js
const {WebhookClient, Payload, Card} = require('dialogflow-fulfillment');
const express = require('express');
const router = express.Router();
const jwtDecode = require('jwt-decode');
const chatbot = require('../chatbot/chatbot');
const mongoose = require('mongoose');
const passport = require('passport');
const keys = require('../config/keys');
const Coupon = mongoose.model('coupon');
const Counter = mongoose.model('counters');
const Define = mongoose.model('define');
const sourceFile = require('./api/users.js');
const Themes = require('../models/Themes');
const Time = require('../models/Time');
const Questionexp = require('../models/Questionexp');
var cookieParser = require('cookie-parser');
var session = require('express-session');
module.exports = app => {
app.post('/api/df_text_query', async (req, res) => {
let responses = await chatbot.textQuery(req.body.text, req.body.userID, req.body.parameters);
res.send(responses[0].queryResult);
});
app.post('/api/df_event_query', async (req, res) => {
let responses = await chatbot.eventQuery(req.body.event, req.body.userID, req.body.parameters);
res.send(responses[0].queryResult);
});
app.post('/', async (req, res) => {
const agent = new WebhookClient({ request: req, response: res });
async function learn(agent) {
Demand.findOne({'course': agent.parameters.courses}, function(err, course) {
if (course !== null ) {
course.counter++;
course.save();
} else {
const demand = new Demand({course: agent.parameters.courses});
demand.save();
}
});
let responseText = `You want to learn about ${agent.parameters.courses}.
Here is a link to all of my courses: https://www.udemy.com/user/jana-bergant`;
let coupon = await Coupon.findOne({'course': agent.parameters.courses});
if (coupon !== null ) {
responseText = `You want to learn about ${agent.parameters.courses}.
Here is a link to the course: ${coupon.link}`;
}
agent.add(responseText);
}
async function welcome(agent) {
agent.add(“Hello!);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Welcome Intent - custom', continuesession);
agent.handleRequest(intentMap);
});
}
The file above file sits in a folder outside the api folder, but inside the routes folder.
I want to share a user's username is personalise conversation using the fulfillment module above.
I have declared a session in my server.js file which sits in the main app root.
server.js
app.use(session({
resave: true,
saveUninitialized: true,
secret: "secret"
}));
In my users.js file in ./routes/api directory, I did the following in users.js
users.js
router.get('/current', passport.authenticate('jwt', { session: false }),
(req, res) => {
req.session.test = req.user.email;
res.send(req.session.test);
res.json({
id: req.user.id,
firstname: req.user.firstname,
lastname: req.user.lastname,
email: req.user.email,
week: req.user.week,
age: req.user.age,
time: req.user.time,
bookmark: req.user.bookmark,
alertDate: req.user.alertDate
});
}
);
However, when I use session extract line (below) in the fulfillmentRoutes.js (file quoted at the beginning) I get undefined.
username = req.session.test;`
Any help would be appreciated please.
Thank you,
P

Node.js + Express - How to log the request body and response body

I have a small api I have built using Node.js and express.
I am trying to create a logger and I need log the request body AND response body.
app.use((req, res) => {
console.log(req);
res.on("finish", () => {
console.log(res);
});
});
"express": "^4.16.3",
However, i am not able to find the body in the req or res object. Please tell me how i can get them. thanks.
For res.body try the following snippet:
const endMiddleware = (req, res, next) => {
const defaultWrite = res.write;
const defaultEnd = res.end;
const chunks = [];
res.write = (...restArgs) => {
chunks.push(new Buffer(restArgs[0]));
defaultWrite.apply(res, restArgs);
};
res.end = (...restArgs) => {
if (restArgs[0]) {
chunks.push(new Buffer(restArgs[0]));
}
const body = Buffer.concat(chunks).toString('utf8');
console.log(body);
defaultEnd.apply(res, restArgs);
};
next();
};
app.use(endMiddleware)
// test
// HTTP GET /
res.status(200).send({ isAlive: true });
You need body-parser that will create body object for you in your request. To do that
npm install body-parser
var bodyParser = require('body-parser')//add this
app.use(bodyParser())//add this before any route or before using req.body
app.use((req, res) => {
console.log(req.body); // this is what you want
res.on("finish", () => {
console.log(res);
});
});
Ran into this problem but didn't like the solutions. An easy way is to simply wrap the original res.send or res.json with your logger.
Put this as middleware before your routes.
app.use(function responseLogger(req, res, next) {
const originalSendFunc = res.send.bind(res);
res.send = function(body) {
console.log(body); // do whatever here
return originalSendFunc(body);
};
next();
});
https://github.com/expressjs/express/blob/master/lib/response.js
res.send has signature of function(body) { return this; }
Here is a working example using the built in PassThrough stream. Remember to use the express.json() built in middleware to enable request body parsing.
After that, you need to intercept all writes to the response stream. Writes will happen on calling write or end, so replace those functions and capture the arguments in a separate stream.
Use res.on('finish', ...) to gather all the written data into a Buffer using Buffer.concat and print it.
const express = require('express');
const { PassThrough } = require('stream')
const app = express();
app.use(express.json());
app.use((req, res, next) => {
const defaultWrite = res.write.bind(res);
const defaultEnd = res.end.bind(res);
const ps = new PassThrough();
const chunks = [];
ps.on('data', data => chunks.push(data));
res.write = (...args) => {
ps.write(...args);
defaultWrite(...args);
}
res.end = (...args) => {
ps.end(...args);
defaultEnd(...args);
}
res.on('finish', () => {
console.log("req.body", req.body);
console.log("res.body", Buffer.concat(chunks).toString());
})
next();
})
app.use('/', (req, res) => {
res.send("Hello");
});
app.listen(3000);
install npm install body-parser
and use this snippet,
var express = require('express')
var bodyParser = require('body-parser')
var app = express()
// create application/json parser
var jsonParser = bodyParser.json()
to get json response
app.use(jsonParser, function (req, res) {
console.log(req.body); // or console.log(res.body);
})
There is ready made module https://www.npmjs.com/package/morgan-body
const express = require('express')
const morganBody = require("morgan-body")
const bodyParser = require("body-parser")
const app = express()
const port = 8888
// must parse body before morganBody as body will be logged
app.use(bodyParser.json());
// hook morganBody to express app
morganBody(app, {logAllReqHeader:true, maxBodyLength:5000});
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Hi was looking for same as complete log of request and response as middleware in express js. Found the solution as well w
/*Added by vikram parihar for log */
const moment = require('moment');
const rfs = require("rotating-file-stream");
const geoip = require('geoip-lite');
const { PassThrough } = require('stream')
let path = require('path');
const accessLogStream = rfs.createStream('access.log', {
interval: '1M', // rotate daily
compress: true,
path: path.join(__dirname, '../../log')
});
module.exports = function (req, res, next) {
try {
let geo = geoip.lookup(req.ip);
let country = geo ? geo.country : "Unknown";
let region = geo ? geo.region : "Unknown";
let log = {
"time": moment().format('YYYY/MM/DD HH:mm:ss'),
"host": req.hostname,
"ip": req.ip,
"originalUrl": req.originalUrl,
"geo": {
"browser": req.headers["user-agent"],
"Language": req.headers["accept-language"],
"Country": country,
"Region": region,
},
"method": req.method,
"path": req.path,
"url": req.url,
"body": req.body,
"params": req.params,
"query": req.query,
"response": {
"body": res.body
}
};
const defaultWrite = res.write.bind(res);
const defaultEnd = res.end.bind(res);
const ps = new PassThrough();
const chunks = [];
ps.on('data', data => chunks.push(data));
res.write = (...args) => {
ps.write(...args);
defaultWrite(...args);
}
res.end = (...args) => {
ps.end(...args);
defaultEnd(...args);
}
res.on('finish', () => {
log.response.body = Buffer.concat(chunks).toString()
accessLogStream.write(JSON.stringify(log) + "\n");
})
} catch (error) {
console.log(error)
next(error)
}
next();
}

Express-Validator v4 minimalistic example always return error

I can't get this very basic piece of code to work in the new version. Here is my code
const express = require("express");
const bodyParser = require("body-parser");
const validator = require("express-validator");
const { check, validationResult } = require("express-validator/check");
const app = express();
app.use(bodyParser.json());
app.use(validator());
app.post('/',
[ check("email").isEmail() ],
(req, resp) => {
const errors = validationResult(req);
resp.send(errors.mapped()); });
app.listen(3000, () => { console.log('listening on port 3000'); });
I send a post request with body containing :
{ "email": "ha#gmail.com" }
The Response is ALWAYS an error even if the email I give is perfectly correct
{ "email": { "location": "body", "param": "email", "msg": "Invalid value" } }
Am i missing something ?
That's because req.body.email is always undefined, you have to tell body-parser middleware to parse incoming request bodies into object by adding this line:
app.use(bodyParser.urlencoded({ extended: true }));
One other thing, always use errors.isEmpty() to check whether the validation contains errors or not:
app.post('/send', [ check("email").isEmail() ], (req, resp) => {
console.log(req.body);
const errors = validationResult(req);
if (!errors.isEmpty()) {
return resp.send(errors.mapped());
}
resp.send('no errors');
});

Resources