What format should data sent to a Google Cloud Functions onCall request be in? - node.js

I have the most simple Google onCall cloud function:
// [START]
exports.echo = functions.https.onCall(async (data, context) => {
return {
value: "echo"
};
});
// [END]
I submit the following in the Google cloud console test:
{"data":"somedata"}
I am getting the following error:
{"error":{"message":"INTERNAL","status":"INTERNAL"}}
It seems there is an issue with the object that I am passing. Can anyone tell me what the correct format is? What am I missing here?
P.s: I am ultimately trying to connect an onCall function with a client application via Firebase/fire, but that is also giving the same error.
Here's the trace:
/workspace/node_modules/firebase-functions/lib/common/providers/https.js:349:16 at fixedLen (/workspace/node_modules/firebase-functions/lib/providers/https.js:66:41) at /workspace/node_modules/firebase-functions/lib/common/providers/https.js:385:32 at processTicksAndRejections (internal/process/task_queues.js:95:5)
Unhandled error TypeError: res.on is not a function at /workspace/node_modules/firebase-functions/lib/common/providers/https.js:350:17 at new Promise () at /workspace/node_modules/firebase-functions/lib/common/providers/https.js:349:16 at fixedLen (/workspace/node_modules/firebase-functions/lib/providers/https.js:66:41) at /workspace/node_modules/firebase-functions/lib/common/providers/https.js:385:32 at processTicksAndRejections (internal/process/task_queues.js:95:5)
Here's the index.ts:
import * as functions from "firebase-functions";
// The Firebase Admin SDK to access Cloud Firestore.
import admin = require("firebase-admin");
// Triggers
import { echo } from "./triggers/echo"
admin.initializeApp();
// Exports
module.exports = {
// OnCall
echo: functions.https.onCall(echo)
};

A few things:
The arrow function signature was incorrect.
async is not required for this synchronous response.
The correct type of the context parameter is CallableContext:
import * as functions from 'firebase-functions';
export const echo = functions.https.onCall((data:any, context:functions.https.CallableContext) => ({
value: "echo"
}))

Related

Using serverless deployment to lambda with ES6 /Node.js v16

Newbie question....
I have a locally working node.js application which I am now trying to deploy express to AWS lambda. I have used this guide to deploy a test version (which worked).
I now am trying to implement my application which uses ES6 (and has type: module in package.json).
In my application I have added
import serverless from 'serverless-http'
but I cannot figure out the appropriate syntax for the export - the original was...
module.exports.handler = serverless(app);
I have tried:
const handler = async (app) =\> {
return serverless(app)
}
export default handler
Error message received:
2022-11-05T15:50:25.962Z undefined ERROR Uncaught Exception
"errorType": "Runtime.HandlerNotFound",
"errorMessage": "app.handler is undefined or not exported",
"stack": [
"Runtime.HandlerNotFound: app.handler is undefined or not exported",
" at Object.UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:979:15)",
" at async start (file:///var/runtime/index.mjs:1137:23)",
" at async file:///var/runtime/index.mjs:1143:1"
]
I strongly suspect I am missing some fundamental understanding - truly appreciate some guidance.
The reason for the error is that you are sending a default export when AWS Lambda is expecting a named export.
The issue is the same as with all ES6 imports/exports:
// export.js
export default const defaultExport = "foo"
export const namedExport = "bar"
// import.js
import { defaultExport } from "./export.js" // error, cannot find defaultExport
import { namedExport } from "./export.js" // success, found namedExport
import defaultExport from "./export.js" // success, found defaultExport
So, it's like the case above where you're sending the defaultExport, but AWS Lambda wants the { namedExport }. You just need to remove default from your export, and make sure you're building the handler properly. Here is a suggestion to do that:
const lambda = serverless(app)
export async function handler(event, context) {
return lambda(event, context)
}
I've tested and it's working with serverless-offline using Node18.x. You can read more about exports on MDN.

Can't get ref on realtime database with emulator

I am trying to attach a listener to realtime database on local emulator with another database selected but it's giving me a weird error.
Here is the code I am using:
const functions = require("firebase-functions");
const handler = async (snapshot, context) => {
console.log(snapshot);
console.log(context);
}
const target = functions.database
.instance("test-db")
.ref("/docs/{docID}")
module.exports = {
create: target.onCreate(handler),
update: target.onUpdate(handler),
delete: target.onDelete(handler),
}
Here is the error that I am getting:
i functions: Beginning execution of "operationsListenerRealtime-update"
! functions: TypeError: Cannot read property 'startsWith' of undefined
at new DataSnapshot (Q:\Projects\test-project\firebase\functions\node_modules\firebase-functions\lib\providers\database.js:270:44)
at RefBuilder.changeConstructor (Q:\Projects\test-project\firebase\functions\node_modules\firebase-functions\lib\providers\database.js:155:28)
at cloudFunction (Q:\Projects\test-project\firebase\functions\node_modules\firebase-functions\lib\cloud-functions.js:133:34)
at C:\Users\duoqu\AppData\Roaming\npm\node_modules\firebase-tools\lib\emulator\functionsEmulatorRuntime.js:592:16
at runFunction (C:\Users\duoqu\AppData\Roaming\npm\node_modules\firebase-tools\lib\emulator\functionsEmulatorRuntime.js:579:15)
at runBackground (C:\Users\duoqu\AppData\Roaming\npm\node_modules\firebase-tools\lib\emulator\functionsEmulatorRuntime.js:591:11)
at processBackground (C:\Users\duoqu\AppData\Roaming\npm\node_modules\firebase-tools\lib\emulator\functionsEmulatorRuntime.js:574:11)
at invokeTrigger (C:\Users\duoqu\AppData\Roaming\npm\node_modules\firebase-tools\lib\emulator\functionsEmulatorRuntime.js:649:19)
at handleMessage (C:\Users\duoqu\AppData\Roaming\npm\node_modules\firebase-tools\lib\emulator\functionsEmulatorRuntime.js:736:15)
at processTicksAndRejections (node:internal/process/task_queues:94:5)
! Your function was killed because it raised an unhandled error.
Clearly I am trying to connect realtime database that is test-db, I also tried writing http://localhost:7002/?ns=test-db but still no luck. I digged deep into the error and saw this thing:
/**
* Interface representing a Firebase Realtime Database data snapshot.
*/
class DataSnapshot {
constructor(data, path, // path will be undefined for the database root
app, instance) {
this.app = app;
if (app && app.options.databaseURL.startsWith('http:')) {
// In this case we're dealing with an emulator
this.instance = app.options.databaseURL;
}
else if (instance) {
// SDK always supplies instance, but user's unit tests may not
this.instance = instance;
}
else if (app) {
this.instance = app.options.databaseURL;
}
else if (process.env.GCLOUD_PROJECT) {
this.instance =
'https://' + process.env.GCLOUD_PROJECT + '.firebaseio.com';
}
this._path = path;
this._data = data;
}
I think the issue may be related to this part of the code
Also the instance function takes parameters like this.
EDIT: I have confirmed that with proper node version it still is giving me the same error.

Why do I get a UserCodeSyntaxError when I have no syntax error in my code?

I'm currently creating a Dialogflow chatbot in nodejs and upon deploying my code I get an error message. I've attempted to uncomment most things out to just be left with the base functioning code and I am still unable to get it working. I'm not exactly sure what the issue is here
'use strict';
import {getAPIresponse} from "./api/index.js";
// const http = require('https');
// const respond = fulfillmentText => {
// return {
// statusCode: 200,
// body: JSON.stringify({
// fulfillmentText
// }),
// headers: {
// "Content-Type": "application/json"
// }
// }
//
// };
module.exports.dining = async (event,context) => {
const incoming= JSON.parse(event.body).queryResult;
console.log(`INCOMING: ${incoming.parameters.hall}`);
const {
displayName
} = incoming.intent;
console.log(displayName);
//const menu = getAPIresponse('https://esb.prod.uds.harvard.edu/api/dining/2.0/','events?locationId=36');
//console.log(menu);
// if(displayName === 'dining'){
// if(incoming.parameters.meal === 'breakfast'){
// //get's dining hall code to include in API request
// const hall = getCode(incoming.parameters.hall);
// //generate response from API based off of parameters passed by user
// const menu = getAPIresponse("https://esb.prod.uds.harvard.edu/api/dining/2.0/","events?locationId=${hall}", hall);
// console.log(menu);
// }
// if(incoming.parameters.meal === 'lunch'){
// //get's dining hall code to include in API request
// const hall = getCode(incoming.parameters.hall);
// //generate response from API based off of parameters passed by user
// const menu = getAPIresponse("https://esb.prod.uds.harvard.edu/api/dining/2.0","/events", hall);
// }
// if(incoming.parameters.meal === 'dinner'){
// //get's dining hall code to include in API request
// const hall = getCode(incoming.parameters.hall);
// //generate response from API based off of parameters passed by user
// const menu = getAPIresponse("https://esb.prod.uds.harvard.edu/api/dining/2.0","/events", hall);
// }
// }
};
Almost everything is commented out and I still get the error message that reads
2019-07-02 16:31:33.351 (-04:00) undefined ERROR Uncaught Exception {
"errorType":"Runtime.UserCodeSyntaxError","errorMessage":"SyntaxError: Unexpected tok
en {","stack":["Runtime.UserCodeSyntaxError: SyntaxError: Unexpected token {"," at
_loadUserApp (/var/runtime/UserFunction.js:98:13)"," at Object.module.exports.loa
d (/var/runtime/UserFunction.js:140:17)"," at Object.<anonymous> (/var/runtime/ind
ex.js:36:30)"," at Module._compile (internal/modules/cjs/loader.js:701:30)"," a
t Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)"," at Modu
le.load (internal/modules/cjs/loader.js:600:32)"," at tryModuleLoad (internal/modu
les/cjs/loader.js:539:12)"," at Function.Module._load (internal/modules/cjs/loader
.js:531:3)"," at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)",
" at startup (internal/bootstrap/node.js:283:19)"]}
Worked for me: Updating Node.js version of lambda
I got this error because lambda was defined to execute with Node.js 12.x, when I changed it to Node.js 14.x (as on my local machine) it worked
If it works - and you're generally using the serverless package to automate the deployment of your lambda - don't forget to update your serverless.yml file accordingly
AWS Lambda does not support the ES6 import specifier as you've written here
import {getAPIresponse} from "./api/index.js";
because the ES6 import syntax isn't yet supported by default in Node.js (note: my lambda runtime was set to Node.js 10.x).
Illustration:
I was having this issue as well when importing a library at the top of my lambda distribution's index.js file.
The stacktrace Uncaught Exception { "errorType":"Runtime.UserCodeSyntaxError", ... unexpected token import found ... blabla... } ... was thrown in my lambda function when I used the import syntax:
import awsServerlessExpress from 'aws-serverless-express';
exports.handler = (event, context) => {
console.log('hello world!')
};
But not in this version below when I just used the standard module require syntax.
const awsServerlessExpress = require('aws-serverless-express');
exports.handler = (event, context) => {
console.log('hello world!')
};
For me, it was the import syntax that was causing the SyntaxError exceptions, but do take note that, for you, any JavaScript syntax not supported by your current Node.js runtime will throw this exception.
A couple of solutions:
Change all import statements to standard module require statements and keep using whatever default JavaScript flavour is supported by your configured Node.js runtime.
Use a transpiler like Babel w/ Webpack to transpile your ES6 JavaScript before deploying to the cloud.
Use the quick solution nicely described by Yitzchak below :) Just bump the NodeJS version on your Lambda Dashboard.
In my case, I pasted code from another lambda which had node version 14.
But my current lambda node version was 12.
The code I pasted was using optional chaining(?.) in one line. Removed it, deployed code and it worked fine.
If you are using TypeScript and encounter this error make sure the target you set in tsconfig.json matches your targeted nodejs version. For a list of recommended settings visit Node Target Mapping
For example I was targeting node.js 12 and using ES2020. Changing this to ES2019 fixed my issue.
Sometimes in lambda, when we click on Deploy all the changes are not deployed. Refresh the page and check for syntax errors.
or at least that's what was the problem in my case.
In my case I changed the tsconfig.json module setting:
"module": "es2015"
to
"module": "CommonJS",
ref: https://www.typescriptlang.org/tsconfig#module

Error:"Failed to get the current sub/segment from the context" when use AWS X-ray in Lambda with node.js

I am trying to use implement the AWS X-ray into my current project (using Node.js and Serverless framework). I am trying to wire the X-ray to one of my lambda function, I got the problem of
Error: Failed to get the current sub/segment from the context.
at Object.contextMissingRuntimeError [as contextMissing] (/.../node_modules/aws-xray-sdk-core/lib/context_utils.js:21:15)
at Object.getSegment (/.../node_modules/aws-xray-sdk-core/lib/context_utils.js:92:45)
at Object.resolveSegment (/.../node_modules/aws-xray-sdk-core/lib/context_utils.js:73:19).....
code below:
import { DynamoDB } from "aws-sdk";
import AWSXRay from 'aws-xray-sdk';
export const handler = async (event, context, callback) => {
const dynamo = new DynamoDB.DocumentClient({
service: new DynamoDB({ region })
});
AWSXRay.captureAWSClient(dynamo.service);
try {
// call dynamoDB function
} catch(err) {
//...
}
}
for this problem, I use the solution from
https://forums.aws.amazon.com/thread.jspa?messageID=821510&#821510
the other solution I tried is from https://forums.aws.amazon.com/thread.jspa?messageID=829923&#829923
code is like
import AWSXRay from 'aws-xray-sdk';
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
export const handler = async (event, context, callback) => {
const dynamo = new AWS.DynamoDB.DocumentClient({region});
//....
}
Still not working...
Appreciated to the help of any kind.
As you mention, that happened because you're running locally (using serverless-offline plugin) and the serverless-offline plugin doesn't provide a valid XRAY context.
One possible way to pass this error and still be able to call your function locally is setting AWS_XRAY_CONTEXT_MISSING environment variable to LOG_ERROR instead of RUNTIME_ERROR (default).
Something like:
serverless invoke local -f functionName -e AWS_XRAY_CONTEXT_MISSING=LOG_ERROR
I didn't test this using serverless framework but it worked when the same error occurred calling an amplify function locally:
amplify function invoke <function-name>
I encountered this error also. To fix it, I disabled XRay when running locally. XRay isn't needed when running locally because I can just set up debug log statements at that time.
This is what the code would look like
let AWS = require('aws-sdk');
if (!process.env.IS_OFFLINE) {
const AWSXRay = require('aws-xray-sdk');
AWS = AWSXRay.captureAWS(require('aws-sdk'));
}
If you don't like this approach, you can set up a contextStrategy to not error out when the context is missing.
Link here
AWSXRay.setContextMissingStrategy("LOG_ERROR");
If you don't want the error clogging up your output you can add a helper that ignores only that error.
// Removes noisy Error: Failed to get the current sub/segment from the context due to Xray
export async function disableXrayError() {
console.error = jest.fn((err) => {
if (err.message.includes("Failed to get the current sub/segment from the context")) {
return;
} else {
console.error(err);
}
});
}

Firebase Cloud Function - Unit Testing - Please supply a Firebase app in the constructor for DataSnapshot in order to use the .ref method

I am trying to setup Unit Testing for Firebase cloud functions. I was following these links:
https://firebase.google.com/docs/functions/unit-testing
https://github.com/firebase/functions-samples/blob/4663b4ddfae3ed8f8a110156d60e71f028680ee7/quickstarts/uppercase/functions/test/test.online.js
I am trying to make the sample code run. Code is as follows:
const chai = require('chai');
const sinon = require('sinon');
const admin = require('firebase-admin');
const projectConfig = {
databaseURL : 'https://gr-automation-5e65c.firebaseio.com',
storageBucket : 'gr-automation-5e65c.appspot.com',
projectId : 'gr-automation-5e65c',
};
const test = require('firebase-functions-test')(projectConfig, '../gr-automation-5e65c-firebase-adminsdk-jkdtf-849f3d0f65.json');
test.mockConfig( /* removed for Clarity */ );
const assert = chai.assert;
describe('Cloud Functions', () => {
let myFunctions;
adminInitStub = sinon.stub(admin, 'initializeApp');
admin.initializeApp();
//console.log(test);
before(() => {
myFunctions = require('../lib/index.js');
//console.log(myFunctions);
//console.log(admin);
});
after(() => {
test.cleanup();
admin.database().ref('messages').remove();
});
describe('makeUpperCase', () => {
it('should upper case input and write it to /uppercase', () => {
const snap = test.database.makeDataSnapshot('input', 'messages/11111/original');
const wrapped = test.wrap(myFunctions.makeUppercase);
return wrapped(snap).then(() => {
return admin.database().ref('messages/11111/uppercase').once('value').then((createdSnap) => {
assert.equal(createdSnap.val(), 'INPUT');
});
});
})
});
});
When I run the test, I get the following error:
Cloud Functions
makeUpperCase
Uppercasing undefined input
1) should upper case input and write it to /uppercase
2) "after all" hook
0 passing (364ms)
2 failing
1) Cloud Functions
makeUpperCase
should upper case input and write it to /uppercase:
Error: Please supply a Firebase app in the constructor for DataSnapshot in order to use the .ref method.
at DataSnapshot.get ref [as ref] (node_modules/firebase-functions/lib/providers/database.js:186:19)
at Function.exports.makeUppercase.functions.database.ref.onCreate [as run] (lib/index.js:135:21)
at wrapped (node_modules/firebase-functions-test/lib/main.js:53:30)
at Context.it (test/index.test.js:46:13)
2) Cloud Functions
"after all" hook:
Error: The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services.
at FirebaseAppError.FirebaseError [as constructor] (node_modules/firebase-admin/lib/utils/error.js:39:28)
at FirebaseAppError.PrefixedFirebaseError [as constructor] (node_modules/firebase-admin/lib/utils/error.js:85:28)
at new FirebaseAppError (node_modules/firebase-admin/lib/utils/error.js:119:28)
at FirebaseNamespaceInternals.app (node_modules/firebase-admin/lib/firebase-namespace.js:105:19)
at FirebaseNamespace.app (node_modules/firebase-admin/lib/firebase-namespace.js:372:30)
at FirebaseNamespace.ensureApp (node_modules/firebase-admin/lib/firebase-namespace.js:388:24)
at FirebaseNamespace.fn (node_modules/firebase-admin/lib/firebase-namespace.js:283:30)
at Context.after (test/index.test.js:31:15)
Any hint on what am I doing wrong?
test.data.makeDataSnapshot has an optional third parameter which is a Firebase app (see https://firebase.google.com/docs/reference/functions/test/test.database#.makeDataSnapshot). However, since you initialized the firebase-functions-test with your project config values, you normally do not need to supply it.
However you have this line:
adminInitStub = sinon.stub(admin, 'initializeApp');
This is causing the next line to initialize a fake app, since initializeApp method was stubbed out to not do anything
This is causing the 2 failures, to fix, remove:
adminInitStub = sinon.stub(admin, 'initializeApp');

Resources