Trying to get the response from Twilio after user send the SMS - javascript

everyone, a newbie in programming here. I am currently taking coding camp for my summer project and I am creating a final project to:
create the meal reminder (SMS) every morning (timer trigger has to work first before HTTP trigger) --> then, users will type what ingredient they would like to have in their meal --> Twilio will call the food API to send one random recipe to the user's phone.
The only thing that works in the code is the meal reminder every 6.50 AM but next steps don't seem to work to me, not sure what's wrong.
I am kinda lost what to do since I have debugged for like a week with my mentor. Could you please enlighten me on what I should do so I can get a response whenever I type specific ingredient such as bread or rice? Because I use both HTTP trigger to call my food API and timer triggers to give the user the reminder every morning and afternoon.
Here is my HTTP trigger function:
const fetch = require('node-fetch')
const express = require('express')
const MessagingResponse = require('twilio').twiml.MessagingResponse;
const bodyParser = require('body-parser');
const app = express();
const http = require('http');
module.exports = async function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
const userInputRecipe = (req.query.userInputRecipe || (req.body && req.body.userInputRecipe));
app.post('/sms', (req, res) => {
const twiml = new MessagingResponse();
userInputRecipeFromSMS = req.body.Body;
context.log(req.body.Body);
// const userTag = (req.query.tag || (req.body && req.body.tag));
// const size = (req.query.size || (req.body && req.body.size));
let resultData = await fetch("https://spoonacular-recipe-food-nutrition-v1.p.rapidapi.com/recipes/findByIngredients?ingredients="+ userInputRecipe+"&number=1&ignorePantry=true&ranking=1", {
"method": "GET",
"headers": {
"x-rapidapi-key": process.env.foodKey,
"x-rapidapi-host": process.env.foodHost
}
}).then(response => {
context.log(response);
})
.catch(err => {
console.error(err);
});
let recipeData = await resultData;
context.log(recipeData);
//Show result to response body
context.res = {
// status: 200, /* Defaults to 200 */
//TODO: create objects for needed to show via Twilio API SMS, HTTP Timer trigger etc
body: recipeData
};
// SMS reply to update and fix
if (req.body.Body == userInputRecipeFromSMS) {
twiml.message('recipe please!' + recipeData);
} else if (req.body.Body == 'recipe yes!') {
twiml.message('I need that');
} else if (req.body.Body == 'recipe yes!' + userInputRecipe) {
twiml.message(recipeData);
} else if (req.body.Body == "") {
twiml.message("Oops, this recipe is not available. Try again by sending us another ingredient.");
} else {
twiml.message(
'Sorry, this recipe is not available. Try again by sending us another ingredient.'
);
}
res.writeHead(200, { 'Content-Type': 'text/xml' });
res.end(twiml.toString());
});
http.createServer(app).listen(1337, () => {
console.log('Express server listening on port 1337');
});
context.done();
}.catch(err => {
context.log.error("Twilio Error: " + err.message + " -- " + err.code);
context.res = {
status: 500,
body: `Twilio Error Message: ${err.message}\nTwilio Error code: ${err.code}`
};
context.done();
});
And here is my timer function:
const fetch = require ('node-fetch')
const twiAccountSid = process.env.TWILIO_SID;
const twiAuthToken = process.env.TWILIO_TOKEN;
const client = require('twilio')(twiAccountSid, twiAuthToken);
module.exports = async function (context, myTimer) {
var timeStamp = new Date().toISOString();
if (myTimer.IsPastDue)
{
context.log('JavaScript is running late!');
}
client.messages
.create({ from: process.env.SENDER_NUMBER,
body: "Morning! Time to have breakfast 😋🥘. What ingredient would you like to have?",
to: process.env.RECIPIENT_NUMBER,
mediaUrl: "https://miro.medium.com/max/700/1*OtRzMDGD0qepGUCToZHZ3Q.jpeg",
})
.then(message => {
context.log("Message sent");
context.res = {
body: 'Text successfully sent'
};
context.log('JavaScript timer trigger done!', timeStamp);
context.done();
}).catch(err => {
context.log.error("Twilio Error: " + err.message + " -- " + err.code);
context.res = {
status: 500,
body: `Twilio Error Message: ${err.message}\nTwilio Error code: ${err.code}`
};
context.done();
});
// just try to call my http trigger function
async function myMessage(){
let httpfunc = "https://serverproject1.azurewebsites.net/api/tastyapirecipe"
let resp = await fetch(httpfunc, {
method: 'GET'
});
let data = await resp.json()
return data;
}
let myResp = await myMessage()
context.res = {
myResp: myResp
}
}
This is the error that I get when I test it on Azure portal
the error
I would appreciate if any of you would like to help to figure out what's wrong with it. I will need to present this project in a week later. Thank you!

Related

How to improve sequential promises execution and force fulfillment

This code is being used in a Sveltekit web application.
In the first step I get a user jwt token from an api like : dashboard.example.com/auth/local
and in the second step I'm using the response of the first api call to get full information from an api endpoint like this : example.com/api/users/token
This is an endpoint in an Sveltekit application:
import { json as json$1, error } from '#sveltejs/kit';
import axios from 'axios';
import md5 from 'md5';
import { SITE_ADDRESS } from '$lib/Env';
let userToken;
/** #type {import('#sveltejs/kit').RequestHandler} */
export async function POST({ request }) {
const bodyData = await request.json();
let identifier = bodyData.data.identifier;
let password = bodyData.data.password;
let loginToken = bodyData.data.loginToken;
let newLoginToken = md5(identifier + password + process.env.SECURE_HASH_TOKEN);
let dataResult = await axios
.post(`${import.meta.env.VITE_SITE_API}/auth/local`, {
identifier: identifier,
password: password
})
.then((response) => {
return response.data;
})
.then((response) => {
let userSummaryData = response;
userToken = md5(
userSummaryData.user.username + userSummaryData.user.id + process.env.SECURE_HASH_TOKEN
);
let userCompleteData = axios
.post(`${SITE_ADDRESS}/api/users/${userToken}`, {
data: {
userID: userSummaryData.user.id,
username: userSummaryData.user.username
}
})
.then((response) => {
return {
userJWT: userSummaryData.jwt,
userSummary: userSummaryData.user,
userFullSummary: response.data.userFullSummary
};
});
return userCompleteData;
})
.catch((error) => {
// console.log(' ---- Err ----');
});
if (dataResult && newLoginToken == loginToken) {
return json$1(
{
userJWT: dataResult.userJWT,
userSummary: dataResult.userSummary,
userFullSummary: dataResult.userFullSummary
},
{
headers: {
'cache-control': 'private, max-age=0, no-store'
}
}
);
} else if (dataResult && newLoginToken != loginToken) {
throw error(400, 'Something wrong happened');
}
throw error(401, 'Something wrong happened');
}
This code is work perfectly in localhost. But when I test it on host I get error 401.
and the question is :
Why this works on localhost but doesn't work on the server?
How can I improve this kind of promises (I'd like to use the response of the first api call in the second api call and return both
as a result)

express router test with multiple handlers

I am testing my guard middleware, but altough everything seems to be working fine my expect statement fails.
/// auth.test.js
const request = require('supertest');
const express = require('express');
const app = require('../../app');
const authMiddleware = require('./auth.middleware');
const mockRes = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.sendStatus = jest.fn().mockReturnValue(res);
res.send = jest.fn().mockReturnValue(res);
return res;
};
describe('Authorization', () => {
const guardedRouter = express.Router();
guardedRouter.get(
'/guardedandauthenticated',
[authMiddleware.authenticate, authMiddleware.authorize('admin')],
(req, res, _next) => {
console.log('seems to be working');
res.status(200);
console.log('res is 200000000');
},
);
let accessToken = '';
beforeAll(async () => {
const res = await request(app).post('/auth/login').send({
username: 'admin',
password: 'admin',
});
expect(res.status).toBe(200);
accessToken = res.body.accessToken;
});
it('should allow access to authorized roles', () => {
const response = mockRes();
// #ts-ignore
guardedRouter.handle(
{
headers: { authorization: `Bearer ${accessToken}` },
url: '/guardedandauthenticated',
method: 'GET',
},
response,
);
// THIS EXPECTATION IS FAILED
expect(response.status).toHaveBeenCalledWith(200);
});
});
/// auth.middleware.js
module.exports.authorize = role => {
return async (req, res, next) => {
if (!req.user) {
return res.status(403).send({
message: 'Unauthorized! No token provided!',
});
}
if (req.user.role === undefined) {
const privileges = await userService.getUserPrivileges(req.user.id);
req.user.role = privileges.map(f => f.privilege_name);
}
const userRoles = req.user.role;
const rolesToCheck = Array.isArray(role) ? role : [role];
if (!rolesToCheck.every(r => userRoles.includes(r))) {
return res.status(403).send({
message: `Unauthorized! Required privileges are: ${userRoles.toString()}`,
});
}
return next();
};
};
/// jest outcome
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: 200
Number of calls: 0
I cleaned up the code, my similar assertions are successfull, and the code seems to be working fine, either the way I setup router is incorrect, or, actually I have no clue. Console messages in the router are on the jest output, so it works fine.
Thanks in Advance,
well it turned out to be a jest issue, you need to tell jest that you are done.
it('should allow access to authorized roles', async done => {
const res = { statusCode: 100 };
res.status = function (code) {
res.statusCode = code;
return res;
};
// #ts-ignore
guardedRouter.handle(
{
headers: { authorization: `Bearer ${accessToken}` },
url: '/guardedandauthenticated',
method: 'GET',
},
res,
);
setTimeout(() => {
done();
expect(res.statusCode).toBe(200);
}, 300);
});
so I added a done callback to test case, and checked value after the handler is done. This still does not look like an ideal solution. The thing is that, handle will call 3 functions, one of them is async, I could not get it to report correct without setting a timer. There should be a solution without the timer, can anyone help with that?

Getting single message from Graph

I'm trying to get a single email from an Office 365 Mailbox.
I'm sending the email id to my app via a POST (req.body.id) and then calling this code in order to get some email properties:
router.post('/id', async function(req, res, next) {
console.log("email with ID -> ", req.body.id)
let parms = { title: 'Inbox', active: { inbox: true } };
const accessToken = await authHelper.getAccessToken(req.cookies, res);
const userName = req.cookies.graph_user_name;
if (accessToken && userName) {
parms.user = userName;
// Initialize Graph client
const client = graph.Client.init({
authProvider: (done) => {
done(null, accessToken);
}
});
try {
const result = await client
.api('/me/messages/', req.body.id)
.select('id,subject,from,toRecipients,ccRecipients,body,sentDateTime,receivedDateTime')
.get();
parms.messages = result.value;
console.log("email -> ", result.value);
res.render('message', parms);
} catch (err) {
parms.message = 'Error retrieving messages';
parms.error = { status: `${err.code}: ${err.message}` };
parms.debug = JSON.stringify(err.body, null, 2);
res.render('error', parms);
}
} else {
// Redirect to home
res.redirect('/');
}
});
At the moment, result.value contains all of the messages in the mailbox instead of just the message with provided id.
Could someone tell me where my error is, please?
The api method has a single path parameter. Calling it like .api('/me/messages/', req.body.id) is effectivly sending it a path ("/me/messages/") along with an additional parameter it ignores.
You need to send it a single string so you'll need to append the req.body.id to the path ({path} + {id}):
const result = await client
.api('/me/messages/' + req.body.id)
.select('id,subject,from,toRecipients,ccRecipients,body,sentDateTime,receivedDateTime')
.get();

Send a POST request via Axios to a Firebase Cloud Function

I try to send a simple request to a Firebase function, but I get the same error every time... Apparently, the Firebase function does not receive the data I want to transmit from the Axios request.
This is the Firebase function :
[...] // Some imports
exports.completeProfile = functions.https.onRequest((req, res) => {
// Debug
console.log(req);
console.log(req.body);
console.log(req.method);
console.log("Test: " + userId + ", " + profilePicture + ", " + username);
// We recover the data
const userId = req.body.userId; // return "undefined"
const profilePicture = req.body.profilePicture; // return "undefined"
const username = req.body.username; // return "undefined"
// we're checking to see if they've been transferred
if (!userId || !profilePicture || !username) {
// At least one of the 3 required data is not completed
console.error("Error level 1: missing data");
return res.status(400).send("Error: missing data");
}
[...] // (We have all the data, we continue the function)
});
And here is my Axios request :
axios
.post(
'<FIREBASE CLOUD FUNCTION URL>',
{
userId: '12345667',
profilePicture: 'https://profilepicture.com/url',
username: 'test',
}
)
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
When I run the Axios query, I always come across the "Network Error" error. Here is what console.log(error); gives :
And here are the server logs:
How to solve the problem? Thanks for your help.
change your firebase code to this
var cors = require("cors");
completeProfileFn = (req, res) => {
// Debug
console.log(req);
console.log(req.body);
console.log(req.method);
console.log("Test: " + userId + ", " + profilePicture + ", " + username);
// We recover the data
const userId = req.body.userId; // return "undefined"
const profilePicture = req.body.profilePicture; // return "undefined"
const username = req.body.username; // return "undefined"
// we're checking to see if they've been transferred
if (!userId || !profilePicture || !username) {
// At least one of the 3 required data is not completed
console.error("Error level 1: missing data");
return res.status(400).send("Error: missing data");
}
// (We have all the data, we continue the function)
};
// CORS and Cloud Functions export logic
exports.completeProfile = functions.https.onRequest((req, res) => {
var corsFn = cors();
corsFn(req, res, function() {
completeProfileFn(req, res);
});
});
It is a CORS issue.

Firebase function to fetch data from Firebase DB to make Push notification

I have chat app with firebase database and Firebase cloud messaging. I can send firebase notification via console but in real scenario it should be automatic. To make automatic notification,My friend wrote Index.js (Added in cloud functions) file for me but its not sending notifications.
As per our logic function should trigger whenever there is any new entries (in any node or in any room) and fetch these values by firebase function and make post request to FCM server to make notification to receiver device (get value of receiver device from token_To).
Message
Message_From
Time
Type
token_To
Index.js
var functions = require('firebase-functions');
var admin = require('firebase-admin');
var serviceAccount = require('./demofcm-78aad-firebase-adminsdk-4v1ot-2764e7b580.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://demofcm-78aad.firebaseio.com/"
})
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
exports.setUserNode = functions.auth.user().onCreate(event => {
// ...
});
exports.notifyMsg = functions.database.ref('/{chatroom}/{mid}/')
.onWrite(event => {
if (!event.data.val()) {
return console.log('Message Deleted');
}
const getDeviceTokensPromise = admin.database().ref('/{chatroom}/{mid}/token_to').once('value');
return Promise.all([getDeviceTokensPromise]).then(results => {
const tokensSnapshot = results[0];
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
const payload = {
notification: {
title: 'You have a new Message!',
body: event.data.val().Message
}
};
const tokens = Object.keys(tokensSnapshot.val());
return admin.messaging().sendToDevice(tokens, payload).then(response => {
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
});
Firebase function Log
How can i fetch above mentioned values of any newly added node in same room(9810012321-9810012347) or any other room(9810012321-9810012325) from database and send it to FCM to make notification
Thanks in Advance.
What i did is created a Message node and I believe doing this by users key. ie, having the receiver(toId) and sender (fromId) key to send the notification.
Hope it helps.
exports.sendMessageNotification = functions.database.ref('/messages/{pushId}')
.onWrite(event => {
let message = event.data.current.val();
console.log('Fetched message', event.data.current.val());
let senderUid = message.fromId;
let receiverUid = message.toId;
let promises = [];
console.log('message fromId', receiverUid);
console.log('catch me', admin.database().ref(`/users/${receiverUid}`).once('value'));
if (senderUid == receiverUid) {
//if sender is receiver, don't send notification
//promises.push(event.data.current.ref.remove());
return Promise.all(promises);
}
let messageStats = message.messageStatus;
console.log('message Status', messageStats);
if (messageStats == "read") {
return Promise.all(promises);
}
let getInstanceIdPromise = admin.database().ref(`/users/${receiverUid}/pushToken`).once('value');
let getSenderUidPromise = admin.auth().getUser(senderUid);
return Promise.all([getInstanceIdPromise, getSenderUidPromise]).then(results => {
let instanceId = results[0].val();
let sender = results[1];
console.log('notifying ' + receiverUid + ' about ' + message.text + ' from ' + senderUid);
console.log('Sender ', sender);
var badgeCount = 1;
let payload = {
notification: {
uid: sender.uid,
title: 'New message from' + ' ' + sender.displayName,
body: message.text,
sound: 'default',
badge: badgeCount.toString()
},
'data': {
'notificationType': "messaging",
'uid': sender.uid
}
};
badgeCount++;
admin.messaging().sendToDevice(instanceId, payload)
.then(function (response) {
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
});
});
const getDeviceTokensPromise = event.data.child('token_To');
should be there instated of getting data from database reference.
or
with fixed path without wildcard like below
const getDeviceTokensPromise = admin.database().ref('/${chatroom}/${mid}/token_to').once('value');
where chatroom and mid is variable which contain value
Second thing:
if (!tokensSnapshot.exists()) {
should in replace of
if (!tokensSnapshot.hasChildren()) {
third thing:
I am not sure about push notification tokenId but
is it required to do?
const tokens = Object.keys(tokensSnapshot.val());
may be we can use directly like below to send push notification
const tokens = tokensSnapshot.val();
You could store all device tokens in a node called tokens like in my example. Tokens could be an array if you would like one user to be able to get notifications on multiple devices. Anyway, store them by their UID.
This works for both Andriod and iOS.
Here is my code:
function loadUsers() {
let dbRef = admin.database().ref('/tokens/' + recieveId);
console.log(recieveId)
let defer = new Promise((resolve, reject) => {
dbRef.once('value', (snap) => {
let data = snap.val();
console.log("token: " + data.token)
//userToken = data.token
resolve(data.token);
}, (err) => {
reject(err);
});
});
return defer;
}
Next we create the notification. I created a lastMessage node to capture just the last message sent in the chat. It is just updated every time a new message is sent in a chat between two users. Makes it easy to get the value. Also makes it easy to show the message on the Conversations screen where there is a list of users who are in a conversation with the current user.
exports.newMessagePush =
functions.database.ref('/lastMessages/{rcId}/{sendId}').onWrite(event => {
if (!event.data.exists()) {
console.log("deleted message")
return;
}
recieveId = event.params.rcId
//let path = event.data.adminRef.toString();
// let recieveId = path.slice(53, 81);
return loadUsers().then(user => {
console.log("Event " + event.data.child("text").val());
let payload = {
notification: {
title: event.data.child("name").val(),
body: event.data.child("text").val(),
sound: 'default',
priority: "10",
}
};
return admin.messaging().sendToDevice(user , payload);
});
});
To implement this logic on your current data structure, just change this line:
let dbRef = admin.database().ref('/tokens/' + recieveId);
and this line:
exports.newMessagePush =
functions.database.ref('/lastMessages/{rcId}/{sendId}').onWrite(event
=> {
to your token location:
let dbRef =
admin.database().ref('/${chatroom}/${mid}/token_to');
and your conversation location:
exports.notifyMsg = functions.database.ref('/{chatroom}/{mid}/')
.onWrite(event => {
Then just change the notification payload be the message you want to display and throw in your error handling on the end of the sendToDevice function, as you did in your code.
Hopefully you figured all this out already but if not maybe this will help you or others trying to use Cloud Functions for notifications.
let payload = {
notification: {
uid: sender.uid,
title: 'New message from' + ' ' + sender.displayName,
body: message.text,
sound: 'default',
badge: badgeCount.toString()
},
'data': {
'notificationType': "messaging",
'uid': sender.uid
}
};
There are two types of FCMs.
1) Data
2) Notification
For detailed overview : FCM Reference
You have to fix your payload for both FCMS. And for Data FCM you have to extract Data in your FCM Service (Client) and generate a push notification according to your need.

Categories

Resources