I am using Angularfire2 along with Ionic2 and looking for a way to catch errors on FirebaseListObservable subscribe() function.
I am subscribing to fbData which is a FirebaseListObservable, and when I switch my browser/device offline, the (error) function is never called. I don't understand why.
My objective is to get data from the localStorage if the user is offline or firebase is not reachable.
Here is my simplified code:
export class MyService {
fbData: FirebaseListObservable<any[]>;
constructor(private af: AngularFire) {
this.data = af.database.list('/data', { preserveSnapshot: true });
}
updateData() {
return new Promise<any[]>((resolve, reject) => {
this.fbData.subscribe(
(snapshots) => {
resolve(snapshot.val());
},
(error) => console.log('error: ', error) // NEVER CALLED
);
});
}
Firebase will not throw an error if the network is unreachable. The SDK silently waits for the socket connection to build up. The subscription can only error out if the security rules denies read access to the query.
If you want to implement such a timeout scheme, you'll have to do everything manually: including starting a timeout when you start a query and cancelling it if the query returns a result in time.
Related
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.
I'm trying to implement a messaging application using Firebase Firestore and Firebase Cloud Functions.
In essence, chat messages are stored as individual documents in a subcollection. At first, I implemented this as directly adding a document from the client and listening on the collection and updating the clients when a change happens but later I decided to switch to using Cloud functions so that I can add some functionality that's better done on the server side(filtering etc.).
So I created a function for sending messages, which creates the documents on behalf of the users when the users call the function from the app(i.e. tap the send button).
The function worked and I was able to monitor the processes through the logs. Unfortunately, the functions began to die out without error, the console was reporting that the functions are executed successfully and it usually took less than a second to execute.
I suspect that it has something to do with the promises that probably continue to run but this is the same code that was working but failing today.
If I try a few more times, the functions seem to be working again. Do I need to keep the functions "warm"? Are cloud functions not reliable enough to handle this kind of tasks? When I say my user that a message is sent, I need to be able to confirm that it is sent and communicate it with the users if it failed.
It's hard to debug the issue because no errors are thrown(not even info message, it's just as if didn't happen), it just says that the function successfully finished execution and nothing happened.
Am I missing something here? Thank you.
exports.sendMessage = functions.https.onCall((data, context) => {
if (context.auth.uid == undefined) {
console.warn("SEND MESSAGE: USER NOT SIGNED IN");
return;
}
console.log("Sending message:", data)
const matchId = data["matchId"];
const message = data["message"]
const uid = context.auth.uid
admin.firestore().collection(MatchingUsers).doc(matchId).collection(UserMessages).add({
type: "text",
from: uid,
message: message,
timestamp: admin.firestore.Timestamp.now()
}).then(result => {
console.log("Message sent")
}).catch(err => {
console.log("Error sending mesage:", err)
})
})
As explained in the documentation of the HTTP Callable Cloud Functions:
To return data after an asynchronous operation, return a promise.
Then follows an example:
const sanitizedMessage = sanitizer.sanitizeText(text); // Sanitize the message.
return admin.database().ref('/messages').push({
text: sanitizedMessage,
author: { uid, name, picture, email },
}).then(() => {
console.log('New Message written');
// Returning the sanitized message to the client.
return { text: sanitizedMessage };
})
So you need to adapt your code as follows:
exports.sendMessage = functions.https.onCall((data, context) => {
if (context.auth.uid == undefined) {
console.warn("SEND MESSAGE: USER NOT SIGNED IN");
//Here send back an error as explained here: https://firebase.google.com/docs/functions/callable#handle_errors
}
console.log("Sending message:", data)
const matchId = data["matchId"];
const message = data["message"]
const uid = context.auth.uid
//Note the return on next line
return admin.firestore().collection(MatchingUsers).doc(matchId).collection(UserMessages).add({
type: "text",
from: uid,
message: message,
timestamp: admin.firestore.Timestamp.now()
}).then(result => {
console.log("Message sent");
return { text: "Message sent" };
}).catch(err => {
console.log("Error sending mesage:", err);
//Here, again, send back an error as explained here: https://firebase.google.com/docs/functions/callable#handle_errors
})
})
If you don't want to return a value to the client, you could do as follows, returning null when the Promise returned by the add() asynchronous method resolves. (Not tested but it should work).
exports.sendMessage = functions.https.onCall((data, context) => {
if (context.auth.uid == undefined) {
console.warn("SEND MESSAGE: USER NOT SIGNED IN");
return null;
}
console.log("Sending message:", data)
const matchId = data["matchId"];
const message = data["message"]
const uid = context.auth.uid
//Note the return on next line
return admin.firestore().collection(MatchingUsers).doc(matchId).collection(UserMessages).add({
type: "text",
from: uid,
message: message,
timestamp: admin.firestore.Timestamp.now()
}).then(result => {
console.log("Message sent"); //Actually, if you don't need this console.log() you can remove this entire then() block, returning the promise from add() is enough
return null;
}).catch(err => {
console.log("Error sending mesage:", err);
return null;
})
})
I got the following cloud function which works great.
It's listening for an update in the real-time database and update Firestore accordingly.
Everything is fine, except when my user does not exist yet my Firestore database.
This where I need to deal with Unhandled rejection that I see in the Google Cloud Functions log.
So see below, in the shortened version of the function, for my db.collection("users").where("email", "==", email).get() how to stop the function to move forward and prevent the crash.
exports.updateActivities = functions.database.ref("delegates/{userId}/activities").onWrite((event) => {
//Here I set all the needed variable
return rtdb.ref(`delegates/${userId}/email`).once("value", snapshot => {
//Here I'm fine, email is always present.
})
.then(() => {
db.collection("users").where("email", "==", email).get()
//This is where I need to handle when there is not matching value, to stop moving forward.
.then(querySnapshot => {
querySnapshot.forEach(val => {
console.log("Found match in FireStore " + val.id);
firestoreId = val.id;
})
})
.then(() => {
//Here I start my update on Firestore
});
})
});
You should use catch() on every promise that you return from your function that could be rejected. This tells Cloud Functions that you handled the error. The promise returned from catch() will be resolved.
Typically you log the error from catch() so you can see it in the console logs:
return somePromise
.then(() => { /* do your stuff */ }
.catch(error => { console.error(error) })
I used bluebird with suppressUnhandledRejections to get round this issue:
import Promise from 'bluebird';
function pool_push(pool, promise)
{
// In Google Cloud there is a change to crash at `unhandledRejection`.
// The following trick, basically, tells not to emit
// `unhandledRejection` since `.catch` will be attached
// a bit latter.
const tmp = Promise.resolve(promise);
tmp.suppressUnhandledRejections();
pool.items.push(tmp);
}
export default pool_push;
I am implementing a command/response pattern where the user writes to a command collection by calling add with a payload under his own userId, and then gets the data from a similar response path.
However the code below doesn't work, because onSnapshot can not listen for a document that hasn't yet been created (document command.id in the /responses/{userId}/register collection). This would be easy to solve with an onCreate handler, which exists for cloud functions but not for the JS firebase client API it seems.
This is using redux-firestore and some of my app helper functions, but you'll get the idea. The command and response document structures use { payload, error} similar to FSA
Cloud Function
export const register = functions.firestore
.document("commands/{userId}/register/{commandId}")
.onCreate(async event => {
const payload = event.data.get("payload");
const { userId, commandId } = event.params;
const response = db.document(`responses/${userId}/register/${commandId}`)
// possibly something bad will happen
try {
// do something with payload...
return response.set({
payload: "ok" // or pass relevant response data
})
} catch(err) {
return response.set({
error: true
payload: error
})
}
});
Client
export async function register(fs: any, userId: string) {
try {
// issue a new command
const command = await fs.add(
{ collection: `commands/${userId}/register` },
{ payload: fs.firestore.FieldValue.serverTimestamp() }
);
// wait for the response to be created
fs.onSnapshot(
{ collection: `responses/${userId}/register`, doc: command.id },
function onNext(doc) {
const {error, payload} = doc.data()
if (error) {
return notify.error({ title: 'Failed to register', message: payload.message });
}
notify.json(payload);
},
function onError(err) {
notify.error(err);
}
);
} catch (err) {
notify.error(err);
}
}
Is there no such thing as onCreate for web clients?
The only scalable solution I can think of is to store the response data as a child in the command document, but I think it is not as nice, because I suspect you can not make the permissions as strict then.
I would like the user only to be able to write to the command, and only read from the response paths. If I place the response as a child of command, this would not be possible I think?
I'm wondering if I'm not overlooking some API...
i'm stuck on this issue working on a ionic2 project with "firestore" service from firebase.
I have an osservable to get some data from firestore in a template using the async pipe.
Rule on this EndPoint give read and write access only to logged user.
When i sign-out i put a redirect to login page.
..and now come the issue..
when i land in the login page, after a few second, jump out the IonicErrorHandler notifying that i have insufficient permission.
so;
how i can tell to firestore osservable;
"hey dude, stop it, i call u later if someone log-in again"
(ill try an unsubscribe() befour the signOut but not work, and also
it does not from persistence)
Recapping:
when i logOut
this.afAuth.auth.signOut();
the error:
core.es5.js:1020 ERROR Error: Missing or insufficient permissions.
at new FirestoreError (error.js:164)
at JsonProtoSerializer.fromRpcStatus (serializer.js:126)
at JsonProtoSerializer.fromWatchChange (serializer.js:517)
at PersistentListenStream.onMessage (persistent_stream.js:334)
at persistent_stream.js:270
at persistent_stream.js:247
at async_queue.js:81
at t.invoke (polyfills.js:3)
at Object.onInvoke (core.es5.js:3890)
at t.invoke (polyfills.js:3)
(to be precise, i recive it 3 times. Exactly the number or of documents in the collection)
Service where i call the firestore endpoint:
export interface Attivita {
id: string;
committente: string;
durata: number;
nome: string;
progetto: string;
userId: string;
}
#Injectable()
export class FirebaseService {
attivitaCollectionRef: AngularFirestoreCollection<Attivita>;
attivita$: Observable<Attivita[]>;
constructor(private afs: AngularFirestore,
public afAuth: AngularFireAuth ) {
}
setOsservableAttivita(uId){
this.attivitaCollectionRef = this.afs.collection('attivita', ref => {
return ref.where("userId", "==", uId)
});
this.attivita$ = this.attivitaCollectionRef.snapshotChanges().map(actions => {
return actions.map(action => {
console.log(action)
const data = action.payload.doc.data() as Attivita;
const id = action.payload.doc.id;
return { id, ...data };
});
});
}
}
tks in advance to all help me to understand it
:)
I'd recommend watching the authState from Firebase and only taking from snapshotChanges while you're authenticated. The switchMap operator allows you to switch between observables based on conditions such as whether or not the user is authenticated. Here is an example of a possible solution.
// Assuming rxjs 5.5.0 with lettable operators
import { map } from 'rxjs/operators/map';
import { switchMap } from 'rxjs/operators/switchMap';
import { empty } from 'rxjs/observable/empty';
import { create } from 'rxjs/observable/create';
const actions$ = this.attivitaCollectionRef.snapshotChanges()
.map(actions => {
return actions.map(action => {
console.log(action)
const data = action.payload.doc.data() as Attivita;
const id = action.payload.doc.id;
return { id, ...data };
});
});
}),
this.attivita$ = create(
// Listen for changes in the authState
subscriber => this.afAuth.onAuthStateChanged(subscriber))
)
.pipe(
// Determine if the user is logged in
map(user => !!user),
// switchMap will unsubscribe from the previous observable
// so when isLoggedIn switches to false actions$ will be unsubscribed from
switchMap(isLoggedIn => isLoggedIn ? actions$ : empty()),
);
calling this after logout solved it for me:
afStore.firestore.disableNetwork();
Yes,
Normally this is solved by doing an unsubscribe of your subscription in the ngOnDestroy() of the component you are navigating away from.
That's the way I do it.
So for you, that would be:
ngOnDestroy() {
this.attivita$.unsubscribe();
}
However, it is very difficult to pinpoint which you should unsubscribe as the error does not give any indication on that.
I have added a question to the devs of angularFire on your issue:
https://github.com/angular/angularfire2/issues/1459.
It would be nice to have helped so that the exception points you in the right direction, for example, the path which was not unsubscribed or the last path segment.
Also, there are alternative methods for doing this listed in this post:
http://brianflove.com/2016/12/11/anguar-2-unsubscribe-observables/
Hope that helps.