I've got a Google Dialogflow application I'm putting together that has a number of fulillments I need to process with external REST apis. I've got the client set up and working with firebase serve, enabling me to test locally. My index.js functions have the following siganture:
exports.clientEmployeeServiceCodes = functions.https.onRequest(async (req, res)=>{...});
But when I run locally any request parameter I pass through the url comes up as undefined. Here is an example.
http://localhost:5000/arc-caregiver-f8ec9/us-central1/clientEmployeeServiceCodes?phone=%22%%2b12123003939%22
But I get params when I call through Dialogflow. So my question is whether I'm doing someting wrong or if there's a way to emulate those params in my url call so the method behaves just as it would if I called it from Dialogflow.
Here is the example where I am getting the Dialogflow parameters via the req object. Note where I get the value of pin.
exports.userHours = functions.https.onRequest(async (req, res)=>{
const start = MyUtils.getDatePlus(-14);
const end = MyUtils.getDatePlus(14);
var view = 'API_Pay_Periods';
var form = 'Pay_Periods';
var criteria = 'Pay_Period_Date >= "'+start+'" %26%26 Pay_Period_Date <=
"'+end+'"'
const ret = await callRestAPI(Config,view,form,criteria);
const pp = getPayPeriod(ret);
const pin = req.query.pin
console.log('pin',pin)
const data = await RESTAPI(Config, view,form,criteria);
res.status(200).send(response);
....
res.end();
});
Dialogflow sends its fulfillment information via a POST to your webhook with the JSON object in the body of the request. You cannot duplicate this by sending query parameters in the URL itself.
One typical way to work with this is to setup an ngrok tunnel to your local environment and use it to record the JSON body that is sent. You can then use this same body for testing later.
Related
I'm trying to connect my app to firebase but the only response I get is not the response json I need from firebase. I included my call to firebase below. Is the url not correct? The response I'm getting back is not the json object made with firebase that I created.
``
<script>
(async function call () {
console.log("hello")
const endpoint = url
console.log(endpoint)
async function initiation () {
const result = await fetch(endpoint, {mode: "no-cors"})
const data = await result
console.log(data)
}
initiation()
})()
</script>
``
is your database in us-central1?
according to documentation [1] "the form https://<"databaseName">.firebaseio.com (for us-central1 databases) or https://<"databaseName"><"region">.firebasedatabase.app (for databases in all other locations)."
If its in another region you should try with https://<"databaseName"><"region">.firebasedatabase.app
[1]https://firebase.google.com/docs/database/web/start#initialize_the_javascript_sdk
The structure within your code seems odd, if you are implementing the CDN you need to initiate your app with your project credentials, right now you are only accessing a real-time database as a public request and does not provide any additional validators as the database is most likely to have Security Rules enabled.
To request data from the endpoint, you need to also include a .json at the end of the URL https://[PROJECT_ID].firebaseio.com/users/jack/name.json
Source: https://firebase.google.com/docs/reference/rest/database#section-get
I am writing a Firebase Function, specifically for Dialogflow chatbot fulfillment. I am having trouble getting an accurate client IP address regardless of how I am testing it.
I've seen on various posts the various ways to read client IP, but they are either undefined or an internal Google IP from one of their data centers.
I've tried reading:
"x-forwarded-for" header
req.connection.remoteAddress
req.ip
req.ips (collection of all of them, there is only ever 1 in the collection)
Any help would be much appreciated. I am trying to log analaytics around user interactions, and right now the IPs are all incorrect.
I've tried the following code which is provided here:
const functions = require('firebase-functions');
const util = require('util');
exports.helloWorld = functions.https.onRequest((req, res) => {
// For Firebase Hosting URIs, use req.headers['fastly-client-ip']
// For callable functions, use rawRequest
// Some users have better success with req.headers['x-appengine-user-ip']
const ipAddress = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const headers = JSON.stringify(req.headers, null, 2);
const message = util.format("<pre>Hello world!\n\nYour IP address: %s\n\nRequest headers: %s</pre>", ipAddress, headers);
res.send(message);
});
When tested (even with mobile data), it returned the public IP of the caller and not a Google Internal IP.
If try this, do you continue getting internal IPs?
I have to create a webhook from typeform to firebase. I will create a cloud function listening to events sent from typeform. The typeform is managed by a third party.
The only issue I have, is the authorization part for the webhook. I understood (from reading different post) that anyone can "talk" to the cloud function URL. But I would like to have a secure and exclusive communication between typeform and firebase.
Any hints ?
Thank for your time.
You can definitively connect a Typeform webhook to a Cloud function and push data to Firebase storage.
In addition to authentication pointed by Frank, Typeform also provides a signature mechanism to ensure that the request comes from Typeform webhook.
Typeform lets you define a secret to sign the webhook payload.
When you receive the payload on your end, in the cloud function, you verify first if it's signed correctly, if it's not it means it's not coming from Typeform, therefore, you should not deal with it.
Here is an example to verify the webhook signature:
app.post('/typeform/webhook', async (request, response) => {
console.log('~> webhook received');
// security check, let's make sure request comes from typeform
const signature = request.headers['typeform-signature']
const isValid = verifySignature(signature, request.body.toString())
if (!isValid) {
throw new Error('Webhook signature is not valid, someone is faking this!');
}
//valid signature let's do something with data received
})
And here is the verifySignature function
const crypto = require('crypto')
const verifySignature = function(receivedSignature, payload){
const hash = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('base64')
return receivedSignature === `sha256=${hash}`
}
There are more details on Typeform documentation.
Hope it helps :)
Calling request.body.toString() does not work the way it is described in #Nicolas GreniƩs answer. The result will always be the string "[Object object]", as it only utilizes the default prototype as described here (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString).
A valid approach to stringify req.body would be to use JSON.stringify() which would still not deliver the expected result as you need to hash the original binary data (https://developer.typeform.com/webhooks/secure-your-webhooks/).
The Solution (without Firebase)
Use app.use(bodyParser.raw({ type: 'application/json' })) as specified here (Validate TypeForm Webhook payload in Node) to get the raw binary data and pass the request body directly into the hashing function.
const bodyParser = require("body-parser");
app.use(bodyParser.raw({ type: "application/json" })); // Notice .raw !
app.post("/typeform-handler", (req, res) => {
const hash = crypto
.createHmac('sha256', MY_TYPEFORM_SECRET)
.update(req.body) // Pass the raw body after getting it using bodyParser
.digest('base64')
})
The solution using Firebase
If you are using a Firebase Cloud Function to handle the request, you can't use bodyParser this way as Firebase already takes care of the parsing (https://firebase.google.com/docs/functions/http-events#read_values_from_the_request). Instead, use req.rawBody to access the raw body and pass it to the hash function.
// No need for bodyParser
app.post("/typeform-handler", (req, res) => {
const hash = crypto
.createHmac('sha256', MY_TYPEFORM_SECRET)
.update(req.rawBody) // Notice .rawBody instead of just .body
.digest('base64')
})
Remark for TypeScript users
The default Express Request object does not contain a rawBody property. Be aware that TypeScript therefore might throw an error of no overload matches this call or Property 'rawBody' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'. The actual Request object will however be provided by Firebase and will contain said properties. You can access the actual Request object type using functions.https.Request.
I have created Firebase Cloud Functions app,
I created function with https.onRequest.
and get data with req.body but there is not data there.
Can Firebase Cloud Functions can handle HTTP POST method?
This is my sample code:-
var functions = require('firebase-functions');
exports.testPost = functions.https.onRequest((req, res) => {
console.log(req.body);
});
I tested by postman with POST method but didn't show result in Firebase log.
Functions built on Firebase can also use Express.js routers for handling GET/POST/PUT/DELETE, etc... is fully supported by Google, and is the recommended way to implement these types of functions.
More documentation can be found here:
https://firebase.google.com/docs/functions/http-events
Here's a working example built on Node.js
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors');
const app = express();
// Automatically allow cross-origin requests
app.use(cors({ origin: true }));
app.get('/hello', (req, res) => {
res.end("Received GET request!");
});
app.post('/hello', (req, res) => {
res.end("Received POST request!");
});
// Expose Express API as a single Cloud Function:
exports.widgets = functions.https.onRequest(app);
Then, run firebase deploy, and that should compile your code and create the new "widgets" function. Note: You can rename widgets to anything you want. Ultimately, it will generate a URL for calling the function.
I am planning to do the same thing. What I reckon the approach should be is to check the request.method in the function body. A probable approach can be:
if (request.method != "POST") {
respond.status(400).send("I am not happy");
return;
}
// handle the post request
Here's some reference to the details regarding what the request object holds: https://firebase.google.com/docs/functions/http-events
Firebase functions support GET, POST, PUT, DELETE, and OPTIONS method, and you can check what kind of methods that trigger your function.
// Check for POST request
if(request.method !== "POST"){
res.status(400).send('Please send a POST request');
return;
}
Then to get data from POST request (for example JSON type) will be in the header of your request.
const postData = request.body;
// for instance
const format = req.body.format;
// query string params
let format = req.query.format;
Maybe your project hasn't been setup to communicate with your firebase database. Try the following from your terminal:
npm install -g firebase-tools
Then inside your project folder, run the following and login using your credentials
firebase login
Then
firebase init functions
This will create a folder with index.js, package.json and node_modules
If you are using Postman correctly the rest of your code should work.
I have registered my web address (let's just call it https://mywebaddress/callbacks) with this external API and it will now send me JSON when it completes an action. I don't need to initiate anything outbound to it, I just need to receive the JSON and store it.
EDIT:
JSON data will be receive via POST
Paul's link sent me in the right direction. (http://www.meteorpedia.com/read/REST_API).
Then I found the section titled "WebApp.connectHandlers and connect".
I used the code found there, but in my instance there was an error in the code. I had to change the first line from var connect = Npm.require('connect'); to var connect = Meteor.require('connect');
Here is the code below.
// necessary to parse POST data
var connect = Meteor.require('connect');
// necessary for Collection use and other wrapped methods
var Fiber = Npm.require('fibers');
WebApp.connectHandlers
.use(connect.urlencoded()) // these two replace
.use(connect.json()) // the old bodyParser
.use('/getUserProfile', function(req, res, next) {
// necessary for Collection use and other wrapped methods
Fiber(function() {
var userId = req.body.userId;
var user = Meteor.users.findOne(userId);
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(user.profile));
}).run();
});
}
Then to test that this was working I used http://www.hurl.it/. I changed the destination to POST and added a header of content-type - application/json. I then pasted in the body some JSON that I knew came from balanced. If you need a tool to see what is actually being posted to your server you can use http://requestb.in/.