AWS lambda timeout on custom intents - javascript

I am building an Amazon Alexa skill that gets data async from Google's Firebase.
When I run the Lambda function locally and call it from my Alexa skill all intents work as expected.
However when I zip up the files (not the folder) and move it to AWS lambda the function times out even thought the data has been received and the response object created as expected.
The built in intents are all working as expected too
My code is on GitHub here
The error log and console.log outputs
I have tried to find any solutions via here and google but no luck. It could be that I have been searching the wrong things or this is a specific problem

Resource
When using firebase with lambda it would appear that you need to initalize and then delete the instance for the response to be returned.
this is a code snippit that I got to work
const Alexa = require("ask-sdk");
const firebase = require("firebase");
var config = {
...
};
const GetOrderIntent = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return (
request.type === "IntentRequest" &&
request.intent.name === "GetOrderIntent"
);
},
async handle(handlerInput) {
firebase.initializeApp(config);
try {
const store = await firebase
.database()
.ref(`teams/${team}`)
.once("value");
// ANY OTHER CODE HERE
} catch (error) {
// HANDLE ERROR
}
// CLOSE THE CONNECTION
await firebase.app("[DEFAULT]").delete();
return handlerInput.responseBuilder
.speak(speechOutput)
.withSimpleCard(SKILL_NAME, speechOutput)
.getResponse();
}
};

Related

Update 'profiles' on Supabase with RLS

I'm currently attempting to use Supabase's JavaScript API to update a row in my 'profiles' database, which has RLS on, via my backend.
This is being done following Stripe sending me a webhook indicating a payment has been successful.
I won't put the full API call in, but here is my Supabase code:
const supabaseUrl = process.env.REACT_APP_SUPABASE_URL
const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY
const supabase = createClient(supabaseUrl, supabaseAnonKey)
module.exports = async (req, res) => {
if (event.type === "checkout.session.completed") {
const userId = String(event.data.object.client_reference_id)
const { error } = await supabase.from('profiles').update({ premium: 'true' }).eq('id', userId)
if (error) {
console.log(error)
}
}
}
However, every time I try to run this, I get a 404 error. This seems to be because I have RLS on.
As a result, I have two questions:
Is it safe for me to turn RLS off?
How can I adjust my code / apply a new database policy to allow this to be accepted?

Interacting with firebase firestore from firebase cloud functions

I am trying to interact with firebase firestore from my cloud function. The cloud functions looks like the following:
const admin = require("firebase-admin");
const functions = require("firebase-functions");
admin.initializeApp();
const db = admin.firestore();
exports.addVote = functions.https.onCall((data, context) => {
return db
.doc("sdd-enheter/enhet/votes/voteID")
.set({ user: "user", vote: 0 });
});
When calling the function from the client side I get a firebase internal error, indicating that the function has been called but throws an error. What should I do to fix this?
Your function needs to return a promise or otherwise terminate by throwing an https error. Throwing the https error will give the client back a relevant error that it can handle, so consider making that a habit. The function below covers both of those bases. If you still get an error then share that error with us as it appears in the console's function log.
exports.addVote = functions.https.onCall((_data, _context) => {
const db = admin.firestore();
try {
return db.doc("sdd-enheter/enhet/votes/voteID").set({user: "user", vote: 0});
} catch (error) {
throw new functions.https.HttpsError("unknown", "Failed to add vote.", error);
}
});

Delete a batch of docs using admin SDK and callable cloud function in most similar way to client SDK?

I have an array of docs ids that I want to delete in using a cloud function, my code looks like the following :
//If the user decieds on deleting his account forever we need to make sure we wont have any thing left inside of db after this !!
// incoming payload array of 3 docs
data = {array : ['a302-5e9c8ae97b3b','92c8d309-090d','a302-5e932c8ae97b3b']}
export const deleteClients = functions.https.onCall(async (data, context) => {
try {
// declare batch
const batch = db.batch();
// set
data.arr.forEach((doc: string) => {
batch.delete(db.collection('Data'), doc);
});
// commit
await batch.commit();
} catch (e) {
console.log(e);
}
return null;
});
I am getting a syntax error on batch.delete how to pass the right arguments in to the batch delete to reference that doc I want to submit for deletion before commit ?
Delete takes a single param, the doc ref of the doc to be deleted.
data.arr.forEach((docId: string) => {
batch.delete(doc(db, "Data", docId));
});
There are several errors in your code:
data.arr.forEach() cannot work wince your data object contains one element with the key array and not the key arr.
You are mixing up the syntax of the JS SDK v9 and the Admin SDK. See the write batch Admin SDK syntax here.
You need to send back some data to the client when all the asynchronous work is complete, to correctly terminate your CF.
You do return null; AFTER the try/catch block: this means that, in most of the cases, your Cloud Function will be terminated before asynchronous work is complete (see the link above)
So the following should do the trick (untested):
const db = admin.firestore();
const data = {array : ['a302-5e9c8ae97b3b','92c8d309-090d','a302-5e932c8ae97b3b']};
export const deleteClients = functions.https.onCall(async (data, context) => {
try {
const batch = db.batch();
const parentCollection = db.collection('Data')
data.array.forEach((docId) => {
batch.delete(parentCollection.doc(docId));
});
// commit
await batch.commit();
return {result: 'success'} // IMPORTANT, see explanations above
} catch (e) {
console.log(e);
// IMPORTANT See https://firebase.google.com/docs/functions/callable#handle_errors
}
});

I cannot get data from firebase database

I am new to firebase. I'm trying to retreive data from a real time database using a node.js server sending associated credentials to firebase, but something gets broken after once('value') is called: its returned promise never gets resolved and server stops itself logging this message: "Process exited with code 3221226505".
I wrote the following code:
async function testFirebase1(firebaseCredentialsObj, path) {
let firebase = require('firebase')
firebase.initializeApp(firebaseCredentialsObj);
var database = firebase.database();
var ref = database.ref(path);
console.log(ref.toString());
try {
// Attempt 1
var prom = await ref.once('value');
const data = prom.;
console.log('data ' + data)
// Attempt 2
prom.then((snapshot) => {
console.log('snapshot ' + snapshot)
}).catch((error) => { console.log(error)} )
} catch (error) {
console.log(error)
}
}
No error ever gets catched.
I also tried to get data as an admin, but i got the same failing result
async function testFirebase3(firebaseCredentials, serviceAccountKey, databaseURL, path) {
const admin=require('firebase-admin');
const serviceAccount = serviceAccountKey;
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: databaseURL
});
var db=admin.database();
var userRef=db.ref(path);
const prom = await userRef.once('value');
console.log(prom)
}
Promise returned from once() method keep beeing pendent. This is its log:
[[PromiseStatus]]:'pending'
[[PromiseValue]]:undefined
Server is supposed to get databases'data in json format and send it to the client.
Why is this happening?
Based on your code, you are mixing traditional Promise chaining and async/await syntax together which is leading to your confusion.
Note: In the below snippets, I use the database query coding style I describe at the end of this answer.
SDK Initialization
To start with, in both testFirebase1 and testFirebase3, you initialize the default Firebase app instance in the function. If you call either function only once, you won't experience any problems, but any time you call them another time, they will always throw an error stating that the app has already been initialized. To solve this, you can lazily load these libraries using the following functions:
function lazyFirebase(options, name = undefined) {
const firebase = require('firebase');
// alternatively use the Promise-based version in an async function:
// const firebase = await import('firebase');
try {
firebase.app(name);
} catch (err) {
firebase.initializeApp(options, name);
}
return firebase;
}
function lazyFirebaseAdmin(options, name = undefined) {
const admin = require('firebase-admin');
// alternatively use the Promise-based version in an async function:
// const admin = await import('firebase-admin');
try {
admin.app(name);
} catch (err) {
const cred = options.credential;
if (typeof cred === "string") {
options.credential = admin.credential.cert(cred)
}
admin.initializeApp(options, name);
}
return admin;
}
Important Note: Neither of the above functions checks whether they use the same options object to initialize them. It just assumes they are the same configuration object.
Correcting testFirebase1
In testFirebase1, you are initializing the default Firebase app instance and then starting the process of the getting the data from the database. Because you haven't returned the promise from the ref.once('value') in the function, the caller will get a Promise<undefined> that resolves before the database call completes.
async function testFirebase1(firebaseCredentialsObj, path) {
let firebase = require('firebase')
// bug: throws error if initializeApp called more than once
firebase.initializeApp(firebaseCredentialsObj);
// bug: using `var` - use `const` or `let`
var database = firebase.database();
var ref = database.ref(path);
console.log(ref.toString());
try {
// Attempt 1
// bug: using `await` here, makes this a DataSnapshot not a Promise<DataSnapshot>
// hence `prom` should be `snapshot`
// bug: using `var` - use `const` or `let`
var prom = await ref.once('value');
// bug: syntax error, assuming this was meant to be `prom.val()`
const data = prom.;
console.log('data ' + data)
// Attempt 2
// bug: a `DataSnapshot` doesn't have a `then` or `catch` method
// bug: if `prom` was a `Promise`, you should return it here
prom
.then((snapshot) => {
console.log('snapshot ' + snapshot)
})
.catch((error) => {
console.log(error)
})
} catch (error) {
console.log(error)
}
}
Correcting these problems (and making use of my coding style when dealing with RTDB queries) gives:
async function testFirebase1(firebaseCredentialsObj, path) {
const firebase = lazyFirebase(firebaseCredentialsObj);
const snapshot = await firebase.database()
.ref(path)
.once('value');
// returns data at this location
return snapshot.val();
}
Correcting testFirebase3
In testFirebase3, you are initializing the default Firebase Admin app instance and correctly waiting for the data from the database. Because you haven't returned the data from the database, the caller will get a Promise<undefined> that resolves when the database call completes but without the containing data.
async function testFirebase3(firebaseCredentials, serviceAccountKey, databaseURL, path) {
const admin = require('firebase-admin');
// note: unnecessary line, either call `serviceAccountKey` `serviceAccount` or use `serviceAccountKey` as-is
const serviceAccount = serviceAccountKey;
// bug: throws error if initializeApp called more than once
// bug: `firebaseCredentials` is unused
// note: when initializing the *default* app's configuration, you
// should specify all options to prevent bugs when using
// `admin.messaging()`, `admin.auth()`, `admin.storage()`, etc
// as they all share the default app instance
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: databaseURL
});
// bug: using `var` - use `const` or `let`
var db=admin.database();
var userRef=db.ref(path);
// bug: using `await` here, makes this a DataSnapshot not a Promise<DataSnapshot>
// hence `prom` should be `snapshot`
const prom = await userRef.once('value');
// bug: logging a `DataSnapshot` object isn't useful because it
// doesn't serialize properly (it doesn't define `toString()`,
// so it will be logged as "[object Object]")
console.log(prom)
}
Correcting these problems (and making use of my coding style when dealing with RTDB queries) gives:
async function testFirebase3(firebaseCredentials, serviceAccountKey, databaseURL, path) {
const admin = lazyFirebaseAdmin({
...firebaseCredentials, // note: assuming `firebaseCredentials` is the complete app configuration,
credential: serviceAccountKey,
databaseURL: databaseURL
});
const snapshot = await admin.database()
.ref(path)
.once('value');
return snapshot.val();
}

Notification not being sent via firebase functions. "undefined" logged in console

I am trying to send a sample notification to all devices according to their token, however the token is being logged as "undefined" and the notification subsequently fails to deliver
The following lines from my code successfully show me the data from the database:
const notificationSnapshot = change.after.val(); //get new value
console.info(notificationSnapshot);
However, the following gives "undefined", despite the above retrieving the data successfully.
const userToken = notificationSnapshot.token;
console.info(userToken);
Is this not the correct way to retrieve the token to send the notification to all the registered devices in my firebase database?
my whole function (index.js)
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendSampleNotification = functions.database.ref('/User')
.onWrite((change, context) => {
const notificationSnapshot = change.after.val(); //get new value
const userToken = notificationSnapshot.token;
const name = notificationSnapshot.name;
const surname = notificationSnapshot.surname;
console.info(notificationSnapshot);
console.info(userToken);
var message = {
notification: {
title: 'test title',
body: 'test message'
},
token: userToken
};
admin.messaging().send(message).then((response) => {
console.log("Message sent successfully:", response);
return response;
})
.catch((error) => {
console.log("Error sending message: ", error);
});
});
I would say that your issue is very similar to this one since you are having a missing token (showed as undefined) due to the executions times, more or less what Doug was pointing out.
Note that the solution relies on considering the execution times and I’ve seen also that the implementation differs in some method executions but I would say the generals point in the same direction.

Categories