I'm trying to write to my Firebase database using a Twilio function (almost exactly the same as AWS Lambda function, if you're familiar with that), but I'm unable to do so. Firebase is generating a reference key for me, but nothing appears in my Firebase database when I check it. I don't see any error message from my function. Here's what I'm working with.
var firebase = require('firebase');
exports.handler = function(context, event, callback) {
var firebaseConfigs = {
apiKey: "[my_api_key]",
authDomain: "[my_domain].firebaseapp.com",
databaseURL: "https://[my_domain].firebaseio.com",
projectId: "[my_project_id]",
storageBucket: "[my_domain].appspot.com",
messagingSenderId: "[my_sender_id]"
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfigs);
console.log('Initialized Firebase app');
}
console.log('saving to firebase');
var ref = firebase.database().ref().push({
emailAddress: event.emailAddress,
test: 'test'
});
console.log('saved to ',ref.key)
setTimeout(function() {}, 3000);
callback();
};
In my function logs, I see the following:
Execution Started
Initialized Firebase app
saving to firebase
saved to [-LdVpr...]
Execution ended in 974.16ms using 97 MB
Clearly Firebase is generating a key for me, but the key [-LdVpr...] is not added to my database. What could be happening, and how can I further troubleshoot this?
With Execution ended in 974.16ms in the logs, sounds like
setTimeout(function() {}, 3000);
callback();
does not really do what you intended to do (wait for 3 seconds and then return to Twilio?).
I would try this ...
setTimeout(function() {
callback();
}, 3000);
... if that's what you'd like or need to do.
Update after reading this https://firebase.google.com/docs/reference/js/firebase.database.Reference.html#push
push() takes a second parameter, a "Callback called when write to server is complete.", so, I would also try something like this:
var ref = firebase.database().ref().push({
emailAddress: event.emailAddress,
test: 'test'
}, function (error) {
if (error) {
console.log(error);
callback();
} else {
console.log('Push successful');
callback();
}
});
maybe you don't need that 3 seconds thing at all.
Related
I'm developing a NodeJS web app to receive Real Time updates from Firestore DB through Admin SDK.
This is the init code for the Firestore object. It's executed just once, when the app is deployed (on AWS Elastic Beanstalk):
const admin = require('firebase-admin');
var serviceAccount = require('./../key.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
var db = admin.firestore();
FUNC.firestore = db;
Then I use this firestore object in a websocket comunication to send realtime updates to browser. The idea is to use the server as a proxy between browser and Firestore.
socket.on('open', function (client) {
var query = FUNC.firestore.collection("notifications").doc(client.user.id.toString()).collection("global");
query.onSnapshot(querySnapshot => {
querySnapshot.docChanges().forEach(change => {
client.send({ id: change.doc.id, body: change.doc.data(), type: change.type });
});
}, err => {
console.log(`Encountered error: ${err}`);
});
});
socket.on('close', function (client) {
var unsub = FUNC.firestore.collection("notifications").doc(client.user.id.toString()).collection("global").onSnapshot(() => {
});
unsub();
});
It works well for a while, but after few hours the client stop receiving onSnapshot() updates, and after a while the server log the error: Encountered error: Error: 10 ABORTED: The operation was aborted.
What's wrong? Should I initialized firestore on each connection? Is there some lifecycle mistake?
Thank you
EDIT (A very bad solution)
I've tried to create a single firebase-admin app instance for each logged user and changed my code in this way
const admin = require('firebase-admin');
var serviceAccount = require('./../key.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
FUNC.getFirestore = function (user) {
try {
user.firebase = admin.app(user.id.toString());
return user.firebase.firestore();
} catch(e) {
//ignore
}
var app = admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
}, user.id.toString());
user.firebase = app;
return user.firebase.firestore();
}
FUNC.removeFirebase = function (user) {
if (user.firebase) {
user.firebase.delete();
}
}
And then socket listeners:
self.on('open', function (client) {
var query = FUNC.getFirestore(client.user).collection("notifications").doc(client.user.id.toString()).collection("global");
query.onSnapshot(querySnapshot => {
querySnapshot.docChanges().reverse();
querySnapshot.docChanges().forEach(change => {
client.send({ id: change.doc.id, body: change.doc.data(), type: change.type });
});
}, err => {
console.log(`Encountered error: ${err}`);
});
});
self.on('close', function (client) {
var unsub = FUNC.getFirestore(client.user).collection("notifications").doc(client.user.id.toString()).collection("global").onSnapshot(() => {
});
unsub();
FUNC.removeFirebase(client.user);
});
So when a client disconnect for a reason the server removes its firebase app, it works, but I've noticed a huge memory leak on server, I need some help
UPDATED ANSWER
After many reaserch I've understand that this kind of approach is wrong. Of course, the old answer could be a workaround but is not the real solution of the problem, because Firestore was not designed to do something like: Firestore <--(Admin SDK)--> Server <--(WebSocket)--> Client.
In order to create the best comunication I have understand and applied Firestore Security Rules (https://firebase.google.com/docs/firestore/security/get-started) together with Custom token generation (https://firebase.google.com/docs/auth/admin/create-custom-tokens). So the correct flow is:
Client login request --> Server + Admin SDK generate custom auth token and return to client
Then, the real time comunication will be only between Client and Firestore itself, so: Client + Custom Auth Token <--(Firebase JS SDK)--> Firestore DB
As you can see, the server is not involved anymore in real-time comunication, but client receive updates directly from Firestore.
OLD ANSWER
Finally I can answer from myself. First of all the second solution I've tried is a very bad one, because each new app created through Admin SDK is stored in RAM, with 20/30 users the app reaches more then 1GB of RAM, absolutely unacceptable.
So the first implementation was the better solution, anyway I've wrong the register/unregister onSnapshot listener lifecycle. Each onSnapshot() call returns a different function, even if called on the same reference. So, instead of close the listener when socket close, I opened another one. This is how should be:
socket.on('open', function (client) {
var query = FUNC.firestore.collection("notifications").doc(client.user.id.toString()).collection("global");
client.user.firestoreUnsub = query.onSnapshot(querySnapshot => {
querySnapshot.docChanges().forEach(change => {
client.send({ id: change.doc.id, body: change.doc.data(), type: change.type });
});
}, err => {
console.log(`Encountered error: ${err}`);
});
});
socket.on('close', function (client) {
client.user.firestoreUnsub();
});
After almost 48h, listeners still works without problems and no memory leaks occurs.
so on my first time learning AWS stuff (it is a beast), I'm trying to create e-mail templates, I have this lambda function:
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region
AWS.config.update({ region: "us-east-1" });
exports.handler = async (event, context, callback) => {
// Create createTemplate params
var params = {
Template: {
TemplateName: "notification" /* required */,
HtmlPart: "HTML_CONTENT",
SubjectPart: "SUBJECT_LINE",
TextPart: "sending emails with aws lambda"
}
};
// Create the promise and SES service object
const templatePromise = new AWS.SES({ apiVersion: "2010-12-01" })
.createTemplate(params)
.promise();
// Handle promise's fulfilled/rejected states
templatePromise
.then((data) => {
console.log(data);
callback(null, JSON.stringify(data) );
// also tried callback(null, data);
}, (err) => {
console.error(err, err.stack);
callback(JSON.stringify(err) );
});
as far as I am understanding, this function should return me a template? an object, anything? when I use the lambda test functionality I always got null in the request response
does anyone know what I am doing wrong here?
edit: and It is not creating the e-mail template, I check the SES Panel - email templates and it is empty
edit2: if I try to return a string eg: callback(null, "some success message"); it does return the string, so my guess is something wrong with the SES, but this function is exactly what we have in the AWS docs, so I assume it should just work..
Try not to resolve the Promise and change your code to just returning it as-is:
return await templatePromise;
which should present you some more detail of what is really going wrong in your code - it might be some hidden access issue - so you might need to adjust the role your lambda function is using. createTemplate on the other side should not return much in case of successful execution but just create the template.
Also try to follow the following try/catch pattern when using async (as described here in more detail: https://aws.amazon.com/de/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/)
exports.handler = async (event) => {
try {
data = await lambda.getAccountSettings().promise();
}
catch (err) {
console.log(err);
return err;
}
return data;
};
In the following event .once event is working fine
'use strict';
let firebase = require('firebase');
exports.handler = function(event, context)
{
context.callbackWaitsForEmptyEventLoop = false;
firebase.initializeApp({
serviceAccount: {},
databaseURL: "https://harmanconnectedcar-180411.firebaseio.com/"
});
firebase.database().ref('events').once('value').then(function(snapshot) {
console.log("*************event**********************")
console.log (snapshot.val()) ;
context.succeed() ;
});
var starCountRef = firebase.database().ref('events' );
starCountRef.on('value', function(snapshot) {
console.log("*************snapshot*****snapshot*****************")
console.log (snapshot.val()) ;
context.succeed();
})
}
When i try starCountRef.on i am not able to see the logs printed
Once i put the lambda function in AWS and write to firebase from firebase console i am not able to see the events where do i need to see the logs how to check starCountRef.on event(i mean the real time logs)
You're starting two asynchronous listeners. Then when the first of them finishes you call context.succeed(). At that point Lambda terminates your function, so the second listener never completes.
To makes this code work, you need to ensure you only call context.succeed() when all of the data has loaded. An easy way to do this is by using Promise.all():
exports.handler = function(event, context)
{
context.callbackWaitsForEmptyEventLoop = false;
firebase.initializeApp({
serviceAccount: {},
databaseURL: "https://harmanconnectedcar-180411.firebaseio.com/"
});
var starCountRef = firebase.database().ref('events' );
var promises = [];
promises.push(firebase.database().ref('events').once('value'));
promises.push(starCountRef.once('value');
Promises.all(promises).then(function(snapshots) {
snapshot.forEach(function(snapshot) {
console.log(snapshot.val();
});
context.succeed();
});
}
You seem to be mixing up two technologies here.
The onWrite method is an construct of Cloud Functions for Firebase, which (as far as I know) cannot be deployed on Amazon lambda.
If you want to access your Firebase Realtime Database from Amazon Lambda, you can use the Firebase Admin SDK. But that doesn't include a way to trigger your code whenever the database is written.
I am using aws-sdk for javascript.
The code below works fine when using in a stand-alone program
//program.js
const AWS = require('aws-sdk');
const firehose = new AWS.Firehose({
accessKeyId: "XXX",
secretAccessKey: "YY"
});
const params = {
DeliveryStreamName: 'demo1',
Record: {
Data: new Buffer("Hello World")
}
};
firehose.putRecord(params, function (err, data){
if (err) {
console.log(err);
return;
}
console.log(data); // successful response
});
Again, the above code works fine as a stand alone file. Data gets pushed into firehose and then further down to Redshift.
so if i execute
node program.js
I am able to see my data in Redshift. Yay!!
=============================
However, what i really want to achieve is to push data to firehose when a certain route gets hit in my express application. So I take the exact same code as above and stick it in my route
// router.js
const AWS = require('aws-sdk');
const firehose = new AWS.Firehose({
accessKeyId: "XXX",
secretAccessKey: "YY"
});
router
.get('/v1/locations/:id?', (req, res) => {
const params = {
DeliveryStreamName: 'demo1',
Record: {
Data: new Buffer("Hello World")
}
};
firehose.putRecord(params, function (err, data){
if (err) {
console.log(err);
return;
}
console.log(data);
});
// do the work that needs to be done for this route and send a response
res.send("some data");
});
The minute firehose.putRecord is executed .. it crashes my program with the following error:
```
TypeError: doneCallback.cal is not a function
at Request.callListeners (/api-project/node_modules/aws-sdk/lib/sequential_executor.js:115:18)
at callNextListener (/api-project/node_modules/aws-sdk/lib/sequential_executor.js:95:12)
at /api-project/node_modules/aws-sdk/lib/event_listeners.js:74:9
at finish (/api-project/node_modules/aws-sdk/lib/config.js:315:7)
at /api-project/node_modules/aws-sdk/lib/config.js:333:9
at Credentials.get (/api-project/node_modules/aws-sdk/lib/credentials.js:126:7)
at getAsyncCredentials (/api-project/node_modules/aws-sdk/lib/config.js:327:24)
at Config.getCredentials (/api-project/node_modules/aws-sdk/lib/config.js:347:9)
at Request.VALIDATE_CREDENTIALS (/api-project/node_modules/aws-sdk/lib/event_listeners.js:69:26)
at Request.callListeners (/api-project/node_modules/aws-sdk/lib/sequential_executor.js:101:18)
I can't understand why this code crashes my express program. is this is bug in the aws-sdk library or am i doing something wrong ?
You should be sending the express response inside your success callback.
firehose.putRecord(params, function (err, data) {
if (err) {
console.log(err);
return;
}
console.log(data);
res.send("some data");
}
);
FYI, your res.send(data) will effectively exit the program and send data. However, your putRecord callback is the time when your exit should occur. In node, things do not happen in a sequence from top to bottom of the code, but instead it executes in order of the callback events. so the execution flow for your code would be like this:
file executes
some operation is performed
callback for operation occurs
then if theres additional code outside of operate, it will continue, otherwise the code will exit in that callback. Hence, put the res.send in your putRecord callback.
I'm trying to get a list of contacts from the Google People API with Cloud Functions for Firebase but I'm only getting an empty object as the response. Any thoughts? Cloud Functions code below:
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
var google = require('googleapis');
var people = google.people('v1');
exports.contacts = functions.https.onRequest((request, response) => {
admin.database().ref('/settings/contacts/credentials/serviceAccount').once("value", function(data) {
var authClient = new google.auth.JWT(
data.child('clientEmail').val(),
null,
data.child('privateKey').val(),
['https://www.googleapis.com/auth/contacts'],
null
);
authClient.authorize(function (err, tokens) {
if (err) {
console.error(err);
response.end();
return;
}
// Make an authorized request to list contacts.
people.people.connections.list({auth: authClient, resourceName: 'people/me'}, function(err, resp) {
if (err) {
console.error(err);
response.end();
return;
}
console.log("Success");
console.log(resp);
response.send(resp);
});
});
});
});
In the Firebase console logs, the success message is printed along with the empty JSON object. Seems to be authorizing successfully so not quite sure what's going on. Any help would be greatly appreciated.
Try wrapping the api call in a Promise. I had a similar issue until I wrapped mine in a promise. Also in the specific example linked to here I needed to use response.data and not response.labels with the the resolve part of the promise. console.log() will be your best friend here.
https://cloud.google.com/community/tutorials/cloud-functions-oauth-gmail
https://github.com/GoogleCloudPlatform/community/blob/master/tutorials/cloud-functions-oauth-gmail/index.js