firebase functions datasnapshot is not working - node.js

I am trying to run the next code in firebase functions, and it gives an error of not recognizing datasnapshot. what can cause it?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.updateVotes = functions.database.ref('travels/{travelId}/locations/{locationId}/voting/users')
.onUpdate((userVotes, context) => {
var travelId = context.params.travelId;
var locationId = context.params.locationId
var sumVotes = 0;
userVotes.forEach((userVote) => {
sumVotes += userVote.val().userVote;
})
// var votes = userVotes.val();
// for (var vote in votes) {
// if (votes.hasOwnProperty(vote)) {
// sumVotes += votes[vote].userVote;
// }
// }
return admin.database()
.ref('travels/' + travelId + '/locations/' + locationId + '/voting')
.update({ votes: sumVotes })
})
The error is:
TypeError: userVotes.forEach is not a function at exports.updateVotes.functions.database.ref.onUpdate

onUpdate doesn't return a DataSnapshot it returns a Change object.
You need to select the after or before property - do you want the information before the write, or after the write (Usually it's after - this is the new data in Firebase).
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.updateVotes = functions.database.ref('travels/{travelId}/locations/{locationId}/voting/users')
.onUpdate((change, context) => {
var travelId = context.params.travelId;
var locationId = context.params.locationId
var sumVotes = 0;
var userVotes = change.after;
userVotes.forEach((userVote) => {
sumVotes += userVote.val().userVote;
})
return admin.database()
.ref('travels/' + travelId + '/locations/' + locationId + '/voting')
.update({ votes: sumVotes })
})

Related

Firestore GeoQuering in node.js returns null

I'm trying to create a geoquery in node.js from Firestore, but when using the example from the Firebase library (https://firebase.google.com/docs/firestore/solutions/geoqueries#query_geohashes), my code returns 'Cannot read properties of null (reading 'text')'.
I want to query for any nearby users in my 'availableDrivers' collection in firebase, based on my current latitude and longitude (hardcoded to a point less than 1km away from the geopoint stored in 'availableDrivers').
Attached is a photo of the database
What is wrong with my code:
import * as functions from 'firebase-functions';
import { catchErrors } from './helpers';
const admin = require('firebase-admin');
const db = admin.firestore();
import * as geofire from 'geofire-common';
export const createFindDriver = async (latitude: number, longitude: number) => {
// Find cities within 50km of location
const radiusInM = 50 * 1000;
const bounds = geofire.geohashQueryBounds([latitude, longitude],radiusInM);
const promises = [];
for (const b of bounds) {
const q = db.collection('availableDrivers')
.orderBy('geohash')
.startAt(b[0])
.endAt(b[1]);
promises.push(q.get());
}
Promise.all(promises).then((snapshots) => {
const matchingDocs = [];
for (const snap of snapshots) {
for (const doc of snap.docs) {
const lat = doc.get('driverLat');
const long = doc.get('driverLong');
const distanceInKm = geofire.distanceBetween([lat, long], [latitude, longitude]);
const distanceInM = distanceInKm * 1000;
if (distanceInM <= radiusInM) {
matchingDocs.push(doc.data());
}
}
}
return matchingDocs;
})
.then((matchingDocs) => {
console.log(matchingDocs);
})
.catch((error) => {
console.log(error);
});
}
export const findDriver = functions.region('europe-west2').https.onCall( async (data, context) => {
const latitude = data.latitude;
const longitude = data.longitude;
return catchErrors(createFindDriver(latitude, longitude));
});
Here is my function when it is called:
const findDriver = httpsCallable(functions, 'findDriver');
findDriver({
latitude: 51.513958,
longitude: -0.0858491,
}).then((result) => {
/** #type {any} */
const data = result.data;
const message = data.text;
console.log(data);
console.log(message);
}).catch((error) => {
console.log("error.message: " + error.message);
});
From a quick glance it seems that your createFindDriver returns an array of promises, so you need to handle that array on the result. Assuming that catchErrors doesn't do anything with the result, that'd be:
findDriver({
latitude: 51.513958,
longitude: -0.0858491,
}).then((result) => {
result.forEach((data) => {
const message = data.text;
console.log(data);
console.log(message);
});
}).catch((error) => {
console.log("error.message: " + error.message);
});

onUpdate function in firebase is not retrieving before state

I'm using onUpdate function to retrieve a before state, but before and after are returning the same value:
exports.subscriptionDowngradeHandler = async (change, context) => {
const before = change.before.data().product;
console.log("product before: ", before);
const after = change.after.data().product;
console.log("product after: ", after);
await change.after.ref.set({
before: before.id,
after: after.id
}, { merge: true });
}
Value before.data().product is expected to be the other id, but instead is the actual one as after.data().product
EDIT
index.js is just calling subscriptionDowngradeHandler as a handler for the function:
const authModule = require("./auth");
const admin = require("firebase-admin");
const creditsModule = require("./credits");
const functions = require("firebase-functions");
const subscriptionsModule = require("./subscriptions");
admin.initializeApp();
exports.subscriptionDowngrade =
functions.firestore
.document("/customers/{userId}/subscriptions/{pushId}")
.onUpdate(subscriptionsModule.subscriptionDowngradeHandler);
exports.subspriptionPeriodEnd = functions.firestore
.document("/customers/{userId}/subscriptions/{pushId}")
.onWrite(subscriptionsModule.subscriptionPeriodEndHandler);
subscriptions.js is as follows:
const admin = require("firebase-admin");
exports.subscriptionPeriodEndHandler = async (change, context) => {
const periodEndBefore = change.before.data().current_period_end;
const periodEndAfter = change.after.data().current_period_end;
console.log(periodEndBefore);
console.log(periodEndAfter);
}
exports.subscriptionDowngradeHandler = async (change, context) => {
const before = change.before.data().product;
const after = change.after.data().product;
await change.after.ref.set({
before: before.id,
after: after.id
}, { merge: true });
}
In both functions before and after shows exactly the same data.

Concat two PDFs in Firebase Cloud Functions with pdf-lib

I'm trying to merge 2 pdf files using pdf-lib (I got the example of code from the official site of pdf-lib). The goal is to trigger the cloud function when new file is uploaded to bucket. The function then collect urls of files to be merged in the same bucket with the new one. I am able to get urls but I have an error in pdf-lib. Maybe I'm importing it the wrong way. Because in example it is in ES6 syntax (import) but nodejs needs require. I'm new to backed and nodejs. So any help is highly appreciated.
const functions = require('firebase-functions');
const { Storage } = require('#google-cloud/storage');
const storage = new Storage();
const admin = require('firebase-admin');
admin.initializeApp();
const { PDFDocument } = require('pdf-lib');
const fetch = require('node-fetch');
exports.testCloudFunc = functions.storage.object().onFinalize(async object => {
const filePath = object.name;
const { Logging } = require('#google-cloud/logging');
console.log(`Logged: FILEPATH: ${filePath}`);
const id = filePath.split('/');
console.log(`Logged: ID: ${id[0]}/${id[1]}`);
const bucket = object.bucket;
console.log(`Logged: BUCKET: ${object.bucket}`);
async function listFilesByPrefix() {
const options = {
prefix: id[0] + '/' + id[1]
};
const [files] = await storage.bucket(bucket).getFiles(options);
const endFiles = files.filter(el => {
return (
el.name === id[0] + '/' + id[1] + '/' + 'invoiceReport.pdf' ||
el.name === id[0] + '/' + id[1] + '/' + 'POD.pdf' ||
el.name === id[0] + '/' + id[1] + '/' + 'rateConfirmation.pdf'
);
});
endFiles.forEach(el => console.log(el.name));
const promises = [];
for (let i = 0; i < endFiles.length; i++) {
console.log(endFiles[i].name);
promises.push(
endFiles[i].getSignedUrl({
action: 'read',
expires: '03-17-2025'
})
);
}
const urlsArray = await Promise.all(promises);
return urlsArray;
}
listFilesByPrefix()
.then(results => {
results.forEach(el => console.log(el));
copyPages(results[0], results[1]);
return results;
})
.catch(console.error);
});
async function copyPages(url1, url2) {
const firstDonorPdfBytes = await fetch(url1).then(res => res.arrayBuffer());
const secondDonorPdfBytes = await fetch(url2).then(res => res.arrayBuffer());
const firstDonorPdfDoc = await PDFDocument.load(firstDonorPdfBytes);
const secondDonorPdfDoc = await PDFDocument.load(secondDonorPdfBytes);
const pdfDoc = await PDFDocument.create();
const [firstDonorPage] = await pdfDoc.copyPages(firstDonorPdfDoc, [0]);
const [secondDonorPage] = await pdfDoc.copyPages(secondDonorPdfDoc, [742]);
pdfDoc.addPage(firstDonorPage);
pdfDoc.insertPage(0, secondDonorPage);
const pdfBytes = await pdfDoc.save();
}
But in firebase cloud console logs I'm getting this:
TypeError: Cannot read property 'node' of undefined
at PDFDocument.<anonymous> (/srv/node_modules/pdf-lib/cjs/api/PDFDocument.js:459:62)
at step (/srv/node_modules/tslib/tslib.js:136:27)
at Object.next (/srv/node_modules/tslib/tslib.js:117:57)
at fulfilled (/srv/node_modules/tslib/tslib.js:107:62)
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
I was facing the same problem
Make sure your files are public or generate a signed url
an example follows:
const options = {
prefix: 'notas', //folder name
};
const optionsBucket = {
version: 'v2',
action: 'read',
expires: Date.now() + 1000 * 60 * 9, // 9 minutes
};
const [files] = await storage.bucket('your-bucket-name').getFiles(options);
const mergedPdf = await PDFDocument.create();
for (let nota of files) {
let fileName = nota.name;
if (fileName.endsWith('.pdf')) {
const [url] = await storage
.bucket(bucketName)
.file(fileName)
.getSignedUrl(optionsBucket); //generate signed url
const arrayBuffer = await fetch(url).then(res => res.arrayBuffer());
const pdf = await PDFDocument.load(arrayBuffer);
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
copiedPages.forEach((page) => {
mergedPdf.addPage(page);
});
}
const mergedPdfFile = await mergedPdf.save();
const file = bucket.file(`folder/filename.pdf`);
await file.save(
mergedPdfFile
);
}

Getting document not a function

I am attempting to retrieve the boolean child (notificationsOn) of an object stored as a Firestore document to see if the rest of a function should be executed.
The overall function works to completion without this portion, but adding the portion from let threadDoc to the if statement presents a "threadDoc.get is not a function" error. I think my syntax is wrong but I don't know how, as a similar function works in a later part of the function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendDMNotification =functions.firestore.document('/dm_threads/{thread_id}/messages/{message_id}').onCreate((snapshot, context) => {
const newMessage = snapshot.data();
const senderName = newMessage.authorName;
const senderID = newMessage.authorUID;
const messageText = newMessage.message;
const recipientID = newMessage.recipientUID;
var notificationsOn = null;
let deviceTokenQuery = admin.firestore().collection(`/users/${recipientID}/device_tokens/`);
var idsToBeSorted = [senderID, recipientID];
idsToBeSorted.sort();
var threadID = idsToBeSorted[0] + idsToBeSorted[1];
console.log(recipientID);
console.log(threadID);
let threadDoc = admin.firestore().document(`users/${recipientID}/threads/${threadID}/`);
return threadDoc.get().then(doc => {
let notificationsOn = doc.data.notificationsOn;
console.log(notificationsOn);
if (notificationsOn !== false){
return deviceTokenQuery.get().then(querySnapshot => {
let tokenShapshot = querySnapshot.docs;
const notificationPromises = tokenShapshot.map(doc => {
let token_id = doc.data().tokenID;
const payload = {
data: {
title: senderName,
body: messageText,
senderID: senderID,
senderName: senderName
}
};
return admin.messaging().sendToDevice(token_id, payload).then(response => {
console.log("Notification sent: ", response);
})
.catch(error => {
console.log("Error sending message: ", error);
});
});
return Promise.all(notificationPromises);
});
}
return;
});
});
admin.firestore().document() was supposed to be admin.firestore().collection(...).doc(...)
This fixed my problem
I think you meant to say admin.firestore() instead of functions.firestore.

Error: read ECONNRESET when working with large data in Firebase Cloud functions

I perform the following task, during the registration of users for the first few months we did not save images of users in Firebase Cloud Storage and took a link that was received from Facebook. Now faced with the problem that some links to images have become expired. Because of this, I decided to make the cloud function and run it once as a script, so that it went through to users who have only one link to the image (which means that this is the first link received from facebook), take the facebook user id and request current profile image. I got a json file with the given users from Firebase, then I get links for each user separately, if the user is deleted then I process this error in a separate catch so that it does not stop the work of other promises. But after running this cloud function, I ran into this error because of this, for almost all users this operation was not successful. Even I increased the memory size in cloud function to 2 gigabytes. Please tell me how it can be fixed?
{ Error: read ECONNRESET
at exports._errnoException (util.js:1018:11)
at TLSWrap.onread (net.js:568:26) code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' }
My function
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const account_file = require('../account_file.json');
var FB = require('fb');
const path = require('path');
const imageDownloader = require('image-downloader');
const os = require('os');
const shortid = require('shortid');
const imageManager = require('../../lib/Images/image-manager.js');
module.exports = functions.https.onRequest((req, res) => {
const token = req.header('X-Auth-Token');
var errorsCount = 0;
return admin.auth().verifyIdToken(token)
.then(function(decodedToken) {
const adminID = decodedToken.uid;
console.log('adminID is', adminID);
const users = account_file['users'];
var fixPhotoPromises = [];
users.forEach(function(user) {
const userID = user['localId'];
const fixPhotoPromise = fixPhoto(userID).catch(error => {
console.log(error);
errorsCount += 1;
});
fixPhotoPromises.push(fixPhotoPromise);
});
return Promise.all(fixPhotoPromises);
}).then(results => {
console.log('results.length', results.length, 'errorsCount', errorsCount);
console.log('success all operations');
const successJSON = {};
successJSON["message"] = "Success operation";
return res.status(200).send(successJSON);
}).catch(error => {
console.log(error);
const errorJSON = {};
errorJSON["error"] = error;
return res.status(error.code).send(errorJSON);
});
});
function fixPhoto(userID) {
var authUser = {};
var filename = '';
return new Promise((resolve, reject) => {
return admin.auth().getUser(userID)
.then(userModel => {
const user = userModel.toJSON();
const facebookID = user['providerData'][0]['uid'];
const userID = user['uid'];
authUser = {'userID' : userID, 'facebookID' : facebookID};
const userImagesPromise = admin.database().ref()
.child('userImages')
.child(userID)
.once('value');
return Promise.all([userImagesPromise])
}).then(results => {
const userImagesSnap = results[0];
if (userImagesSnap.val() !== null && userImagesSnap.val() !== undefined) {
const userProfileImagesDict = userImagesSnap.val()['userProfileImages'];
const keys = Object.keys(userProfileImagesDict);
var userProfileImages = [];
keys.forEach(function(key){
const userProfileImage = userProfileImagesDict[key];
userProfileImages.push(userProfileImage);
});
if (userProfileImages.length > 1) {
const status = 'user has more than one image';
return resolve(status);
}
}
const facebookAppID = functions.config().facebook.appid;
const facebookAppSecret = functions.config().facebook.appsecret;
const facebookAccessPromise = FB.api('oauth/access_token', {
client_id: facebookAppID,
client_secret: facebookAppSecret,
grant_type: 'client_credentials'
});
return Promise.all([facebookAccessPromise]);
}).then(results => {
const facebookResult = results[0];
const facebookAccessToken = facebookResult['access_token'];
const profileImageURL = 'https://graph.facebook.com/' + authUser.facebookID + '/picture?width=9999&access_token=' + facebookAccessToken;
const shortID = shortid.generate() + shortid.generate() + shortid.generate();
filename = shortID + ".jpg";
const tempLocalFile = path.join(os.tmpdir(), filename);
const options = {
url: profileImageURL,
dest: tempLocalFile // Save to /path/to/dest/image.jpg
};
const imageDownloaderPromise = imageDownloader.image(options);
return Promise.all([imageDownloaderPromise])
}).then(results => {
const imageDownloaderResult = results[0];
const userID = authUser.userID;
const localImagePath = imageDownloaderResult['filename'];
const imageManagerPromise = imageManager.saveUserImageToCloudStorage(localImagePath, filename, userID);
return Promise.all([imageManagerPromise]);
}).then(results => {
const result = results[0];
return resolve(result);
}).catch(function(error) {
reject(error)
})
});
}
exports.saveUserImageToCloudStorage = function saveUserImageToCloudStorage(localImagePath, filename, userID) {
const bucketName = functions.config().googlecloud.defaultbacketname;
const bucket = gcs.bucket(bucketName);
const profileImagePath = path.normalize(path.join('userImages', userID, 'profileImages', filename));
const profileImageFile = bucket.file(profileImagePath);
return new Promise((resolve, reject) => {
bucket.upload(localImagePath, {destination: profileImagePath})
.then(() => {
const config = {
action: 'read',
expires: '03-01-2500'
};
const userRefPromise = admin.database().ref()
.child('users')
.child(userID)
.once('value');
return Promise.all([profileImageFile.getSignedUrl(config), userRefPromise])
}).then(function(results) {
const url = results[0][0];
const userSnap = results[1];
if (userSnap.val() === null || userSnap.val() === undefined) {
return resolve('user was deleted from database');
}
const userModel = userSnap.val();
const userCheckID = userModel['id'];
if (userCheckID !== userID) {
return reject("WARNING userCheckID !== userID");
}
// save to database
const userImagesRef = admin.database().ref().child('userImages')
.child(userID)
.child('userProfileImages')
.push();
const timeStamp = timestamp.now();
const imageModelID = userImagesRef.key;
const userImagesRefPromise = userImagesRef.update({
'path': url,
'id': imageModelID,
'fileName': filename,
'timeStamp': timeStamp
});
const userRef = admin.database().ref()
.child('users')
.child(userID)
.child('currentProfileImage');
const userRefPromise = userRef.update({
'path': url,
'id': imageModelID,
'fileName': filename,
'timeStamp': timeStamp
});
return Promise.all([userImagesRefPromise, userRefPromise]);
}).then(() => {
const successJSON = {};
successJSON["message"] = "Success operation";
return resolve(successJSON);
}).catch(function(error) {
return reject(error);
});
});
};
I added this code when init google cloud storage and I did not have this error anymore.
var gcs = require('#google-cloud/storage')({keyFilename: "service-account-credentials.json"});
gcs.interceptors.push({
request: function(reqOpts) {
reqOpts.forever = false
return reqOpts
}
});

Resources