AWS lambda to firebase realtime db not updated - node.js

'use strict';
var Firebase = require('firebase');
var config = {
apiKey: "apiKey",
authDomain: "projectId.firebaseapp.com",
databaseURL: "https://databaseName.firebaseio.com",
storageBucket: "bucket.appspot.com"
};
if(Firebase.apps.length === 0) {
Firebase.initializeApp(config);
}
exports.handler = function(event, context,callback) {
try{
var request = event.request;
if(request.type === "LaunchRequest"){
let options = {};
options.speechText= "Welcome to TV. Using this skill you can control over 60 channels on your tv. To change to a different channel? You can say for example, play BBC Earth on tv";
options.endSession= false;
context.succeed(buildResponse(options));
}else if(request.type ==="IntentRequest"){
let options ={};
if(request.intent.name === "ChannelIntent"){
var message = 'hey guys';
var ref = Firebase.database().ref().child("hello");
var messagesRef = ref.child('messages');
var messageRef = messagesRef.push(message);
let ChannelName = request.intent.slots.ChannelName.value;
options.speechText= "Channel changed to " +ChannelName+ ". ";
options.endSession= true;
context.succeed(buildResponse(options));
}else{
throw "unknown intent type";
}
}else if(request.type ==="SessionEndedRequest"){
}else{
throw "unknown intent type";
}
} catch(e){
context.fail("Exception: "+e);
}
}
function buildResponse(options){
var response = {
version: "1.0",
response:{
outputSpeech: {
type: "PlainText",
text: options.speechText
},
shouldEndSession: options.endSession
}
};
if(options.repromptText){
response.reponse.reprompt ={
outputSpeech: {
type: "PlainText",
text: options.repromptText
}
};
}
return response;
}
The idea is to use alexa skill to control android tv app. firebase is used to connect alexa skill to the android tv app. The code runs successfully in AWS lambda but firebase db is not updated.
If someone has encountered similar issue and has found solution, please share the solution.
I will add code snippet of my aws lambda function.

Most Firebase operations are async, so you can't just assume success. You must wait for the call to complete:
messagesRef.push(message, (err) => {
if (err) return context.fail(err);
let ChannelName = request.intent.slots.ChannelName.value;
options.speechText= "Channel changed to " +ChannelName+ ". ";
options.endSession= true;
context.succeed(buildResponse(options));
})

Related

Firebase function TypeError .once is not a function and onCreate not working

I was trying to deploy a function to Firebase to send notifications to all admin accounts when a new user signs up to the app, this is my current code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.newDoctorNotification = functions.database.ref("/doctors/{pushId}")
.onCreate((snapshot, context) => {
const newDoctorID = context.params.pushId;
const notificationContent = {
notification: {
title: "New Doctor",
body: "A new doctor just signed up! uid: " + newDoctorID,
icon: "default",
sound: "default",
},
};
const adminTokensRef = functions.database.ref("device_tokens/admin");
const tokens = [];
adminTokensRef.once("value", (querySnapshot) => {
querySnapshot.forEach((adminToken) => {
tokens.push(adminToken.val());
});
});
if (tokens.length > 0) {
return admin.messaging().sendToDevice(tokens, notificationContent)
.then(function(result) {
console.log("Notification sent");
return null;
})
.catch(function(error) {
console.log("Notification failed ", error);
return null;
});
}
});
I have tried many variations such as the get() function and on(), but all give me the same error, I was trying to check the docs on this but they only talked about database triggers so I'm not sure if normal retrieval can work or not.
EDIT:
I updated my code to reach the database node through the snapshot given in the onCreate event, and now it works, although I am facing another problem now, if I push a new doctor under the node "doctors" it doesn't call the function.. but if I hit "test" in the Google Cloud console and it calls the function I get "null" in my snapshot.val() and "undefined" in the newDoctorID above, whereas the snapshot.key gives "doctors", why is it not calling the onCreate function?

AWS Lambda cannot publish to IoT topic properly

I have written Lambda function using JavaScript, which responses to my voice and turns on the LED on my Raspberry.
But I have a problem with publishing its state to my thing topic. While Alexa responses correct ("Turning on" if Im asking to turn it on and "Turning off" if asking to off), my topic doesn't always get the state changes. Some times it gets data and sometime it doesn't and after few more invocations it gets data in bulk, and I cant even get the logic of creating a sequence of data in that bulk.
var AWS = require('aws-sdk');
var config = {};
config.IOT_BROKER_ENDPOINT = "xxxxxx.iot.us-east-1.amazonaws.com";
config.IOT_BROKER_REGION = "us-east-1";
config.IOT_THING_NAME = "raspberry";
var iotData = new AWS.IotData({endpoint: config.IOT_BROKER_ENDPOINT});
var topic = 'LED';
exports.handler = function (event, context) {
...
...
function updatePowerState (intent, session, callback) {
var speechOutput = '';
var newValue = '';
var repromptText = '';
const cardTitle = 'Power';
var sessionAttributes = {};
const shouldEndSession = true;
var value = intent.slots.PowerState.value;
if(value == 'on' || value == 'off') {
newValue = value.toUpperCase();
speechOutput = 'Turning your lamp ' + value;
updateShadow(newValue);
} else {
speechOutput = 'I didnt understand you. Please, repeat your request.';
}
callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
function updateShadow(newValue) {
let payload = {
state: {
desired: {
power_state: newValue
}
}
};
var JSON_payload = JSON.stringify(payload);
var updates = {
topic: topic,
payload: JSON_payload,
qos: 0
};
iotData.publish(updates, (err, data) => {
if(err) {
console.log(err);
}
else {
console.log('Success!');
}
});
}
Do you have any ideas about its causes? Thank you!
Async methods like iotData.publish cause problems into AWS Lambda, because you request a execution and the lambda function ends soon without waiting for the response and processing the request.
Another problem could be your permissions.
var AWS = require('aws-sdk');
var iotdata = new AWS.IotData({endpoint: 'iotCoreEndpoint.iot.us-east-1.amazonaws.com'});
exports.handler = async (event) => {
var params = {
topic: 'topic/topicName',
payload: JSON.stringify(event.body),
qos: 0
};
await iotdata.publish(params).promise()
};
Just make sure to add the required permissions or you can attach the following policy to your lambda role: AWSIoTWirelessFullPublishAccess

Alexa skill which can read Facebook posts using AWS Lambda, Node.js and the Alexa Skills Kit

I am developing an Alexa skill using AWS Lambda, Node.js and the Alexa Skills Kit.I am using a forked from skill-sample-nodejs-fact project & successfully deployed & tested the sample fact project .Now I am trying to modify that code to read posts on some Facebook feeds.First I tried to develop some node application which can read posts & it was successful.Please find below code for your reference.I used fb module -
https://www.npmjs.com/package/fb
const FB = require('fb');
FB.setAccessToken('abc');
const query='cnninternational/posts';
FB.api(query, function (res) {
if(!res || res.error) {
console.log(!res ? 'error occurred' : res.error);
return;
}
console.log(res);
});
Next, I tried to integrate above code block into the lambda function.Unfortunately, I was unable, to read Facebook posts using these codes.Please find those code blocks in the below panel .Also, I checked cloudwatch logs as well.I can see the "GetNewsIntent", but I didn't see "fb-init" , "fb-error" or "fb-exit"entries in logs.Surprisingly, no error in logs as well.I would much appreciate it if someone can help to solve that issue.
'use strict';
const Alexa = require('alexa-sdk');
const FB = require('fb');
const APP_ID = 'abc';
const SKILL_NAME = 'test';
const GET_FACT_MESSAGE = "Here's your news: ";
const STOP_MESSAGE = 'Goodbye!';
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
};
const handlers = {
'LaunchRequest': function () {
this.emit('GetNewsIntent');
},
'GetNewsIntent': function () {
console.log('GetNewsIntent');
const speechOutput = GET_FACT_MESSAGE;
const query='cnninternational/posts';
FB.setAccessToken('abc');
FB.api(query, function (res) {
console.log('fb-init');
if(!res || res.error) {
console.log(!res ? 'error occurred' : res.error);
console.log('fb-error');
return;
}
console.log(res);
speechOutput = speechOutput + res;
console.log('fb-exit');
});
this.response.cardRenderer(SKILL_NAME, speechOutput);
this.response.speak(speechOutput);
this.emit(':responseReady');
},
'AMAZON.StopIntent': function () {
this.response.speak(STOP_MESSAGE);
this.emit(':responseReady');
},
};
Have you implemented account linking? You should be using event.session.user.accessToken for the parameter to setAccessToken().
I have removed this.response.cardRenderer , this.response.speak & changed the code bit.It's working fine now.Please find the below code snippet which can be used to read posts on the BBC Facebook page.
var accessToken = '';
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
};
const handlers = {
'NewSession': function() {
var welcomeMessage = "Welcome to Athena";
welcomeMessage = welcomeMessage +"<break time=\"1s\"/>"+ "<audio src='https://s3.amazonaws.com/my-ssml-samples/Flourish.mp3' />"+"<break time=\"1s\"/>";
welcomeMessage += HELP_MESSAGE;
accessToken = this.event.session.user.accessToken;
if (accessToken) {
FB.setAccessToken(accessToken);
this.emit(':ask', welcomeMessage, HELP_REPROMPT);
}
else {
// If we don't have an access token, we close down the skill.
this.emit(':tellWithLinkAccountCard', "This skill requires you to link a Facebook account. Seems like you are not linked to a Facebook Account. Please link a valid Facebook account and try again.");
}
},
'LaunchRequest': function () {
this.emit('NewSession');
},
'ReadBbcNewsFacebookPostsIntent': function () {
var alexa = this;
FB.api("bbcnews/posts", function (response) {
if (response && !response.error) {
if (response.data) {
var output = "Here are recent posts" + "<break time=\"1s\"/>";
var max = 5;
for (var i = 0; i < response.data.length; i++) {
if (i < max) {
output += "<break time=\"1s\"/>" + "Post " +
(i + 1) +
response.data[i].message.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '')
+ ". ";
}
}
alexa.emit(':ask', output+ ", What would you like to do next?",HELP_MESSAGE);
} else {
// REPORT PROBLEM WITH PARSING DATA
}
} else {
// Handle errors here.
console.log(response.error);
this.emit(':tell', EMPTY_ACCESS_TOKEN_MESSAGE, TRY_AGAIN_MESSAGE);
}
});
}
};

AWS Lambda not firing Email via nodemailer, but does in the local development environment

I'm working on a aws-lambda which is supposed to shoot mail when an event is triggered. I using nodejs for this and below is the code:
"use strict";
exports.sendEmail = function(event, context, callback) {
var config = require('./config');
var fs = require('fs');
var _ = require('lodash');
if (_validSchema(event.payload)) {
var templatePath = config.schemaMapping[event.payload.emailDetails.emailType]["templatePath"]
var emailHTML = _getHTML(templatePath, event.payload.params)
if (emailHTML && templatePath) {
_sendSESEmail(_emailParams(event.payload.emailDetails), emailHTML)
context.succeed(JSON.stringify(_setResponse(200, [{
code: "11",
source: "Email template or Email params in payload",
message: "Please provide correct Email template and correct email params",
detail: "Template path is provided via config and Params via Payload"
}])));
} else
context.fail(JSON.stringify(_setResponse(400, [{
code: "01",
source: "Email template or Email params in payload",
message: "Please provide correct Email template and correct email params",
detail: "Template path is provided via config and Params via Payload"
}])));
} else {
context.fail(JSON.stringify(_setResponse(400, [{
code: "02",
source: "Payload schema",
message: "Please provide correct schema to validate and a payload validating it",
detail: "Payload is provided "
}])));
}
function _validSchema(payload) {
var schemaPath = config.schemaMapping[payload.emailDetails.emailType]["schemaPath"];
var payloadVerification = _verifyPayload(payload, schemaPath);
console.log(payloadVerification.valid);
return payloadVerification.valid;
}
function _emailParams(emailDetails) {
var details = {};
details.to = _.join(emailDetails.to, ',');
details.from = emailDetails.from;
details.cc = _.join(emailDetails.cc, ',');
details.bcc = _.join(emailDetails.bcc, ',');
details.attachments = emailDetails.attachments;
details.subject = emailDetails.subject;
return details;
}
function _verifyPayload(payload, schemaPath) {
var schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
var Validator = require('jsonschema').Validator;
var verifier = new Validator();
console.log(verifier.validate(payload, schema))
return verifier.validate(payload, schema);
}
function _setResponse(status_code, error_list) {
return {
status: status_code,
errors: error_list
};
}
function _sendSESEmail(email, emailHTML) {
var nodemailer = require('nodemailer');
var sesTransport = require('nodemailer-ses-transport');
var transporter = nodemailer.createTransport(sesTransport({
accessKeyId: config.SES.accessKeyId,
secretAccessKey: config.SES.secretAccessKey
}));
transporter.sendMail({
from: email.from,
to: email.to,
cc: email.cc,
bcc: email.bcc,
attachments: email.attachments,
subject: email.subject,
html: emailHTML
});
}
function _getHTML(templateFile, params) {
var ejs = require('ejs');
console.log({ params: params })
var baseHTML = fs.readFileSync(templateFile, 'ascii');
return ejs.render(baseHTML, { params: params });
}
}
Above code works fine when tested in the dev environment with the below code, but does not fire a mail when tested on aws-lamda.
"use strict";
var exports = require('./exports');
var bankDetailsSchemaSample = {
"payload": {
"emailDetails": {
"from": 'some#something.com',
"to": ['kunal#something.com'],
"subject": 'My Amazon SES Simple Email',
"html": '',
"cc": ['nimesh.verma#something.com'],
"bcc": ['lokesh.gour#something.com'],
"emailType": 'bankDetails',
"attachments": [{
"filename": 'test.md',
"path": 'https://raw.github.com/nodemailer/nodemailer/master/LICENSE'
}]
},
"params": {
"orderId": 1234567,
"firstName": "Nimesh",
}
}
}
var context = {
fail: function(x) { console.log(" Fail " + x) },
succeed: function(x) { console.log(" Success " + x) }
}
exports.sendEmail(bankDetailsSchemaSample, context, {})
I can't find out, why this is happening, I also tried it using nodemailer-smtp-transport instead of nodemailer-ses-transport but the same results were obtained. When nothing helped I tried using aws-sdk instead of nodemailer and nodemailer-ses-transport and the mail is fired in both dev environment as well via aws lamda testing.
// load aws sdk
exports.sendEmail = function(event, context, callback) {
var aws = require('aws-sdk');
// load aws config
aws.config.loadFromPath('config.json');
// load AWS SES
var ses = new aws.SES({ apiVersion: '2010-12-01' });
// send to list
var to = ['nimesh.verma#something.com']
// this must relate to a verified SES account
var from = 'some#something.com'
// this sends the email
// #todo - add HTML version
ses.sendEmail({
Source: from,
Destination: { ToAddresses: to },
Message: {
Subject: {
Data: 'A Message To You Rudy'
},
Body: {
Text: {
Data: 'Stop your messing around',
}
}
}
}, function(err, data) {
if (err) throw err
console.log('Email sent:');
});
}
Why is this happening?
The problem was that the context.succeed method should be placed in the callback of _sendSESEmail method.
The complete working code is present at: https://github.com/nimeshkverma/aws-lambda-node-mailer

node AWS Lambda + twitter API: data is not defined

I'm having fun with the Alexa API, so I downloaded a Hello World example from here
https://developer.amazon.com/appsandservices/solutions/alexa/alexa-skills-kit/getting-started-guide
I managed to made some minor changes and have Alexa say other things.
But now I want to have a real world example working, so I tried to get the latest tweet for user.
so I coded a twitter function and it works, I see the tweet on my console.
Besides, the downloaded example works just fine too.
But now, when I try to combine them by adding my twitter function into the Alexa example, it throws the following error when trying to print the value (if I don't print it, it doesn't break):
{"errorMessage": "Exception: ReferenceError: data is not defined"}
here is the code but the modified function is getWelcomeResponse()
// Route the incoming request based on type (LaunchRequest, IntentRequest,
// etc.) The JSON body of the request is provided in the event parameter.
exports.handler = function (event, context) {
try {
console.log("event.session.application.applicationId=" + event.session.application.applicationId);
/**
* Uncomment this if statement and populate with your skill's application ID to
* prevent someone else from configuring a skill that sends requests to this function.
*/
/*
if (event.session.application.applicationId !== "amzn1.echo-sdk-ams.app.[unique-value-here]") {
context.fail("Invalid Application ID");
}
*/
if (event.session.new) {
onSessionStarted({requestId: event.request.requestId}, event.session);
}
if (event.request.type === "LaunchRequest") {
onLaunch(event.request,
event.session,
function callback(sessionAttributes, speechletResponse) {
context.succeed(buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === "IntentRequest") {
onIntent(event.request,
event.session,
function callback(sessionAttributes, speechletResponse) {
context.succeed(buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === "SessionEndedRequest") {
onSessionEnded(event.request, event.session);
context.succeed();
}
} catch (e) {
context.fail("Exception: " + e);
}
};
/**
* Called when the session starts.
*/
function onSessionStarted(sessionStartedRequest, session) {
console.log("onSessionStarted requestId=" + sessionStartedRequest.requestId
+ ", sessionId=" + session.sessionId);
}
/**
* Called when the user launches the skill without specifying what they want.
*/
function onLaunch(launchRequest, session, callback) {
console.log("onLaunch requestId=" + launchRequest.requestId
+ ", sessionId=" + session.sessionId);
// Dispatch to your skill's launch.
getWelcomeResponse(callback);
}
/**
* Called when the user specifies an intent for this skill.
*/
function onIntent(intentRequest, session, callback) {
console.log("onIntent requestId=" + intentRequest.requestId
+ ", sessionId=" + session.sessionId);
var intent = intentRequest.intent,
intentName = intentRequest.intent.name;
// Dispatch to your skill's intent handlers
if ("MyColorIsIntent" === intentName) {
setColorInSession(intent, session, callback);
} else if ("WhatsMyColorIntent" === intentName) {
getColorFromSession(intent, session, callback);
} else if ("HelpIntent" === intentName) {
getWelcomeResponse(callback);
} else {
throw "Invalid intent";
}
}
/**
* Called when the user ends the session.
* Is not called when the skill returns shouldEndSession=true.
*/
function onSessionEnded(sessionEndedRequest, session) {
console.log("onSessionEnded requestId=" + sessionEndedRequest.requestId
+ ", sessionId=" + session.sessionId);
// Add cleanup logic here
}
// --------------- Functions that control the skill's behavior -----------------------
function getWelcomeResponse(callback) {
var twit = require('twitter'),
twitter = new twit({
consumer_key:'***',
consumer_secret:'***',
access_token_key:'***',
access_token_secret:'***'
});
//var count = 0;
var util = require('util');
params = {
screen_name: 'kilinkis', // the user id passed in as part of the route
count: 1 // how many tweets to return
};
// request data
twitter.get('https://api.twitter.com/1.1/statuses/user_timeline.json', params, function (data) {
console.log(util.inspect(data[0].text));
});
// If we wanted to initialize the session to have some attributes we could add those here.
var sessionAttributes = {};
var cardTitle = "Welcome";
/*var speechOutput = "Welcome to the Alexa Skills Kit sample, "
+ "Please tell me your favorite color by saying, "
+ "my favorite color is red";*/
//var speechOutput=util.inspect(data[0].text);
var speechOutput=data[0].text;
// If the user either does not reply to the welcome message or says something that is not
// understood, they will be prompted again with this text.
var repromptText = "Please tell me your favorite color by saying, "
+ "my favorite color is red";
var shouldEndSession = true;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
/**
* Sets the color in the session and prepares the speech to reply to the user.
*/
function setColorInSession(intent, session, callback) {
var cardTitle = intent.name;
var favoriteColorSlot = intent.slots.Color;
var repromptText = "";
var sessionAttributes = {};
var shouldEndSession = false;
var speechOutput = "";
if (favoriteColorSlot) {
favoriteColor = favoriteColorSlot.value;
sessionAttributes = createFavoriteColorAttributes(favoriteColor);
speechOutput = "I now know your favorite color is " + favoriteColor + ". You can ask me "
+ "your favorite color by saying, what's my favorite color?";
repromptText = "You can ask me your favorite color by saying, what's my favorite color?";
} else {
speechOutput = "I'm not sure what your favorite color is, please try again";
repromptText = "I'm not sure what your favorite color is, you can tell me your "
+ "favorite color by saying, my favorite color is red";
}
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
function createFavoriteColorAttributes(favoriteColor) {
return {
favoriteColor: favoriteColor
};
}
function getColorFromSession(intent, session, callback) {
var cardTitle = intent.name;
var favoriteColor;
var repromptText = null;
var sessionAttributes = {};
var shouldEndSession = false;
var speechOutput = "";
if(session.attributes) {
favoriteColor = session.attributes.favoriteColor;
}
if(favoriteColor) {
speechOutput = "Your favorite color is " + favoriteColor + ", goodbye";
shouldEndSession = true;
}
else {
speechOutput = "I'm not sure what your favorite color is, you can say, my favorite color "
+ " is red";
}
// Setting repromptText to null signifies that we do not want to reprompt the user.
// If the user does not respond or says something that is not understood, the session
// will end.
callback(sessionAttributes,
buildSpeechletResponse(intent.name, speechOutput, repromptText, shouldEndSession));
}
// --------------- Helpers that build all of the responses -----------------------
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: "PlainText",
text: output
},
card: {
type: "Simple",
title: "SessionSpeechlet - " + title,
content: "SessionSpeechlet - " + output
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession: shouldEndSession
}
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: "1.0",
sessionAttributes: sessionAttributes,
response: speechletResponse
}
}
Can some one please guide me on what's wrong? it's probably a scope issue, I'm not sure.
Move your call back inside the twitter get function. Then your callback will be called on a successful get from the twitter api. Also you will have access to the data object. You will probably want to add a failure case as well and include a context.fail().
If you need to, you can also update the timeout parameter under the configuration tab of the AWS console. Its under advanced settings. Also, Its often useful to take Alexa out of the equation when debugging and just get the twitter api piece working first.
// request data
twitter.get('https://api.twitter.com/1.1/statuses/user_timeline.json', params, function (data) {
console.log(util.inspect(data[0].text));
// If we wanted to initialize the session to have some attributes we could add those here.
var sessionAttributes = {};
var cardTitle = "Welcome";
var speechOutput=data[0].text;
var repromptText = "";
var shouldEndSession = true;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
});

Resources