Firebase V9 does not give error in catch when offline - firebase

I want to set state in catch even if user offline but firebase V9 setDoc does not give anything in catch when user offline
For Example: in Authentication, if the user offline firebase gives (network error) in catch but in firestore "add document" no message from catch...

This is by design thanks to Firestore's Offline Behaviour (queued up to the right spot, but I do recommend watching in full).
The promise will resolve once the server has acknowledged the request. If the server is currently unavailable, that request is cached within the SDK and attempted as soon as a connection is restored. During this window, the Promise will be kept in its pending state because that's the state its actually in - pending. While the promise may not resolve, all your local realtime listeners and such will still fire off and your app will function as normal - just offline.
Dealing with this behaviour is an exercise for the developer. One way to approach this would be to use Promise.race() to implement your own offline-handling logic.
As a quick & dirty example, here's a setDocWithTimeout implementation:
const setDocWithTimeout = (ref, data, options) => {
const timeoutMS = options && options.timeout || 10000;
const setDocPromise = setDoc(ref, data);
return Promise.race([
setDocPromise.then(() => ({ timeout: false })),
new Promise((resolve, reject) => setTimeout(resolve, timeoutMS, { timeout: true, promise: setDocPromise }));
]);
}
which you can apply using:
try {
const result = await setDocWithTimeout(doc(db, "cities", "new-city-2"), data);
if (result.timeout) {
// offline? (could be poor connection too)
console.log("Document added to Firestore queue");
// dismiss any blocking UIs/loading bubbles
// tell user will be written to server later
await result.promise; // wait for write to complete as before
}
// online! (or back online)
console.log("Document written successfully!");
} catch (err) {
console.error(`error found! ${err}`);
}
Alternatively where an error is thrown:
const setDocWithTimeoutError = (ref, data, options) => {
const timeoutMS = options && options.timeout || 10000;
const setDocPromise = setDoc(ref, data);
return Promise.race([
setDocPromise,
new Promise((_, reject) => setTimeout(reject, timeoutMS, new Error("timeout"));
]);
}
which you can apply using:
try {
await setDocWithTimeoutError(doc(db, "cities", "new-city-2"), data);
console.log("Document written successfully!");
} catch (err) {
console.error(`error found! ${err}`);
}

works on web v9, see
docs from v8.
import { onLog } from 'firebase/app';
onLog((e) => {
const { level, message } = e;
if (level === 'warn') {
console.log('connection interruption after intial load was success:', message);
}
if (level === 'error') {
console.log('no connection on inital load:', message);
}
});

Related

Firebase Realtime Database listeners not returning data

In our PWA iOS app (built with Outsystems) we are have an issue where the Firebase Realtime Database Javascript SDK .on() and .get() functions are not returning any data or an error. It seems to fail silently.
On first start up everything works perfectly. When the app is in a paused state for an undetermined amount of time, coming back to app and making the calls below do not return anything.
Before making the calls we check for connection and the status is true.
Here is the code:
var _setConversationOnValue = function (conversationKey, successCallback) {
try {
_firebaseRefCheckConnection.on('value', (snap) => {
if (snap.val() === true) {
console.log('connected'); //This is TRUE and hit.
} else {
console.log('not connected');
}
});
//Attempting a .get()
_refs.Conversation.child(conversationKey)
.get()
.then(function (snapshot) {
// NEVER GETS HERE
console.log('IN GET() Conversation Callback');
})
.catch(function (error) {
// NEVER GETS HERE
console.error(error);
});
//Attempting a .on()
return _refs.Conversation.child(conversationKey).on('value',
successCallback, //NEVER GETS HERE
function (error) {
//NEVER GETS HERE
console.error('Error Creating Lisenter');
console.error(error);
});
} catch (error) {
console.error('Error in _setConversationOnValue');
console.error(error);
}
};
Javascript SDK version is 8.6.1
We have tried this workaround:
if (localStorage) { // fix for API
localStorage.removeItem("firebase:previous_websocket_failure");
}
But, this doesn't work as the item is not in localStorage when the app resumes.

Vue Axios Interceptor Response Firebase 401 Token Expired/Refresh (undefined)

I'm using the following interceptors in a Vuejs v2 website to push a firebase token to my node backend. There in the backend, I detect/verify the token, pull some data using the uid from a database and then process any api calls.
Even though I am using the firebase onIdTokenChanged to automatically retrieve new ID tokens, sometimes, if the user is logged in, yet inactive for an hour, the token expires without refreshing. Now, this isn't a huge deal - I could check in the axios response interceptor and push them to a login page, but that seems annoying, if I can detect a 401 token expired, resend the axios call and have a refreshed token, the user won't even know it happened if they happen to interact with a component that requires data from an API call. So here is what I have:
main.js
Vue.prototype.$axios.interceptors.request.use(function (config) {
const token = store.getters.getSessionToken;
config.headers.Authorization = `Bearer ${token}`;
return config;
});
Vue.prototype.$axios.interceptors.response.use((response) => {
return response }, async function (error) {
let originalRequest = error.config
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
let user = auth.currentUser;
await store.dispatch("setUser", {user: user, refresh: true}).then(() => {
const token = store.getters.getSessionToken;
Vue.prototype.$axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
return Vue.prototype.$axios.request(originalRequest);
});
}
return Promise.reject(error); });
let app;
auth.onAuthStateChanged(async user => {
await store.dispatch("setUser", {user: user, refresh: false}).then(() => {
if (!app) {
app = new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')
}
})
.catch(error => {
console.log(error);
});
});
vuex
setUser({dispatch, commit}, {user, refresh}) {
return new Promise((resolve) => {
if(user)
{
user.getIdToken(refresh).then(token => {
commit('SET_SESSION_TOKEN', token);
this._vm.$axios.get('/api/user/session').then((response) => {
if(response.status === 200) {
commit('SET_SESSION_USER', response.data);
resolve(response);
}
})
.catch(error => {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: 'Server unavailable: '+error
});
resolve();
});
})
.catch(error => {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: 'Unable to verify auth token.'+error
});
resolve();
});
}
else
{
console.log('running logout');
commit('SET_SESSION_USER', null);
commit('SET_SESSION_TOKEN', null);
resolve();
}
})
},
I am setting the token in vuex and then using it in the interceptors for all API calls. So the issue I am seeing with this code is, I'm making an API call with an expired token to the backend. This returns a 401 and the axios response interceptor picks it up and goes through the process of refreshing the firebase token. This then makes a new API call with the same config as the original to the backend with the updated token and returns it to the original API call (below).
This all seems to work, and I can see in dev tools/network, the response from the API call is sending back the correct data. However, it seems to be falling into the catch of the following api call/code. I get an "undefined" when trying to load the form field with response.data.server, for example. This page loads everything normally if I refresh the page (again, as it should with the normal token/loading process), so I know there aren't loading issues.
vue component (loads smtp settings into the page)
getSMTPSettings: async function() {
await this.$axios.get('/api/smtp')
.then((response) => {
this.form.server = response.data.server;
this.form.port = response.data.port;
this.form.authemail = response.data.authemail;
this.form.authpassword = response.data.authpassword;
this.form.sendemail = response.data.sendemail;
this.form.testemail = response.data.testemail;
this.form.protocol = response.data.protocol;
})
.catch(error => {
console.log(error);
});
},
I have been looking at this for a few days and I can't figure out why it won't load it. The data seems to be there. Is the timing of what I'm doing causing me issues? It doesn't appear to be a CORS problem, I am not getting any errors there.
Your main issue is mixing async / await with .then(). Your response interceptor isn't returning the next response because you've wrapped that part in then() without returning the outer promise.
Keep things simple with async / await everywhere.
Also, setting common headers defeats the point in using interceptors. You've already got a request interceptor, let it do its job
// wait for this to complete
await store.dispatch("setUser", { user, refresh: true })
// your token is now in the store and can be used by the request interceptor
// re-run the original request
return Vue.prototype.$axios.request(originalRequest)
Your store action also falls into the explicit promise construction antipattern and can be simplified
async setUser({ dispatch, commit }, { user, refresh }) {
if(user) {
try {
const token = await user.getIdToken(refresh);
commit('SET_SESSION_TOKEN', token);
try {
const { data } = await this._vm.$axios.get('/api/user/session');
commit('SET_SESSION_USER', data);
} catch (err) {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: `Server unavailable: ${err.response?.data ?? err.message}`
})
}
} catch (err) {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: `Unable to verify auth token. ${error}`
})
}
} else {
console.log('running logout');
commit('SET_SESSION_USER', null);
commit('SET_SESSION_TOKEN', null);
}
}

#firebase/firestore: Firestore (8.2.2): Connection webchannel transport errored: [object, Object] when trying to cache in React Native

I am trying to cache items that get pulled from a Firebase database, I'm using React Native.
Getting and posting data works fine, the problem happens when trying to cache the data.
When internet gets turned off it shows the error:
#firebase/firestore: Firestore (8.2.2): Connection webchannel transport errored: [object, Object]'
when trying to cache.
For caching I use the "#react-native-async-storage/async-storage": "~1.15.0", library.
In the code below I get data and store it. When the internet is turned off the product stays visible for a few seconds then it throws the error and disappears.
The intended behaviour for this code is that the item stays on screen even when internet is off.
How do I fix this problem? Or is there a better way to sort out caching?
async function getSelfMadeItems() {
let list = [];
if (getIsConnected === true) {
let snapshot = await firestore
.collection("SelfMadeProducts")
.where("UserUID", "==", user.uid)
.orderBy("MadeOn", "asc")
.limit(5)
.get()
.then(async (querySnapshot) => {
querySnapshot.forEach((doc) => {
list.push({
MadeOn: doc.data().MadeOn.toDate().toDateString(),
//ImageUri: doc.data().ImageUri,
id: doc.id,
});
});
try {
const jsonValue = JSON.stringify(list);
if (!querySnapshot.empty) {
await AsyncStorage.setItem("#SelfMade", jsonValue);
}
} catch (e) {
console.log(e);
}
setproductsState(list);
});
} else {
try {
const ItemValue = await AsyncStorage.getItem("#SelfMade");
if (ItemValue !== null) {
const jsonValue = await AsyncStorage.getItem("#SelfMade");
console.log("Json", JSON.parse(jsonValue));
}
jsonValue != null ? setproductsState(JSON.parse(jsonValue)) : null;
} catch (e) {
console.log(e);
}
}```

Setting up a firebase cloud function call

I am trying to set up cloud functions with firebase and I am having a slightly difficult time getting it set up.
I want to set up a function that gets called by an HTTP request. The function would take the information provided, double-check if those values are indeed the same values as the ones found in my firestorm
and then execute some Javascript code before responding; this is my code:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require("firebase-functions");
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
// [START trigger]
exports.buyCrypto = functions.https.onRequest((request, res) =>
{
// [END trigger]
// [START sendError]
// Forbidding PUT requests.
if (request.method === 'PUT') {
return res.status(403).send('Forbidden!');
}
// [END sendError]
// [START readQueryParam]
const uid = request.body.uid
const crypto = request.body.crypto
const amount = request.body.amount
const docRef = admin.firestore().collection("users").doc(uid);
docRef.get().then((doc) => {
if (doc.exists) {
if(crypto === "BTC")
{
if(doc.data.btc <= amount)
{
//execute buy
return res.status(200).send("Sucess");
}
}
if(crypto === "ETH")
{
if(doc.data.btc <= amount)
{
//execute buy
return res.status(200).send("Sucess");
}
}
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
// Push the new message into Firestore using the Firebase Admin SDK.
//const writeResult = await admin.firestore().collection('messages').add({original: original});
// Send back a message that we've successfully written the message
// [START sendResponse]
const formattedResponse = "IDK"
return res.status(403).send("Failed");
// [END sendResponse]
});
Unfortunatly I cannot seem to find a great deal of documentation for firebase functions and when I try to test it with the emulator through a web browser it goes into infinite loading and does not display an error message so I am finding it impossible to debug anything.
Thank you for your time
You are calling return res.status(403).send("Failed"); outside of the then() block, so this line will be called before the asynchronous call to the get() method is completed and the Promise returned by this method is fulfilled. Result: your Cloud Function always sends back an error to its caller.
In addition, you do doc.data.btc instead of doc.data().btc. See the doc for the DocumentSnapshot, data() is a method.
Also, note that you don't need to use return in an HTTPS Cloud Function. Just send back a response with res.redirect(), res.send(), or res.end(). You may watch this video: https://www.youtube.com/watch?v=7IkUgCLr5oA.
The following should therefore do the trick:
exports.buyCrypto = functions.https.onRequest((request, res) => {
if (request.method === 'PUT') {
return res.status(403).send('Forbidden!');
}
const uid = request.body.uid
const crypto = request.body.crypto
const amount = request.body.amount
const docRef = admin.firestore().collection("users").doc(uid);
docRef.get().then((doc) => {
if (doc.exists) {
if (crypto === "BTC") {
if (doc.data().btc <= amount) {
//execute buy
res.status(200).send("Success");
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else if (crypto === "ETH") {
if (doc.data.btc <= amount) {
//execute buy
return res.status(200).send("Success");
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else {
console.log("No such document!");
// send a 200 response or throw an error res.status(200).send("....");
}
}).catch((error) => {
console.log("Error getting document:", error);
res.status(500).send(error);
});
});

Google Translate API and Firebase Firestore are killing each other

We're trying to write a Google Cloud Function that gets a translation from Google Translate API, and then write the results to our Firebase Firestore database. Each works alone, but together nothing works. In other words, we can get a translation from Google Translate. We can write data to Firestore. But if we try to do both, we don't get a translation back from Google Translate, and nothing is written to Firebase. We get no error messages. We've tried the code with async await and with promises. Here's the code with promises:
exports.Google_EStranslateEN = functions.firestore.document('Users/{userID}/Spanish/Translation_Request').onUpdate((change, context) => {
if (change.after.data().word != undefined) {
const {Translate} = require('#google-cloud/translate');
const projectId = 'myProject-cd99d';
const translate = new Translate({
projectId: projectId,
});
// The text to translate
const text = change.after.data().word;
// The target language
const target = 'en';
let translationArray = []; // clear translation array
translate.translate(text, target)
.then(results => {
translation = results[0];
translationArray.push(translation);
try {
// write translation to dictionary
admin.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(text).collection('Translations').doc('English').set({
'translationArray': translationArray,
'language': 'en',
'longLanguage': 'English'
})
.then(function() {
console.log("Translation written");
})
.catch(function(error) {
console.error(error);
});
} catch (error) {
console.error(error);
}
})
.catch(error => {
console.error('ERROR:', error);
});
}
});
Here's the same code with async await:
exports.Google_EStranslateEN = functions.firestore.document('Users/{userID}/Spanish/Translation_Request').onUpdate((change, context) => { // triggers when browser writes a request word to the database
if (change.after.data().word != undefined) {
async function getTranslation() {
try {
let translationArray = []; // clear translation array
const {Translate} = require('#google-cloud/translate');
const projectId = 'myProject-cd99d';
const translate = new Translate({
projectId: projectId,
});
// The text to translate
const text = change.after.data().word;
const options = {
to: 'en',
from: 'es',
format: 'text'
}
let [translations] = await translate.translate(text, options);
translations = Array.isArray(translations) ? translations : [translations]; // If translations is an array, leave it alone; if not, put it in an array
translationArray.push(translations[0]);
await admin.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(text).collection('Translations').doc('English').set({
'translationArray': translationArray,
'language': 'en',
'longLanguage': 'English'
})
.then(function() {
console.log("Translation written");
})
.catch(function(error) {
console.error(error);
});
// };
} catch (error) {
console.error(error);
}
} // close getTranslation
getTranslation();
}
});
You're not returning a promise that's resolved when all the async work is complete. If you don't do that, Cloud Functions assumes that all your work is complete, and will clamp down on all resources, and any pending work will be shut down.
The promise returned by translate.translate().then().catch() is being ignored. Your nested call to admin.firestore()...set() has a similar problem. It's not sufficient to just call then() and catch() on every promise because then() and catch() both return yet another promise.
You're also unnecessarily mixing usage of try/catch with catch() on the promise. You don't need both strategies for error handling, just one or the other.
When you used await in your second example, you forced JavaScript to pause until the async work represented by the promise returned by set() was complete. This allowed your function to return only after all the work was finished, which is why it worked correctly.
You might be helped by watching my video series on use of promises and async/await in Cloud Functions. Proper handling of promises is crucial to creating a correctly working function.

Resources