Azure Function app with knex not finishing - azure

I created funnction app and service bus (with topic and its subscriptions), then added function (triggered by new message in topic's sub.). I wanted to write that message to DB (created that db and sql server for it - tried and i coudl connect with my local scripts and clients). Then, I added script's code into index.js in that new function:
module.exports = function(context, mySbMsg) {
context.log('EVENT : ', mySbMsg);
const dbConfig = {
client: 'mssql',
connection: {
server: '---',
user: '---',
password: '---',
options: {
database: '---',
port: 1433,
encrypt: true
}
}
}
const knex = require('knex')(dbConfig);
const newRow = {MessageRecived:mySbMsg}
context.log(' New row : ', newRow);
var res;
return knex.insert(newRow).into('MyEvents').timeout(3000)
.then((output)=>{
context.log(' result : ');
context.log(output);
res = { status: 201, body: "Insert succeeded." };
})
.catch((error) => {
context.log(' error : ');
context.log(error);
res = { status: 500, body: "Error occured" };
})
.finally(() => {
console.log('>> FINALLY');
knex.destroy();
context.done(null, res);
});
};
I dont understand why each request just "hangs" until Fucntion App itself decide it wont finsih, mark it as timeout and leaves. I thought it is becouse of knex, but I added "Destroy"... Still i don't know - Why isn't it working?

Related

Firebase 401 Error https://fcm.googleapis.com/fcm/send works with admin().messaging().send() but does not work with admin().messaing().sendTopic()

I have created my backend in nodejs like shown below. When calling code example 1, code executes without problem. Example 1 calls admin().messaging().send() and works; however, when calling code example 2 (sending topic message) there is 401 error (like shown below)
An error occurred when trying to authenticate to the FCM servers.
Make sure the credential used to authenticate this SDK has the proper permissions.
See https://firebase.google.com/docs/admin/setup for setup instructions.
PROJECT_NOT_PERMITTED
Error 401
Is there authorization setting when sending topic message? What needs to be done to resolve issue with 401 Error? Thank you
Example 1
// Load the AWS SDK for Node.js
var AWS = require("aws-sdk");
// Set the region
AWS.config.update({ region: "ap-northeast-2" });
var admin = require("firebase-admin");
var serviceAccount = require("../firebadeCredentialInformation.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
module.exports.handler = async (event) => {
console.log(event);
var body = JSON.parse(event.body);
console.log(body);
var topic;
var topicPayload;
try {
//body, title, data, token
const message = {
data: {
type: "VOTE",
senderName: body.senderName,
question: body.question,
questionRangeKey: body.questionRangeKey,
senderToken: body.senderToken,
gender: body.gender,
schoolGrade: body.schoolGrade,
schoolName: body.schoolName,
},
token: body.token,
};
//? Make sure the message is sent
//TODO REMOVE BELOW AWAIT TO IMPROVE SPEED
var result = await admin.messaging().send(message);
console.log(result);
return {
statusCode: 200,
body: JSON.stringify(
{
message: "Sucessfully sent response",
input: event,
},
null,
2
),
};
} catch (e) {
console.log("Error", e);
return {
statusCode: 500,
body: JSON.stringify(
{
message: e,
input: event,
},
null,
2
),
};
}
};
Example 2
// Load the AWS SDK for Node.js
var AWS = require("aws-sdk");
// Set the region
AWS.config.update({ region: "ap-northeast-2" });
var admin = require("firebase-admin");
var serviceAccount = require("../firebadeCredentialInformation.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
module.exports.handler = async (event) => {
console.log(event);
var body = JSON.parse(event.body);
console.log(body);
var topic;
var topicPayload;
try {
//body, title, data, token
const message = {
data: {
type: "VOTE",
senderName: body.senderName,
question: body.question,
questionRangeKey: body.questionRangeKey,
senderToken: body.senderToken,
gender: body.gender,
schoolGrade: body.schoolGrade,
schoolName: body.schoolName,
},
token: body.token,
};
//? Make sure the message is sent
//TODO REMOVE BELOW AWAIT TO IMPROVE SPEED
var result = await admin.messaging().send(message);
console.log(result);
if (body.schoolId != "") {
//* Ff not missing school id
const topicPayload = {
data: {
type: "TOPIC",
senderName: body.senderName,
question: body.question,
questionRangeKey: body.questionRangeKey,
senderToken: body.senderToken,
gender: body.gender,
schoolGrade: body.schoolGrade,
schoolName: body.schoolName,
},
};
const schoolId = body.schoolId;
const topic = "/topics/" + schoolId;
console.log(topic);
//? Make sure the message is sent
//TODO REMOVE BELOW AWAIT TO IMPROVE SPEED
// Send a message to devices subscribed to the provided topic.
var topicResult = await admin
.messaging()
.sendToTopic(topic, topicPayload);
console.log(topicResult);
}
return {
statusCode: 200,
body: JSON.stringify(
{
message: "Sucessfully sent response",
input: event,
},
null,
2
),
};
} catch (e) {
console.log("Error", e);
return {
statusCode: 500,
body: JSON.stringify(
{
message: e,
input: event,
},
null,
2
),
};
}
};
Have figured out the issue for those who are stuck as well. Instead of using admin().message.sendTopic(). I have used the command below which is also same as sending a topic message, which worked.
//* if not missing school id
const schoolId = body.schoolId;
const topic = "/topics/" + schoolId;
const topicPayload = {
data: {
type: "TOPIC",
senderName: body.senderName,
},
topic: topic,
};
console.log(topic);
// Send a message to devices subscribed to the provided topic.
var topicResult = await admin.messaging().send(topicPayload);

Data command failed: 421 4.3.0 Temporary System Problem. Try again later (10) - gsmtp

I have deployed an HTTPS Function on Firebase, and I always get this error when I'm trying to send emails in bulk. I am using nodemailer pool to send emails.
Error: Data command failed: 421 4.3.0 Temporary System Problem. Try again later (10) - gsmtp
Client-side code:
export const sendEmails = async () => {
// Fetch All Users
let users = []
let data = await db.collection('users')
data.forEach(e => {
let data = e.data()
let email = data['email']
users.push({ email })
})
// Divide users into multiple batches of 25
let batches = [[]], num = 0, batchSize = 25
Promise.all(users.map(batch => {
if (batches[num].length < batchSize) {
batches[num].push(batch)
} else {
batches.push([batch])
num++
}
}))
// Send Email Request for each batch with some cooldown time.
Promise.all(batches.map((batch, index) => {
setTimeout(async () => {
await sendBroadcast(batch)
}, 2000 * index)
}))
}
export const sendBroadcast = async (users) => {
const url = base + "sendBroadcast?"
const body = JSON.stringify({ users })
return await fetch(url, { method: "POST", body })
}
Server-side code:
let transporter = nodemailer.createTransport({
service: 'gmail',
pool: true,
maxConnections: 20,
maxMessages: 500,
auth: {
user: 'SOME EMAIL',
pass: 'SOME PASSWORD',
},
})
exports.sendBroadcast = functions.runWith({ timeoutSeconds: 540, memory: '2GB' }).https.onRequest((req, res) => {
cors(req, res, () => {
const body = JSON.parse(req.body)
const users = body.users
console.log('info', 'Send email to ' + users.length + ' users')
users.forEach(function (to, index) {
var msg = {
from: 'SOME EMAIL',
to: to.email,
subject: "SUBJECT",
html: "<h2> SOME HTML </h2>",
}
return transporter.sendMail(msg, (erro, info) => {
if (erro) {
console.log('error', erro.message, 'failed to send to ' + email)
return res.status(500).send(erro.toString())
}
return res.send('sent')
})
})
})
})
Is this because gmail limits number of emails per day? I read somewhere that it would work if I added cooldowns after each email, I tried that as well but I was getting "Client network socket disconnected before secure TLS connection was established" error. Here's the Stack Overflow thread for that issue: Firebase Function Error "Client network socket disconnected before secure TLS connection was established" when sending bulk emails via Nodemailer

Why am I apparently connecting to SQL Azure database using node,js but query is not executed?

I am using the following code to connect to my SQL Azure database using node.js
I took the code from How to connect an existing nodejs server app to Azure SQL database
const express=require('express');
const router = express.Router()
const sql = require('mssql')
const config = {
user: "<user>",
password: "<password>",
server: "<myserver>.database.windows.net",
database: "<mydatabase>",
connectionTimeout: 3000,
parseJSON: true,
options: {
encrypt: true,
enableArithAbort: true
},
pool: {
min: 0,
idleTimeoutMillis: 3000
}
};
const pool = new sql.ConnectionPool(config);
const poolConnect = pool.connect();
router.get('/', async function (req, res) {
console.log('here');
await poolConnect;
try {
const request = pool.request();
const result = await request.query('select 1 as number')
console.log(result);
res.json(result.recordset);
} catch (err) {
console.error('SQL error', err);
res.send(err);
}
});
I know the code is connecting, because if I change the password then I get an error like this
(node:24172) UnhandledPromiseRejectionWarning: ConnectionError: Login failed for user '<user>'.
at Connection.<anonymous> (C:\Users\myuser\node_modules\mssql\lib\tedious\connection-pool.js:68:17)
at Object.onceWrapper (events.js:417:26)
at Connection.emit (events.js:310:20)
at Connection.message (C:\Users\myuser\node_modules\mssql\node_modules\tedious\lib\connection.js:2148:18)
at Connection.dispatchEvent (C:\Users\myuser\node_modules\mssql\node_modules\tedious\lib\connection.js:1279:15)
But if I put the correct password in, then the code just does not output anything at all.
It appears that it is simply not entering the asynchronous function at router.get('/', async function (req, res) {
Any ideas why?
As you stated in your comment, you don't start an express listener or do any requests targeting your express app. So of course, your code will never be executed. If it's just for testing purposes, you don't need express. Just create an async function and call it in your app.
const sql = require('mssql')
const config = {
user: "<user>",
password: "<password>",
server: "<myserver>.database.windows.net",
database: "<mydatabase>",
connectionTimeout: 3000,
parseJSON: true,
options: {
encrypt: true,
enableArithAbort: true
},
pool: {
min: 0,
idleTimeoutMillis: 3000
}
};
const pool = new sql.ConnectionPool(config);
doRequest()
.then(_ => { console.log("succeeded"); })
.catch(e => { console.log("error", e); });
async function doRequest() {
await pool.connect();
let result = await pool.request().query("select 1 as number");
console.log(result);
}
If you really need it in an express app, grab one basic express beginners tutorials which are out there, create a simple express and include your db-request code in one of the route-handlers ...

AWS Lambda NodeJS import returns null module, but only in AWS

UPDATED
I am getting the following error when trying to invoke my Lambda function
{
"errorType": "TypeError",
"errorMessage": "e is not a function",
"trace": [
"TypeError: e is not a function",
" at Runtime.handler (/var/task/serverless_sdk/index.js:9:88355)",
" at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
]
}
I have tracked this down to the reference to DB (see last few lines of schema.js DB should be imported at the top of schema.js
const { DB } = require('./db.js')
Indeed, when I try the same code on my local computer, there is no issue.
Does this have to do with some subtle ways how Lambda Functions (LF) are frozen for re-use in AWS? Where should I be initializing the DB connection in a LF?
I tried merging db.js into schema.js (no import) and I still get the same error.
I have checked the zip file that serverless loaded and it looks fine (node_modules and mine).
This is very hard to debug. So any tips in that direction would help.
server.js
const { ApolloServer } = require('apollo-server')
const { ApolloServer: ApolloServerLambda } = require('apollo-server-lambda')
const { typeDefs, resolvers, connect } = require('./schema.js')
// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.
async function setup(where) {
if (where == 'local') {
const server = new ApolloServer({ typeDefs, resolvers })
let { url } = await server.listen()
console.log(`Server ready at ${url}`)
} else {
const server = new ApolloServerLambda({
typeDefs,
resolvers,
playground: true,
introspection: true,
cors: {
origin: '*',
credentials: true,
},
context: ({ event, context }) => (
{
headers: event.headers,
functionName: context.functionName,
event,
context
})
})
exports.graphqlHandler = server.createHandler()
}
}
let location = (process.env.USERNAME == 'ysg4206') ? 'local' : 'aws'
connect(location, setup)
schema.js
const { gql } = require('apollo-server')
const { GraphQLDateTime } = require('graphql-iso-date')
const { DB } = require('./db.js')
exports.typeDefs = gql`
scalar DateTime
type User {
id: Int
"English First Name"
firstName: String
lastName: String
addressNumber: Int
streetName: String
city: String
email: String
createdAt: DateTime
updatedAt: DateTime
}
type Query {
users: [User]
findUser(firstName: String): User
hello(reply: String): String
}
type Mutation {
addUser(user: UserType): User!
}
type Subscription {
newUser: User!
}
`
exports.resolvers = {
Query: {
users: () => DB.findAll(),
findUser: async (_, { firstName }) => {
let who = await DB.findFirst(firstName)
return who
},
hello: (_, { reply }, context, info) => {
console.log(`hello with reply ${reply}`)
console.log(`context : ${JSON.stringify(context)}`)
console.log(`info : ${JSON.stringify(info)}`)
return reply
}
},
Mutation: {
addUser: async (_, args) => {
let who = await DB.addUser(args.user)
return who
}
}
}
exports.connect = async (where, setup) => {
console.log(`DB: ${DB}') // BUG DB is returning null
await DB.dbSetup(where) //BUG these lines cause Lambda to fail
await DB.populate() //BUG these lines cause Lambda to fail
let users = await DB.findAll() //BUG these lines cause Lambda to fail
console.log(users) //BUG these lines cause Lambda to fail
await setup(where)
}
db.js
const { Sequelize } = require('sequelize')
const { userData } = require('./userData')
const localHost = {
db: 'm3_db',
host: 'localhost',
pass: 'xxxx'
}
const awsHost = {
db: 'mapollodb3_db',
host: 'apollodb.cxeokcheapqj.us-east-2.rds.amazonaws.com',
pass: 'xxxx'
}
class DB {
async dbSetup(where) {
let host = (where == "local") ? localHost : awsHost
this.db = new Sequelize(host.db, 'postgres', host.pass, {
host: host.host,
dialect: 'postgres',
logging: false,
pool: {
max: 5,
min: 0,
idle: 20000,
handleDisconnects: true
},
dialectOptions: {
requestTimeout: 100000
},
define: {
freezeTableName: true
}
})
this.User = this.db.define('users', {
firstName: Sequelize.STRING,
lastName: Sequelize.STRING,
addressNumber: Sequelize.INTEGER,
streetName: Sequelize.STRING,
city: Sequelize.STRING,
email: Sequelize.STRING,
})
try {
await this.db.authenticate()
console.log('Connected to DB')
} catch (err) {
console.error('Unable to connect to DB', err)
}
}
async select(id) {
let who = await this.User.findAll({ where: { id: id } })
return who.get({ plain: true })
}
async findFirst(name) {
let me = await this.User.findAll({ where: { firstName: name } })
return me[0].get({ plain: true })
}
async addUser(user) {
let me = await this.User.create(user)
return me.get({ plain: true })
}
async populate() {
await this.db.sync({ force: true })
try {
await this.User.bulkCreate(userData, { validate: true })
console.log('users created');
} catch (err) {
console.error('failed to create users')
console.error(err)
} finally {
}
}
async findAll() {
let users = await this.User.findAll({ raw: true })
return users
}
async close() {
this.db.close()
}
}
exports.DB = new DB()
serverless.yml
service: apollo-lambda
provider:
name: aws
stage: dev
region: us-east-2
runtime: nodejs10.x
# cfnRole: arn:aws:iam::237632220688:role/lambda-role
functions:
graphql:
# this is formatted as <FILENAME>.<HANDLER>
handler: server.graphqlHandler
vpc:
securityGroupIds:
- sg-a1e6f4c3
subnetIds:
- subnet-4a2a7830
- subnet-1469d358
- subnet-53b45038
events:
- http:
path: graphql
method: post
cors: true
- http:
path: graphql
method: get
cors: true
folder structure of zip
When AWS Lambda imports your file, the export isn't available yet. That's why it complains that your handler is not a function (because it is actually undefined at that time it is being imported).
Here are a couple of suggested solutions:
1. Use only apollo-server-lambda and use serverless-offline for local development. This way your handler code is exactly the same as what you have in Lambda.
const { ApolloServer: ApolloServerLambda } = require("apollo-server-lambda");
const { typeDefs, resolvers, connect } = require("./schema.js");
const server = new ApolloServerLambda({
typeDefs,
resolvers,
playground: true,
introspection: true,
cors: {
origin: "*",
credentials: true
},
context: ({ event, context }) => ({
headers: event.headers,
functionName: context.functionName,
event,
context
})
});
exports.graphqlHandler = server.createHandler();
2. Use apollo-server-lambda in your Lambda but use apollo-server in another file (e.g. local.js).. Then, you just use node local.js for local development. No need for that process.env.USERNAME check that you do at the end.
Found the problem. It is a bit embarrassing. But I post it in case others need this.
I was trying to connect to the DB as part of the initialization of the lambda app. Hoping that when the cold start or warm start happened, the variable with DB would be already holding the connection.
That is anti-pattern.
With apollo one has to reconnect to the DB on each request. that is in the resolver for the GraphQL one has to reconnect to the DB and then close it so that AWS can see there are no open connections and then close the Lambda function.
What threw me was this worked fine when running as ApolloServer and connecting to a local DB.

Serverless notifications with Cloud Functions for Firebase

I have used firebase chat notification cloud function but when
notification triggered I am getting this error in firebase function
console
Cannot read property 'current' of undefined
at exports.sendNotification.functions.database.ref.onWrite.event (/user_code/index.js:8:35)
Here is my function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/notifications/messages/{pushId}').onWrite(event => {
console.log('Push notification event triggered for testing');
console.log(event);
const message = event.data.current.val();
const senderUid = message.from;
const receiverUid = message.to;
console.log(receiverUid);
const promises = [];
if (senderUid === receiverUid) {
//if sender is receiver, don't send notification
promises.push(event.data.current.ref.remove());
return Promise.all(promises);
}
const getInstanceIdPromise = admin.database().ref(`/usersnew/${receiverUid}`).once('value');
const getSenderUidPromise = admin.auth().getUser(senderUid);
return Promise.all([getInstanceIdPromise, getSenderUidPromise]).then(results => {
const instanceId = results[0].val();
const sender = results[1];
console.log('notifying ' + receiverUid + ' about ' + message.body + ' from ' + senderUid);
const payload = {
notification: {
title: sender.displayName,
body: message.body,
icon: sender.photoURL
}
};
admin.messaging().sendToDevice(instanceId, payload)
.then(function (response) {
return console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
return console.log('This is the notify feature');
});
});
Does anyone know how to solve this?
When I log event it shows like below in console
{ before:
DataSnapshot {
app:
FirebaseApp {
firebaseInternals_: [Object],
services_: {},
isDeleted_: false,
name_: '__admin__',
options_: [Object],
INTERNAL: [Object] },
instance: 'https://teleport-24f52.firebaseio.com',
_path: '/notifications/messages/-LIlFNd2spo_V1rM-G-f',
_data: null },
after:
DataSnapshot {
app:
FirebaseApp {
firebaseInternals_: [Object],
services_: {},
isDeleted_: false,
name_: '__admin__',
options_: [Object],
INTERNAL: [Object] },
instance: 'https://teleport-24f52.firebaseio.com',
_path: '/notifications/messages/-LIlFNd2spo_V1rM-G-f',
_data:
{ body: 'abc',
dayTimestamp: 1532975400000,
from: 'q8gtwtwXqbV2DtpsrbYajFsWzSr2',
negatedTimestamp: -1533056068309,
timestamp: 1533056068309,
to: 'Cmpu7mbIENTYyoHZjCjZnbnBMbl2' } } }
10:22:44.625 PM
In your code, event.data.current should be event.after.val() , event.after.ref, etc...
There was a change of API in cloud functions 1.0.
Read:
https://firebase.google.com/docs/functions/database-events#reading_the_previous_value
https://firebase.google.com/docs/functions/beta-v1-diff
Maybe the problem is in the SDK version. From v1.0 things change a bit. Try updating SDK and follow these instructions for miragation:
https://firebase.google.com/docs/functions/beta-v1-diff

Resources