I am trying to develop a friend request system for my website using cloud functions to send, receive, and accept the requests. Yet I am not able to call them. I scoured through all the docs and modified my code to meet the right protocol but still no cigar. Pretty lost at this point, any help would really be appreciated.
Heres my cloud function
exports.sendFriendRequest = functions.https.onCall((data, context) => {
var jsonData = JSON.parse(data);
var requestedUserProfileRef = firebase.database().ref("Users/" + jsonData["recievingUser"]);
requestedUserProfileRef.child("FriendRequests").push();
var pushKey = requestedUserProfileRef.key;
requestedUserProfileRef.set(jsonData["sendingUser"]);
console.log(requestedUserProfileRef.Name);
});
Heres how I am calling it, or trying to at least
function sendFriendRequest(userUid)
{
//userUid is user that will recieve request
var curUser = firebase.auth().currentUser;
userUid = userUid.substring(1);
var sendRequest = firebase.functions().httpsCallable('sendFriendRequest');
sendRequest({"data": {"sendingUser": curUser.uid, "recievingUser": userUid}}).then(function(result) {});
}
Could it have something to do with not having a result returned?
Finally, here is the error that I am getting when I try and call the function
POST https://us-central1-accounts-cfe00.cloudfunctions.net/sendFriendRequest
Uncaught (in promise) Error: INTERNAL
at new t (firebase.js:1)
at _errorForResponse (firebase.js:1)
at e.<anonymous> (firebase.js:1)
at firebase.js:1
at Object.next (firebase.js:1)
at a (firebase.js:1)
Yes it's happening because you returned nothing. Return the promise like this:
exports.sendFriendRequest = functions.https.onCall((data, context) => {
var jsonData = JSON.parse(data);
var requestedUserProfileRef = firebase.database().ref("Users/" +
jsonData["recievingUser"]);
var promise = requestedUserProfileRef.child("FriendRequests").push();
var pushKey = requestedUserProfileRef.key;
return requestedUserProfileRef.set(jsonData["sendingUser"])
.then(function(result) {
console.log(requestedUserProfileRef.Name);
};
});
Related
I am attempting to send a json file created from fields in a webpage to a node function in AWS Lambda to add it to a DynamoDB table. I have the JSON made but I don't know how to pass it from the js used for the page to the lambda function. As this is for a class project, my group and I have decided to forego amazon's gateway API, and are just raw calling lambda functions using amazon's js sdk. I've checked Amazon's documentation and other various examples, but I haven't been able to find a complete solution.
Node function in lambda
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
exports.handler = async (event) => {
const params = {
TableName : 'characterTable',
Item: {
name : 'defaultName'
}
};
const userID = 'placeholder';
params.Item.userID = userID;
return await db.put(params).promise();
};
//}
Webpage js:
var lambda = new AWS.Lambda();
function makeJSON(){
var userID = "";
var name = document.forms["characterForm"]["characterName"].value;
var race = document.forms["characterForm"]["race"].value;
var playerClass = document.forms["characterForm"]["class"].value;
var strength = document.forms["characterForm"]["strength"].value;
var dexterity = document.forms["characterForm"]["dexterity"].value;
var constitution = document.forms["characterForm"]["constitution"].value;
var intelligence = document.forms["characterForm"]["intelligence"].value;
var wisdom = document.forms["characterForm"]["wisdom"].value;
var charisma = document.forms["characterForm"]["charisma"].value;
characterSheetObj = {userID: userID, name: name, race: race, class: playerClass, strength: strength, dexterity: dexterity, constitution: constitution, intelligence: intelligence, wisdom: wisdom, charisma: charisma}
characterSheetJSON = JSON.stringify(characterSheetObj);
alert(characterSheetJSON);
var myParams = {
FunctionName : 'addCharacterSheet',
InvocationType : 'RequestResponse',
LogType : 'None',
Payload : characterSheetJSON
}
lambda.invoke(myParams, function(err, data){
//if it errors, prompts an error message
if (err) {
prompt(err);
}
//otherwise puts up a message that it didnt error. the lambda function presently doesnt do anything
//in the future the lambda function should produce a json file for the JavaScript here to do something with
else {
alert("Did not error");
}
});
}
The html page for the raw javascript includes the proper setup for importing the sdk and configuring the region/user pool
I just don't know how to get the payload from the invocation in my node function, as this is my first time working with lambda and amazon's sdk, or doing any web development work at all, to be honest.
i would do it with async await. It's better to read.
lambda.invoke = util.promisify(lambda.invoke);
const result = await lambda.invoke(yourParams);
const payload = JSON.parse(result.Payload);
I have a Dialogflow Webhook fulfillment integration, which does a GET request to an API i have set up. But i can't seem to get the API's response as text in my conversation.
The API does receive a http GET request and returns a response with statuscode 200.
If i do the same request in my browser this is the result:
{
"avmid": "1011GZ 18",
"straat": "Snoekjesgracht",
"postcode": "1011GZ",
"stad": "AMSTERDAM",
"provincienaam": "Noord-Holland",
"date": "2013-12-30",
"koopsom": 199800,
"koopsom2018q2": 333849
}
I have tried several things but i don't seem to be able to get it to work.
This is my JavaScript:
'use strict';
const http = require('http');
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
function address_api_request (agent) {
let postcode = agent.parameters.zipcode;
let housenumber = agent.parameters.housenumber;
let avmid = postcode.toString()+'+'+housenumber.toString();
let url = 'http://XXX.XXX.XXX.XXX:XX/api/1011GZ+18';
var http = require('http');
http.get(url, function(response) {
var body = '';
response.on('data', function(d) {
body += d;
});
response.on('end', function() {
var parsed = JSON.parse(body);
agent.add(parsed.toString());
});
});
}
function welcome (agent) {
agent.add(`Welcome to my agent!`);
agent.add(agent.request_.body.queryResult.fulfillmentText);
}
function fallback (agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('company.woningwaarde-get-address-api', address_api_request);
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
agent.handleRequest(intentMap);
});
The intents work, and if i replace the address_api_request function with the underneath code it returns "test:
function address_api_request (agent) {
agent.add('test');
}
There are two issues here.
The first is that if you are using Dialogflow's built-in editor, it is using Firebase Cloud Functions under the covers. The default level of Firebase Cloud Functions does not allow network access outside of Google's cloud.
You can resolve this by upgrading the project to Firebase's Blaze Plan, which does require a credit card on file and charges per-use, however there is a free tier which is more than sufficient for reasonable testing, and even some light use under production. Once your action has been approved, you'll be eligible to receive Google Cloud credits which may be used for this purpose.
The other problem is that you have an asynchronous function (the http.get()), but you aren't using a Promise to handle the function and let the handleRequest() method know that it needs to wait for the function to resolve before returning a result. If you are using async functions, the Dialogflow library requires that you return a Promise from the function.
You have a few choices for how to handle this. First, you can wrap your call to http.get() as part of creating a new Promise object and in your end handler, send the message as you've indicated and then call the resolve parameter that you need to accept in the Promise handler. Easier, however, would be to use a library such as request-promise-native which wraps much of this for you and lets you get a result as part of a then() clause, where you would then handle it.
I haven't tested it, but your code might then look something like this:
function address_api_request (agent) {
let postcode = agent.parameters.zipcode;
let housenumber = agent.parameters.housenumber;
let avmid = postcode.toString()+'+'+housenumber.toString();
let url = 'http://XXX.XXX.XXX.XXX:XX/api/1011GZ+18';
const rp = require('request-promise-native');
var options = {
uri: url,
json: true
};
return rp( options )
.then( body => {
// Since json was set true above, it parses it for you
// You wouldn't really want to send back the whole body
agent.add( body );
})
.catch( err => {
console.log('Problem with request', err );
agent.add( "Uh oh, something went wrong" );
});
}
So I had been stuck on this exercise in Treehouse some time ago and just moved on. I came back to it now that I understand things better and I'm still fighting with the wunderground api. I've read through the json data and documentation, updated a few things from when the class was first recorded (and the API updated since then), and still am getting errors I can't field. I've got three js files- app.js, weather.js, and api.json (which is just my api key so not shared here.)
After my corrections, I'm still getting the error "TypeError: Cannot read property 'temp_f' of undefined" which doesn't make sense as I keep reading over the JSON to check that it's pointing to the right place.
Can anyone put an end to my misery trying to fix this?
App.js:
const weather = require('./weather');
//Join multiple values passed as arguments and replace all spaces with underscores
const query = process.argv.slice(2).join("_").replace(' ', '_');
//query: 90201
//query: Cleveland_OH
//query: London_England
weather.get(query);
Weather.js
const https = require('https');
const http = require('http');
const api = require('./api.json');
// Print out temp details
function printWeather(weather) {
const message = `Current temp in ${weather.location} is ${weather.current_observation.temp_f}F`;
console.log(message);
}
// Print out error message
function get(query) {
const request = http.get(`http://api.wunderground.com/api/${api.key}/conditions/q/${query}.json`, response => {
let body = "";
// Read the data
response.on('data', chunk => {
body += chunk;
});
response.on('end', () => {
//Parse data
const weather = JSON.parse(body);
//Print the data
printWeather(weather);
});
});
}
module.exports.get = get;
//TODO: Handle any errors
Please see my code below:
I am trying to assign the recordset to a variable, can use index.js to call this variable out.
I am able to console.log the recordset. But when I call this IIFE, it is always says undefined.
var mssql = require('mssql');
var dbcon = require('./dbcon');
var storage = (function () {
var connection = new mssql.Connection(dbcon);
var request = new mssql.Request(connection);
connection.connect(function (recordset) {
request.query('select getdate()', function (err, recordset) {
console.dir(recordset);
});
connection.close();
});
})();
module.exports = storage;
index.js
var storage = require('./storage');
"AMAZON.HelpIntent": function (intent, session, response) {
storage(function (recordset){
var speechOutput = 'Your result is '+recordset;
response.ask(speechOutput);
});
However, I can't get the recordset. I got "Your result is {object, object}. "
that's because the IIFE is executing right away, try returning a function instead and then executing that function when you import that module,
var storage = (function(mssql, dbcon) {
return function() {
var connection = new mssql.Connection(dbcon);
var request = new mssql.Request(connection);
connection.connect(function(recordset) {
request.query('select getdate()', function(err, recordset) {
console.dir(recordset);
});
connection.close();
});
}
})(mssql, dbcon);
and I don't understand why you need the IIFE, why don't you just assign the function to the variable?
If you're trying to assign the variable "recordset" to "storage" then this will never work as "connection.connect" is an asynchronous function, and in that case you should think about callback functions or promises.
Update
Based on your request, here's an implementation with a callback function and how it's used
var mssql = require('mssql');
var dbcon = require('./dbcon');
var storage = function(callback) {
var connection = new mssql.Connection(dbcon);
var request = new mssql.Request(connection);
connection.connect(function(recordset) {
request.query('select getdate()', function(err, recordset) {
if(!err && callback){
callback(recordset);
}
connection.close();
});
});
}
module.exports = storage;
// --------------------------------------------------
// implementation in another module
var storage = require("module_path"); // (1)
var answer;
storage(function(recordset){ // (2)
answer = recordset;
console.log(answer); // actual data, (3)
// implement your logic here
});
console.log(answer); // undefined (4)
// --------------------------------------------------
How this code works:
- You start by calling the storage method and sending it a callback method.
- The whole point of the callback function is that you won't wait for the result, your code will actually continue working at the same time that the storage method is connecting to the database and trying to get the data, ans since db operations are much slower, line(4) will execute before line(3).
- The flow of work will be as follows:
line (1)
line (2)
line (4)
line (3) at sometime in the future when the data is retrieved from database
- To see this more clearly, try doing this at the last line,
setTimeout(function(){console.log(answer);}, 3000);
This will wait for sometime until the data comes back;
I have the following code in server/statusboard.js;
var require = __meteor_bootstrap__.require,
request = require("request")
function getServices(services) {
services = [];
request('http://some-server/vshell/index.php?type=services&mode=json', function (error, response, body) {
var resJSON = JSON.parse(body);
_.each(resJSON, function(data) {
var host = data["host_name"];
var service = data["service_description"];
var hardState = data["last_hard_state"];
var currState = data["current_state"];
services+={host: host, service: service, hardState: hardState, currState: currState};
Services.insert({host: host, service: service, hardState: hardState, currState: currState});
});
});
}
Meteor.startup(function () {
var services = [];
getServices(services);
console.log(services);
});
Basically, it's pulling some data from a JSON feed and trying to push it into a collection.
When I start up Meteor I get the following exception;
app/packages/livedata/livedata_server.js:781
throw exception;
^
Error: Meteor code must always run within a Fiber
at [object Object].withValue (app/packages/meteor/dynamics_nodejs.js:22:15)
at [object Object].apply (app/packages/livedata/livedata_server.js:767:45)
at [object Object].insert (app/packages/mongo-livedata/collection.js:199:21)
at app/server/statusboard.js:15:16
at Array.forEach (native)
at Function.<anonymous> (app/packages/underscore/underscore.js:76:11)
at Request._callback (app/server/statusboard.js:9:7)
at Request.callback (/usr/local/meteor/lib/node_modules/request/main.js:108:22)
at Request.<anonymous> (/usr/local/meteor/lib/node_modules/request/main.js:468:18)
at Request.emit (events.js:67:17)
Exited with code: 1
I'm not too sure what that error means. Does anyone have any ideas, or can suggest a different approach?
Just wrapping your function in a Fiber might not be enough and can lead to unexpected behavior.
The reason is, along with Fiber, Meteor requires a set of variables attached to a fiber. Meteor uses data attached to a fiber as a dynamic scope and the easiest way to use it with 3rd party api is to use Meteor.bindEnvironment.
T.post('someurl', Meteor.bindEnvironment(function (err, res) {
// do stuff
// can access Meteor.userId
// still have MongoDB write fence
}, function () { console.log('Failed to bind environment'); }));
Watch these videos on evented mind if you want to know more:
https://www.eventedmind.com/posts/meteor-dynamic-scoping-with-environment-variables
https://www.eventedmind.com/posts/meteor-what-is-meteor-bindenvironment
As mentioned above it is because your executing code within a callback.
Any code you're running on the server-side needs to be contained within a Fiber.
Try changing your getServices function to look like this:
function getServices(services) {
Fiber(function() {
services = [];
request('http://some-server/vshell/index.php?type=services&mode=json', function (error, response, body) {
var resJSON = JSON.parse(body);
_.each(resJSON, function(data) {
var host = data["host_name"];
var service = data["service_description"];
var hardState = data["last_hard_state"];
var currState = data["current_state"];
services+={host: host, service: service, hardState: hardState, currState: currState};
Services.insert({host: host, service: service, hardState: hardState, currState: currState});
});
});
}).run();
}
I just ran into a similar problem and this worked for me. What I have to say though is that I am very new to this and I do not know if this is how this should be done.
You probably could get away with only wrapping your insert statement in the Fiber, but I am not positive.
Based on my tests you have to wrap the insert in code I tested that is similar to the above example.
For example, I did this and it still failed with Fibers error.
function insertPost(args) {
if(args) {
Fiber(function() {
post_text = args.text.slice(0,140);
T.post('statuses/update', { status: post_text },
function(err, reply) {
if(reply){
// TODO remove console output
console.log('reply: ' + JSON.stringify(reply,0,4));
console.log('incoming twitter string: ' + reply.id_str);
// TODO insert record
var ts = Date.now();
id = Posts.insert({
post: post_text,
twitter_id_str: reply.id_str,
created: ts
});
}else {
console.log('error: ' + JSON.stringify(err,0,4));
// TODO maybe store locally even though it failed on twitter
// and run service in background to push them later?
}
}
);
}).run();
}
}
I did this and it ran fine with no errors.
function insertPost(args) {
if(args) {
post_text = args.text.slice(0,140);
T.post('statuses/update', { status: post_text },
function(err, reply) {
if(reply){
// TODO remove console output
console.log('reply: ' + JSON.stringify(reply,0,4));
console.log('incoming twitter string: ' + reply.id_str);
// TODO insert record
var ts = Date.now();
Fiber(function() {
id = Posts.insert({
post: post_text,
twitter_id_str: reply.id_str,
created: ts
});
}).run();
}else {
console.log('error: ' + JSON.stringify(err,0,4));
// TODO maybe store locally even though it failed on twitter
// and run service in background to push them later?
}
}
);
}
}
I thought this might help others encountering this issue. I have not yet tested calling the asynchy type of external service after internal code and wrapping that in a Fiber. That might be worth testing as well. In my case I needed to know the remote action happened before I do my local action.
Hope this contributes to this question thread.