How to run Alexa skill with the alexa-sdk on own server with Node.js without Lambda drop-in? - node.js

The Alexa skill docs will eventually allow you to send webhooks to https endpoints. However the SDK only documents lambda style alexa-sdk usage. How would one go about running Alexa applications on one's own server without anything abstracting Lambda? Is it possible to wrap the event and context objects?

You can already use your own endpoint. When you create a new skill, in the configuration tab, just choose HTTPS and provide your https endpoint. ASK will call your endpoint where you can run anything you want (tip, check ngrok.com to tunnel to your own dev machine). Regarding the event and context objects; your endpoint will receive the event object information. You don't need the context object for anything, that just lets you interact with Lambda-specific stuff (http://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html). Just make sure that you comply with the (undocumented) timeouts by ASK and you are good to go.

Here's a way to do this that requires only a small change to your Skill code:
In your main index.js entry point, instead of:
exports.handler = function (event, context) {
use something like:
exports.myAppName = function (funcEvent, res) {
Below that, add the following workaround:
var event = funcEvent.body
// since not using Lambda, create dummy context with fail and succeed functions
const context = {
fail: () => {
res.sendStatus(500);
},
succeed: data => {
res.send(data);
}
};
Install and use Google Cloud Functions Local Emulator on your laptop. When you start and deploy your function to the emulator, you will get back a Resource URL something like http://localhost:8010/my-project-id/us-central1/myAppName.
Create a tunnel with ngrok. Then take the ngrok endpoint and put it in place of localhost:8010 in the Resource URL above. Your resulting fulfillment URL will be something like: https://b0xyz04e.ngrok.io/my-project-id/us-central1/myAppName
Use the fulfillment URL (like above) under Configuration in the Alexa dev console, selecting https as the Service Endpoint Type.

Related

how does users.watch (in gmail google api) listen for notifications?

I am confused as to how should the watch feature in the gmail API be implemented to recieve the push notificatons inside a node.js script. Should I call the method inside an infinite loop or something so that it doesn't stop listening for notifications for email once after the call is made?
Here's the sample code that I've written in node.js:
const getEmailNotification = () => {
return new Promise(async (resolve, reject) => {
try{
let auth = await authenticate();
const gmail = google.gmail({version: 'v1', auth});
await gmail.users.stop({
userId: '<email id>'
});
let watchResponse = await gmail.users.watch({
userId: '<email id>',
labelIds: ['INBOX'],
topicName: 'projects/<projectName>/topics/<topicName>'
})
return resolve(watchResponse);
} catch(err){
return reject(`Some error occurred`);
}
})
Thank you!
Summary
To receive push notifications through PUB/SUB you need to create a web-hook to receive them. What does this mean? You need a WEB application or any kind of service that exposes a URL where notifications can be received.
As stated in the Push subscription documentation:
The Pub/Sub server sends each message as an HTTPS request to the subscriber application at a pre-configured endpoint.
The endpoint acknowledges the message by returning an HTTP success status code. A non-success response indicates that the message should be resent.
Setup a channel for watch the notifications could be summarized in the following steps (the documentation you refer to indicates them):
Select/Create a project within the Google Cloud Console.
Create a new PUB/SUB topic
Create a subscription (PUSH) for that topic.
Add the necessary permissions, in this case add gmail-api-push#system.gserviceaccount.com as Pub/Sub Publisher.
Indicate what types of mail you want it to listen for via Users.watch() method (which is what you are doing in your script).
Example
I give you an example using Apps Script (it is an easy way to visualize it, but this could be achieved from any kind of WEB application, as you are using Node.js I suppose that you are familiarized with Express.js or related frameworks).
First I created a new Google Apps Script project, this will be my web-hook. Basically I want it to make a log of all HTTP/POST requests inside a Google Doc that I have previously created. For it I use the doPost() equal to app.post() in Express. If you want to know more about how Apps Script works, you can visit this link), but this is not the main topic.
Code.gs
const doPost = (e) => {
const doc = DocumentApp.openById(<DOC_ID>)
doc.getBody().appendParagraph(JSON.stringify(e, null, 2))
}
Later I made a new implementation as a Web App where I say that it is accessible by anyone, I write down the URL for later. This will be similar to deploying your Node.js application to the internet.
I select a project in the Cloud Console, as indicated in the Prerequisites of Cloud Pub/Sub.
Inside this project, I create a new topic that I call GmailAPIPush. After, click in Add Main (in the right bar of the Topics section ) and add gmail-api-push#system.gserviceaccount.com with the Pub/Sub Publisher role. This is a requirement that grants Gmail privileges to publish notification.
In the same project, I create a Subscription. I tell it to be of the Push type and add the URL of the Web App that I have previously created.
This is the most critical part and makes the difference of how you want your application to work. If you want to know which type of subscription best suits your needs (PUSH or PULL), you have a detailed documentation that will help you choose between these two types.
Finally we are left with the simplest part, configuring the Gmail account to send updates on the mailbox. I am going to do this from Apps Script, but it is exactly the same as with Node.
const watchUserGmail = () => {
const request = {
'labelIds': ['INBOX'],
'topicName': 'projects/my_project_name/topics/GmailAPIPush'
}
Gmail.Users.watch(request, 'me')
}
Once the function is executed, I send a test message, and voila, the notification appears in my document.
Returning to the case that you expose, I am going to try to explain it with a metaphor. Imagine you have a mailbox, and you are waiting for a very important letter. As you are nervous, you go every 5 minutes to check if the letter has arrived (similar to what you propose with setInterval), that makes that most of the times that you go to check your mailbox, there is nothing new. However, you train your dog to bark (push notification) every time the mailman comes, so you only go to check your mailbox when you know you have new letters.

Error "ReferenceError: request is not defined" from basic Firebase Functions run

Trying to send off a webhook to Slack whenever onWrite() is triggered directed toward my Firebase DB. Going off a few other posts/guides I was able to deploy the below code, but get the ReferenceError: Request is not defined error on execution. I can't figure out how to fix the Request is not defined.
const functions = require('firebase-functions');
const webhookURL = "https://hooks.slack.com/services/string/string";
exports.firstTest = functions.database.ref('first').onWrite( event => {
return request.post(
webhookURL,
{json: {text: "Hello"}}
);
});
Calling your Cloud Function via an URL and sending back a response
By doing exports.firstTest = functions.database.ref('first').onWrite() you trigger your firstTest Cloud Function when data is created, updated, or deleted in the Realtime Database. It is called a background trigger, see https://firebase.google.com/docs/functions/database-events?authuser=0
With this trigger, everything happens in the back-end and you do not have access to a Request (or a Response) object. The Cloud Function doesn't have any notion of a front-end: for example it can be triggered by another back-end process that writes to the database. If you want to detect, in your front-end, the result of the Cloud Function (for example the creation of a new node) you would have to set a listener to listen to this new node location.
If you want to call your function through an HTTP request (possibly from your front-end, or from another "API consumer") and receive a response to the HTTP Request, you need to use another type of Cloud Function, the HTTP Cloud Function, see https://firebase.google.com/docs/functions/http-events. See also the other type of Cloud Function that you can call directly: the Callable Cloud Functions.
Finally, note that:
With .onWrite( event => {}), you are using the old syntax, see https://firebase.google.com/docs/functions/beta-v1-diff?authuser=0
The Firebase video series on Cloud Function is a good point to start to learn more on all these concepts, see https://firebase.google.com/docs/functions/video-series?authuser=0
Calling, from your Cloud Function, an external URL
If you want, from a Cloud Function, to call an external URL (the Slack webhook mentioned in your question, for example) you need to use a library like request-promise (https://github.com/request/request-promise).
See How to fetch a URL with Google Cloud functions? request? or Google Cloud functions call URL hosted on Google App Engine for some examples
Important: Note that you need to be on the "Flame" or "Blaze" pricing plan.
As a matter of fact, the free "Spark" plan "allows outbound network requests only to Google-owned services". See https://firebase.google.com/pricing/ (hover your mouse on the question mark situated after the "Cloud Functions" title)

How to test http request on Dialogflow Fulfillment with the Inline Editor

I am testing Dialogflow Fulfillment with the Inline Editor.
What I am trying to do is a http request using que 'request' library.
Here is the code I am using:
const requesthttp = require('request');
requesthttp('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY', { json: true }, (err, res, body) => {
if (err) { return console.log(err); }
console.log(body.url);
console.log(body.explanation);
});
But It returns me an error of not found.
I also noticed an alert on my Dialogflow with the following message:
"Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions."
So... Probably I can't test this piece of code without configuring a billing account.
My question is... Is there a url that I can use to test this code?
Or the only way for me to test this code is configuring a billing account and paying for it?
Thanks in advance
There are a number of approaches to testing your code.
If you want to continue to use Dialogflow's Inline Editor, you will need to setup Firebase to use a payment plan. However, the Blaze plan is "pay as you go" after a basic level of use. This level of use should be sufficient to cover most testing (and even very light production) uses of the service without imposing a charge. Once your Action has been approved, you're able to receive credits for the Google Cloud Platform, which can be applied to this use in case you go over the minimum level.
You can also use Firebase Cloud Functions, which the Inline Editor is based on, and your own local editor. One advantage of this is that you can serve the function locally, which has many of the same features as deploying it, but doesn't have the URL restriction (it is your own machine, after all). You can use a tool such as ngrok to create a secure tunnel to your your machine during testing. Once you have tested, you can deploy this to Firebase with a paid plan.
You can, of course, choose to use any other hosting method you wish. Google and Dialogflow allow you to run your fulfillment webhook on any server, as long as that server can provide an HTTPS connection using a valid, non-self-signed, certificate. If you're using node.js, you can continue to use these libraries. If you wish to use another language, you will need to be able to parse and return JSON, but otherwise you have no restrictions.
There are a lot of ways to create your own server like using NodeJS client with Express.JS which you can expose to the internet using NGROK as webhook for fulfilment.
Develop a webhook. You can use different client libraries in NodeJS (AoG Client or Dialogflow Client) or in Python (Flask-Assistant or Dialogflow Client) or can create your own just using JSON request/response with Dialogflow and Action-on-Google.
Once the webhook is ready, run it locally and expose to the internet using NGROK.
Start with following code for Actions-on-Google with Express.JS
'use strict';
const {dialogflow} = require('actions-on-google');
const express = require('express');
const bodyParser = require('body-parser');
const app = dialogflow();
app.intent('Default Welcome Intent', conv => {
conv.ask('Hi, Welcome to Assistant by Express JS ');
});
express().use(bodyParser.json(), app).listen(8080);
Since DF uses firebase cloud functions you can use https as in nodejs. But requesting domains outside of the google/firebase universe will require the paid version of firebase.
const https = require('https');
return new Promise((resolve, reject) => {
const hostname = info.hostname;
const pathname = info.pathname;
let data = '';
const request = https.get(`https://${hostname}${pathname}`, (res) => {
res.on('data', (d) => {
data += d;
});
res.on('end', resolve);
});
request.on('error', reject);
});

Implementing isRequestFromAssistant in Node.js on actions-on-google project fulfillment

I am having trouble implementing the isRequestFromAssistant method to verify requests to my fulfillment webhook. Using Node.js, I instantiate the following variables at the start of my index.js file:
const App = require('actions-on-google').ApiAiApp;
const app = new App({ request, response });
I then use "app" with the .ask and .tell and other methods throughout my functions.
The code I see in the docs for implementing isRequestFromAssistant is:
const app = new ActionsSdkApp({request, response});
app.isRequestFromAssistant('my-project-id')
.then(() => {
app.ask('Hey there, thanks for stopping by!');
})
.catch(err => {
response.status(400).send();
});
If I leave out the first line and use my existing app variable, created with the .ApiAi method instead of the .ActionsSdkApp method, it doesn't work. If I create a new variable App1 and app1 using the .ActionsSdkApp method and change the above code to be app1.isRequestFromAssistant, it also doesn't work. I have tried other variations with no luck.
When I say it doesn't work, I mean I receive a 500 Internal Server Error when I call it. I am hosting it with NGROK currently. I am still a beginner with Node.js, although I have managed to get the other 700 lines of code working just fine, learning mostly from Google searches and reading these forums.
You have a few things going on here which, individually or separately, may be causing the problem.
First - make sure you have the most recent version of the actions-on-google library. The isRequestFromAssistant() function was added in version 1.6.0, I believe.
Second - Make sure you're creating the right kind of App instance. If you're using Dialogflow (formerly API.AI), you should be creating it with something like
const App = require('actions-on-google').DialogflowApp;
const app = new App( {request, response} );
or
const { DialogflowApp } = require('actions-on-google');
const app = new DialogflowApp( {request, response} );
(They both do the same thing, but you'll see both forms in documentation.) You should switch to DialogflowApp from ApiAiApp (which your example uses) to reflect the new name, but the old form has been retained.
If you're using the Actions SDK directly (not using Dialogflow / API.AI), then you should be using the ActionsSdkApp object, something like
const { ActionsSdkApp } = require('actions-on-google');
const app = new ActionsSdkApp({request: request, response: response});
(Again, you'll see variants on this, but they're all fundamentally the same.)
Third - Make sure you're using the right function that matches the object you're using. The isRequestFromAssistant() function is only if you are using the Actions SDK.
If you are using Dialogflow, the corresponding function is isRequestFromDialogflow(). The parameters are different, however, since it requires you to set confirmation information as part of your Dialogflow configuration.
Finally - If you're getting a 500 error, then check your logs (or the output from stderr) for the node.js server that is running. Typically there will be an error message there that points you in the right direction. If not - posting that error message as part of your StackOverflow question is always helpful.
Set the secure (randomly generated) auth header & key values in the dialogflow Fulfillment page, then in nodejs:
if (app.isRequestFromDialogflow("replace_with_key", "replace_with_value")) {
console.log("Request came from dialogflow!");
// rest of bot
} else {
console.log("Request did not come from dialogflow!");
response.status(400).send();
}
Also see: https://developers.google.com/actions/reference/nodejs/DialogflowApp#isRequestFromDialogflow

Listen to POST requests, parse-server, node.js

I have a self-hosted parse-server in AWS EC2. I want to update my database when I receive POSTnotifications from Apple. For that, I created a cloud function, but since Apple asks for a urlto send notifications, I'm not sure how to make my cloud function directly accessible via url or if I need to create an endpoint somewhere (AWS) to receive the notification from Apple and then make a new httpRequest or curl to my cloud function.
I'm looking for any directions or services (AWS) on how to perform this.
I don't think you need to do anything in AWS. You just need to add the cloudFunction to your main.js
As an example, here is an endPoint called add3NumbersTogether . I put this in my main.js file and then I can call this code from iOs (or another client). In iOs I use the Parse iOs SDK to make calls
Parse.Cloud.define("add3NumbersTogether", function(request, response) {
response.success( {request.params.num1 + request.params.num2 + request.params.num3});
});

Resources