Firebase admin isn't writing to the database.
I am instantiating the database:
var db = admin.database();
Then setting up a reference to the table I want:
var systemsRef = db.ref("systems/");
I then have a function to check if the 'system', (an encrypted hardware id), exists.
function isSystemRegistered(id){
var isTrue;
systemsRef.once('value', function(snapshot) {
isTrue = (snapshot.hasChild(id))? true : false;
});
return isTrue;
}
Which, as of yet returns false; which is true, because it doesn't exist yet. If the system doesn't exist, it writes the data.
const sysID = getSysID();
var sys.info.name = generateUniqueSystemName();
if(isSystemRegistered(sysID){
console.log("system is already registered!");
} else {
msystemsRef.set({
sysID : sys.info.name
}, function(error){
console.log('There was an error while attempting to write to database: ' + error);
});
});
}
I've experimented, and temporarily made my prototype database fully public for a few minutes, just to be sure my rules weren't the issue... They weren't: still no bueno. No writes to the database... and no errors.
I tried a different set, just be sure:
msystemsRef.set("I'm writing data", function(error) {
if (error) {
alert("Data could not be saved." + error);
} else {
alert("Data saved successfully.");
}
});
Again, I'm using an admin account, with public rules, so I should see a now I'm writing data table, just below root. Nothing...
So I switched tactics and attempted to push to the database with the canned tutorial, with my database still fully public:
systemsRef.push({sysID : sys.info.name});
And nothing... Want am I missing?
Make sure the credentials and databaseURL are correct.
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: databaseURL
});
Check if they're matching - credential from one app and databaseURL from another existing app could produce such result.
If you are not loading credential's data from file but from somewhere else, make sure it's not modified - I had an issue with newlines in private key when putting the credential's data to shell variable.
In isSystemRegistered you're returning the synchronized value of isTrue which is undefined.
You should return the promise of the .once('value') method and in the calling method attach a then() to check if it exists.
You can also use the snapshot.exists() to check on a reference for existence.
Edit:
Suggested edit:
var systemRef = admin.database('system');
function isSystemRegistered(id) {
return systemRef.child(id).once('value')
.then(function (snap) {
return snap.exists();
});
}
function writeData(aSystem) {
var sysId = generateUniqueSystemName();
return systemRef.child(sysId)
.once('value')
.then(function (snapshot) {
if (!snapshot.exists()) {
return systemRef.child(sysId).update(aSystem);
}
// Here `sysId === snapshot.key`
return systemRef.child(sysId).set(aSystem);
})
.catch(function (err) {
console.error(err);
});
}
Running on Raspberry Pi raises some more questions.
How does it connect to the internet?
How fast is the connection?
What NodeJS version do you run?
Did this run successfully on your PC?
Same issue here. I eventually realised that the database went offline before the command was even sent to firebase. This made sense because there was no error, since it never even sent the request.
Eg. .set({blah: 123}) does not immediately transmit to the server. Instead, something is placed on the node event queue to execute. If you let the database go offline too soon, it won't process the queue.
Perhaps (like me) you're calling admin.database().goOffline(); at the end of the script? If so, you may just need to defer or delay the offline method until after the transmission.
// (TYPESCRIPT)
import * as admin from 'firebase-admin';
let app = admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "https://blahblah.firebaseio.com/",
});
admin.database().ref().push({ something: 123 }).then(() => {
console.log("push complete");
});
// delay before going offline
setTimeout(() => {
admin.database().goOffline();
process.abort();
}, 2000);
Related
I have this end point that creates a new server in my database, when I say server, it's just a server name. We have several gaming servers, so, we want to manage them from a website,
const createServer = asyncHandler(async (req, res) => {
const { gameName, serverName } = req.body;
const serverExist = await Server.findOne({ serverName });
if (!gameName || !serverName) {
res.status(400);
throw new Error("Please add game name / server name");
}
if (!serverExist?.isHidden) {
// if i set it to just serverExists it works
res.status(401);
throw new Error("Server exist");
}
//Get user using the id in the JWT
const user = await User.findById(req.user.id);
if (!user) {
res.status(401);
throw new Error("User not found");
}
const server = {
gameName,
serverName,
user: req.user.id,
};
await Server.create(server);
res.status(201).json(server);
});
Now, I'm checking if the server already exists in the database with serverExist, it returns all the document about that server, all works good. It has a property called isHidden, the time I set it in the if statement, the server is not able to return any response. if I set if statement to only if(serverExist), it does work, but I need it to work if the server was set to hidden so that a user could re-create it. I don't want to delete as it will be needed for later.
I checked all the returns and all seems ok, even the server object at the end does have the information. The problem is happening when I'm calling the create method. Don't know why adding serverExist.isHidden makes it unable to create the document!
You've got a little logic problem.
Consider these states when evaluating !serverExist?.isHidden...
State
!serverExist?.isHidden
No record exists
true ⚠️
Record exists with isHidden: true
false
Record exists with isHidden: false
true
I would instead include the isHidden parameter in your query and use the Server.exists() method
const serverExists = await Server.exists({ serverName, isHidden: true });
if (serverExists) {
// Server exists and is not hidden
res.status(409); // 409 Conflict is a better status than 401 Unauthorized
throw new Error("Server exists");
}
Firebase cloud functions logs "Error: 1 CANCELLED: Call cancelled" sometimes. am trying to send the push notifications one day before the timestamp, everything works till getting the fcm tokens but not able to send notification here is my code.
const NotificationLive_dayBefore = async () => {
try {
const path = models.payments.firebasePath;
let imageURL = null;
let tokens = [];
let subscribedUsers = await getSubscribedUsers().catch(e => { console.log(e) });
if (subscribedUsers && subscribedUsers.length > 0) {
for(const subscriber of subscribedUsers){
const userDoc = db
.collection('Payments').doc('v1').collection('users')
.doc(subscriber)
.collection('subscriptions').where('deleted_at', '==', null)
.get() // <------------here is the error according to logs
.then(async (snapshot) => {
if (!snapshot.empty) {
console.log('snapshot.empty', snapshot.empty);
for(const doc of snapshot.docs ){
// let friend_doc_id = doc.data().friend_doc_id
console.log("Friend id", doc.id);
if (doc.exists) {
let oneDay = new Date().getTime() + (24 * 60 * 60 * 1000);
let oneDayMinus1 = new Date().getTime() + (23 * 60 * 60 * 1000);
console.log(oneDayMinus1);
console.log(oneDay);
try {
let liveLesson = await db
.collection('Lessons').doc('v1').collection('friends')
.doc(doc.id)
.collection('live')
.where('start_time', '>', new Date(oneDayMinus1))
.where('start_time', '<', new Date(oneDay))
.where('deleted_at', '==', null)
.get();
for( const liveSnap of liveLesson.docs){
console.log("liveSnapid", '=>', liveSnap.data());
console.log('Private lesson exists', liveSnap.exists);
if (liveSnap.exists) {
// time is equal send notification
console.log("Subscriber ID", subscriber);
const Users = db
.collection(models.notification.firebasePath)
.where('deleted_at', '==', null)
.where('__name__', '==', subscriber)
.get()
.then(async (UserSnapshot) => {
for( const userdoc of UserSnapshot.docs){
const userdocument = userdoc.data();
if (userdocument.fcm_token) {
tokens = userdocument.fcm_token;
}
console.log('tokens',tokens);
if (tokens.length>0) {
try {
let live_lessons_images = await db
.collection('Accounts').doc('v1').collection('friends')
.doc(doc.id)
.get();
if (!live_lessons_images.data().image_url) {
imageURL = null;
console.log("Image not found");
} else {
imageURL = live_lessons_images.data().image_url;
}
} catch (error) {
console.log('That did not go well.', error)
}
console.log("notification sent live lesson 24 hr before");
await sendNotificationNew(
"test",
test notification,
imageURL,
tokens
).catch(error => { console.error("promise 1", error) });; //send notification to users as a remainder for live lesson before one day
}
}
}).catch(error => { console.error("promise 1", error) });
}
}
} catch (error) {
console.log('That did not go well.', error)
}
} else {
console.log("friend_doc_id is not present");
}
}
}
}).catch(error => { console.error("promise error", error) });
} //end foreach of subscribed user
}
return Promise.all('success');
}
catch (err) {
// Handle error here
// This will return error from api
console.log("Exceptions: ", err);
}}
and my sendNotificationNew function is
const sendNotificationNew = async (title, body, image, tokens) => {
console.log("title", title);
if (tokens.length > 0) {
if(tokens.length>1){
tokens = tokens[tokens.length-1];
}
try {
let message = {
notification: {
title: title,
body: body
},
token: tokens
};
if (image) {
message.notification.image = image;
}
admin.messaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
console.log('Error sending message:', tokens);
});
} catch (err) {
console.log(err);
}
} else {
console.log("Token not available");
}
};
i was able to see the logs printing "title" of the notification, but not "Successfully sent message" or "Error sending message:".
Error message i got
Error: 1 CANCELLED: Call cancelled
at Object.callErrorFromStatus (/workspace/node_modules/#grpc/grpc-js/build/src/call.js:31)
at Object.onReceiveStatus (/workspace/node_modules/#grpc/grpc-js/build/src/client.js:327)
at Object.onReceiveStatus (/workspace/node_modules/#grpc/grpc-js/build/src/client-interceptors.js:299)
at (/workspace/node_modules/#grpc/grpc-js/build/src/call-stream.js:145)
at processTicksAndRejections (internal/process/task_queues.js:79)
And this
Error: 9 FAILED_PRECONDITION: The requested snapshot version is too old.
at Object.callErrorFromStatus (/workspace/node_modules/#grpc/grpc-js/build/src/call.js:31)
at Object.onReceiveStatus (/workspace/node_modules/#grpc/grpc-js/build/src/client.js:327)
at Object.onReceiveStatus (/workspace/node_modules/#grpc/grpc-js/build/src/client-interceptors.js:299)
at (/workspace/node_modules/#grpc/grpc-js/build/src/call-stream.js:145)
at processTicksAndRejections (internal/process/task_queues.js:79)
3rd Edit: Finally I've found the solution to this problem. A same error occurred in 2 cloud functions and I solved both of them by following the same process.
I'm not deleting the previous wrong answers, which didn't work for me, so that you also could know what won't work.
This error occurs because the Firestore has hit a limit.
I was trying to do lots of writes and updates in Firestore in parallel.
Example: Here all the functions are called all together. This is very fast as all the tasks are done in parallel. And recommended by Firebase.
But this might hit a limit in firestore and give error.
exports.botsCompletingLectures = functions.region('asia-south1')
.database.ref('triggerCloudFunctions/botsCompletingLectures')
.onUpdate(async(change, context)=>{
const promises = [];
promises.push(doSomeTaskInFirestore());
promises.push(doAnotherTaskInFirestore());
promises.push(doSomeMoreTaskInFirestore());
function doSomeTaskInFirestore(){
//Write to lots of documents in a collection
}
async function doAnotherTaskInFirestore(){
//Update lots of documents in firestore
}
async function doSomeMoreTaskInFirestore(){
//Do more tasks in firestore
}
return Promise.all(promises);
});
Solution: Here only one task will execute at a time and so it will take little more time. But will not give any error.
exports.botsCompletingLectures = functions.region('asia-south1')
.database.ref('triggerCloudFunctions/botsCompletingLectures')
.onUpdate(async(change, context)=>{
await doSomeTaskInFirestore()
await doAnotherTaskInFirestore()
return doSomeMoreTaskInFirestore()
function doSomeTaskInFirestore(){
//Write to lots of documents in a collection
}
async function doAnotherTaskInFirestore(){
//Update lots of documents in firestore
}
async function doSomeMoreTaskInFirestore(){
//Do more tasks in firestore
}
});
2nd Edit: The Cloud Function worked well for 4-5 days and then started giving errors again.
So this time I've given up on trying to fix it and instead enabled Retry on failure in Cloud Functions.
In my case as the Cloud Function is running correctly for some days and gives error on other days, due to some temperory problems, like network issue or cold start, so I can enable it here.
We should not enable it, if the error is permanent for eg a Bug in the code, else the function will keep on retrying for 7 days.
You can learn about Enabling Retry in Cloud Functions from this video.
https://www.youtube.com/watch?v=Pwsy8XR7HNE
1st Edit: The error appeared again the next day
So after reading and searching about it, I found out this problem occurs because of Cold Starting of a function after a long time and some Network problem and memory leak and most of the people (including me), who are getting this error, are getting it in PubSub Cloud Function and while doing some tasks in Firestore.
So I used a workaround. I don't know if it is recommended or not, but I'm tired of these errors, so I just did it.
I created a PubSub Cloud Function that updates a field value in Realtime Database. And this update in the field will trigger another function, that will do the task in firestore.
exports.triggerTheMainFunction = functions.pubsub.schedule('40 11 * * *').onRun(async(context)=> {
return admin.database().ref()
.child('triggerOtherFunction')
.child('doSomeTaskInFirestore')
.set(admin.database.ServerValue.increment(1))
.catch((error)=>{
console.log('Error incrementing the value', error);
});
});
And converted the Actual Function for Firestore from PubSub to Event Triggered. And since then I'm not getting any errors.
exports.doSomeTaskInCloudFirestore = functions
.database.ref('triggerOtherFunction/doSomeTaskInFirestore')
.onUpdate(async(change, context)=>{
//Do the task that was needed to be done in firestore.
});
If I get any errors in future, then I'll update this answer.
First Answer
I also got a similar error. I don't know what was causing the problem. But I solved it by installing the latest version.
So first I saved a copy of index.js on desktop and reinstalled everything.
I was using NodeJS 14 version. So I uninstalled it from control panel and downloaded the nodeJS 16 version from the nodejs website. And installed it.
ran in terminal.
npm install -g npm
then
npm install npm#latest -g
Then
firebase init
then
npm install -g firebase-tools
And then redeployed the same cloud function, without making any changes. And test run the function. And the error disappeared.
I resolved this issue by adding indexes in the firestore and proper catch blocks to all the promises to avoid unhandled rejection errors.
So I have designed a basic Publisher-Subscriber model using rhea in JS that takes an API request for saving data in DB and then publishes it to a queue.
From there a subscriber(code added below) picks it up and tries to save it in a DB. Now my issue is that this DB instance goes through a lot of changes during development period and can result in errors during insert operations.
So now when the subscriber tries to push to this DB and it results in an error, the data is lost since it was dequeued. I'm a total novice in JS so is there a way to make sure that a message isn't dequeued unless we are sure that it is saved properly without having to publish it again on error?
The code for my subscriber:
const Receiver = require("rhea");
const config = {
PORT: 5672,
host: "localhost"
};
let receiveClient;
function connectReceiver() {
const receiverConnection = Receiver.connect(config);
const receiver = receiverConnection.open_receiver("send_message");
receiver.on("connection_open", function () {
console.log("Subscriber connected through AMQP");
});
receiver.on("error", function (err) {
console.log("Error with Subscriber:", err);
});
receiver.on("message", function (element) {
if (element.message.body === 'detach') {
element.receiver.detach();
}
else if (element.message.body === 'close') {
element.receiver.close();
}
else {
//save in DB
}
}
receiveClient = receiver;
return receiveClient;
}
You can use code like this to explicitly accept the message or release it back to the sender:
try {
save_in_db(event.message);
event.delivery.accept();
} catch {
event.delivery.release();
}
See the delivery docs for more info.
I'm trying to return values from Parse.Cloud.define functions in cloud code using Parse. I'm using Parse 3.0.0 and I can get it to return values from simple cloud code defines but not complex ones.
I'm coding client side iOS in Objective-C.
Here's the cloud code function (I don't care if this is unsafe, I'm not changing it)
Parse.Cloud.define("doStuff", (request) => {
const query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username);
query.first({useMasterKey:true})
.then((results) => {
Parse.User.requestPasswordReset(results.get("email"))
.then(() => {
return "good work";
}).catch((error) => {
});
})
.catch((error) => {
});
});
This works just fine, it sends the email to the user as expected by using the User's username field.
In iOS I'm calling it like this:
[PFCloud callFunctionInBackground:#"doStuff" withParameters:#{#"username" : cleanEntryData} block:^(NSString * object, NSError * error) {
if (!error) {
NSLog(#"success %#", object);
} else {
NSLog(#"error %#", error);
}
}];
This call works in iOS and the email is successfully sent to the user for password reset. However, here's the problem.
when I call
NSLog(#"success %#", object);
the value in Xcode debug window is
success (null)
I expect it to be
success good work
When I a simple cloud code define like so:
Parse.Cloud.define("testing", (req) => {
return "very good";
});
with iOS like so:
[PFCloud callFunctionInBackground:#"testing" withParameters:#{#"nothing" : #"nothing"} block:^(NSString * object, NSError * error) {
if (!error) {
NSLog(#"success %#", object);
} else {
}
}];
then i get the result in Xcode debugger that i'm looking for
success very good
i don't know why the "doStuff" cloud code define is not returning the string "good work" when the function is clearly executing and sending the email as it should. I've read both the Parse 3.0.0 and JS 2.0.0 guides and they aren't very descriptive on how this should work with Parse Cloud defines. I'm not a JS coder, I only code in mobile, so I'm probably doing something stupid. Any help would be great. Thanks.
There's no issue in your iOS code, the issue lies in the cloud code, so you'll need to change the cloud code, since it's not necessarily unsafe, but rather flawed.
The issue is that you are nesting Promises inside each other instead of chaining them together, hence the single nested return value is lost in the several nested layers.
Parse.Cloud.define("doStuff", (request) => {
const query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username);
return query.first({useMasterKey:true})
.then((results) => {
return Parse.User.requestPasswordReset(results.get("email"));
}).then(() => {
return "good work";
})
});
Trying to wrap my head around what this error really means, but it's saying Anonymous caller does not have storage.objects.get access to... when I try to get fileData from a bucket file.
app.get('/api/videos', (req, res) => {
const storageBucket = storageClient.bucket(config.video_bucket);
storageBucket.getFiles(function(err, files) {
if (!err) {
let fileArray = [];
files.forEach(function(file) {
const videoAnnotationBucket = storageClient.bucket(config.video_json_bucket);
const videoAnnotationFilename = (file.metadata.name).replace('/', '').replace('.', '') + '.json';
const annotationFile = videoAnnotationBucket.file(videoAnnotationFilename);
// GET ANNONATIONS FOR EACH FILE
annotationFile.get(function(error, fileData) {
if (error) {
console.log('error getting file', error);
}
else {
const remoteJsonUrl = fileData.metadata.mediaLink;
// console.log(fileData.metadata);
request({
url: remoteJsonUrl,
json: true
},
function(jsonReadErr, jsonResp, body) {
console.log('logging body:');
console.log(body);
The error is occuring on the callback, and I'm reading the error via console.log(body) which gives me the error message I stated above.
What's weird is it's saying I'm anonymous when I did gcloud auth login as well as I'm providing creds when I declare storageBucket as such:
const storageClient = storage({
credentials: {
"client_email": "clientEmail",
"private_key": "privateKey",
},
projectId: "projectId"
});
So right off the bar, to avoid any "did you set this" questions, no I am not actually supplying those values I omitted the real values, and we use them elsewhere so I know they are correct.
My question is, what does Anonymous caller mean? And how can I fix it? How is it thinking I am anonymous when I did all the (seemingly) necessary things to use the API?
It's possible that you need to explicitly authenticate within request. This SO thread looks related.
Let us know how explicitly authenticating worked out!