Firestore emulator - simulate loss of connectivity - google-cloud-firestore

I have an app with a real time listener setup to instantly pick up all updates on a collection. I also have some logic to pick missing documents afterwards in case my app loses its connectivity (in which case the listener would miss some documents).
I want to test this logic using the simulator, but how do I simulate the loss of connectivity? is there built-in function for that?

Actually the following page shows how to proceed in general, be it with the emulator or an actual Firestore instance:
firebase.firestore().disableNetwork()
.then(() => {
// Do offline actions
// ...
});
firebase.firestore().enableNetwork()
.then(() => {
// Do online actions
// ...
});

Related

ServerValue.increment doesn't work properly when Internet goes down

The addition of ServerValue.increment() (Add increment() for atomic field value increments #2437) was a great news as it allows field values ​​to be increased atomically in Firebase RTDB.
I have an application that keeps inventories and this function has been key because it allows updating the inventory regardless of whether the user is offline at times. However, I started to notice that sometimes the function is executed twice, which completely misstates the inventory in the wrong way.
To isolate the problem I decided to do the following test, which shows that ServerValue.Increment() works wrong when the connection goes from Online to Offline:
Make a for loop function from 1 to 200:
for (var i = 1; i <= 200; i++) {
testBloc.incrementTest(i);
print('Pos: $i');
}
The function incrementTest(i) must increment two variables: position (count from 1 in 1 up to 200) and sum (add 1 + 2 + 3, ..., + 200 which should result in 20,100)
Future<bool> incrementTest(int value) async {
try {
db.child('test/position')
.set(ServerValue.increment(1));
db.child('test/sum')
.set(ServerValue.increment(value));
} catch (e) {
print(e);
}
return true;
}
Note that db refers to the Firebase instance (FirebaseDatabase.instance.reference())
With this, comes the tests:
Test 1: 100% Online. PASSED
The function works properly, reaching the two variables to the correct result (in the Firebase console):
position: 200
sum: 20100
Test 2: 100% Offline. PASSED
To do this I used a physical device in airplane mode, then I executed the for loop function, and when the function finished executing I deactivated airplane mode and checked the result in the firebase console, which was satisfactory:
position: 200
sum: 20100
Test 3: Start Online and then go to Offline. FAILED
It is a typical operating scenario when the Internet Connection goes down. Even worse when the connections are intermittent, you are traveling on a subway or you are in a low coverage site for which Offline Persistence is a desired feature. To simulate it, what I did was run the for loop function in online mode, and before it finished, I put the physical device in airplane mode. Later I went Online to finish the test and see the results on the Firebase console. The results obtained are incorrect in all cases. Here are some of the results:
As you can see, the Increment was erroneously repeated 10, 18 and 9 times more.
How can I avoid this behavior?
Is there any other way to increment atomically a number in Firebase that works properly online / Offline ?
firebaser here
That's an interesting edge-case in the increment behavior. Between the client and the server neither can be certain whether the increment was executed or not, so it ends up being retried from the client upon the reconnect. This problem can only occur with the increment operation as far as I can tell, as all the other write operations are idempotent except for transactions, but those don't work while offline.
It is possible to ensure each increment happens only once, but it'll take some work:
First, add a nonce to write operation that unique identifies this operation. You can use a push key for this, but any other UUID also works fine. Combine this with your original set() call into a single multi-path update call, writing the nonce to a top-level node with a server-side timestamp as its value.
Now in your security rules for the top-level location, only allow the write if there is no existing data. This ensures the secondary writes you're seeing get rejected, and since security rules are checked across multi-path updates as a whole, the faulty increment will get rejected too.
You'll probably want to periodically clean up the node with nonce keys, based on the timestamp value in there. It won't matter for performance (since you're never searching here outside of during the cleanup), but may help control the storage cost for the nonces.
I haven't used this approach for this specific use-case yet, but have done it for others. If you'd include a client-side retry, the above essentially builds your own multi-path transaction mechanism, which is what I needed it for in the past. But since you don't need that here, it's simpler without that.
Based on #puf answer, you can proceed as follows:
Future<bool> incrementTest(int value, int dateOfToday) async {
var id = db.push().key;
Map<String, dynamic> _updates = {
'test/position': ServerValue.increment(1),
'test/sum': ServerValue.increment(value),
'test/nonce/$id': dateOfToday,
};
db.child('previousPath').update(_updates)
.catchError((error) => print('Increment Duplication Rejected ${error.message}'));
return true;
}
Then, in Firebase Security Rules, you need to add a rule in test/nonce/id location. Something as follows:
{
"previousPath": {
"test": {
".read": "auth != null", //It depends on your root rules
".write": "auth != null", //It depends on your root rules
"nonce": {
"$nonce_id": {
".validate": "!data.exists()" //THE MAGIC IS HERE
}
}
}
}
}
In this way, when the device tries to write to the database again (wrongly), Firebase will reject it since it already had a write with that same ID before.
I hope it serves someone else!!!

Intercepting filter/sort/column "model" changes / transactions

I would like to control the grid's sorting, filtering, and column states with an external store. Ideally I want to intercept the filterChanged and sortChanged events, emit them to the store, and let the store update the grid programmatically using the grid API.
readonly GRID_CONFIG: GridOptions = {
onSortChanged: (event: SortChangedEvent) => {
// Tell store that the sort changed
this.sortChanged.emit(this.grid.api.getSortModel());
},
onFilterChanged: (event: FilterChangedEvent) => {
// Tell store that the filter changed
this.filterChanged.emit(this.grid.api.getFilterModel());
},
onFilterModified: (event: FilterModifiedEvent) => {
// This isn't useful because it only relates to the floating filters pre-apply...
}
}
Unfortunately, once the onFilterChanged and onSortChanged events are called, the grid has already been updated, so the updates from the store are redundant.
The closest thing to what I want is isApplyServerSideTransaction, as this callback allows cancelling the transaction.
isApplyServerSideTransaction: (params: IsApplyServerSideTransactionParams) => {
// Emit model changes to the store so that the store can update the grid
this.sortChanged.emit(this.grid.api.getSortModel());
this.filterChanged.emit(this.grid.api.getFilterModel());
// Do not update the grid (redundant)
return false;
}
However, this is only supported for Server Side Row Model Full mode. This callback is never fired in my case, since I am using partial mode.
Are there any other tricks for hooking into the model changes before they're applied, or is this just not supported?
Update: I'm specifically trying to intercept the UI-triggered changes to the sort/filter model. I know this can be achieved with custom filters (and maybe custom headers?) but I'd much rather leverage Ag-Grid's built-in UI than build a whole custom copy, just to intercept one event.
Since you are using Server-side Row Model, the getRows callback is fired whenever the Grid is requesting data, i.e. whenever you filter/sort/group etc. So if you want to intercept what is being returned to the Grid, you should do it inside the getRows callback.

Can I preserve global state in Firebase Emulator cloud functions?

I'd like to run tests that call multiple Cloud Functions in the emulator that make use of a mocked out external service (getstream.io). That means the mock would have to stay around across function invocations. Is something like this possible?
let mock = new SomethingMock();
exports.resetMock = functions.https.onCall((data, context) => {
mock = new SomethingMock();
});
exports.addActivity = functions.https.onCall(async (data, context) => {
await mock.addActivity(something);
});
exports.getActivities = functions.https.onCall((data, context) => {
// assumes addActivity has been called a few times
return mock.getActivities();
});
This page says gives no guarantees about preservation of global state in production, but says nothing about the emulator:
https://firebase.google.com/docs/functions/tips#use_global_variables_to_reuse_objects_in_future_invocations
The firebase emulator will neither guarantee the preservation of global state.
The answer is in the definition itself:
The Firebase Local Emulator Suite consists of individual service emulators built to accurately mimic the behavior of Firebase services. This means you can connect your app directly to these emulators to perform integration testing or QA without touching production data. They(emulatored firebase services) are built for accuracy, not performance or security, and are not appropriate to use in production.
Having said that, there is not much added value in having an emulator that does not return results as if the service was in production.

Can I use this.userId in Meteor.onConnection()?

I am building a mobile application where my users can connect using accounts-facebook.
What I would like to do is refreshing their list of friends that use the app when they are logging in (is this a good way to keep this list up to date ?) or when they are connecting
I built something like this :
Meteor.startup(function(){
myFunctionToRefreshFriendLists()
});
Meteor.onConnection(function(conn) {
if (this.userId) {
myFunctionToRefreshFrindLists();
}
}
But this returns me "undefined", even when the user is connected. I know there is a problem using "this.userId" (this here does not seem relevant to me) but I do not know what to do ?
Any help appreciated, thank you !
Don't use Meteor.onConnection, as it will run every time a user starts a new connection. This can happen frequently depending on the stability of their connection; instead use Accounts.onLogin:
Accounts.onLogin(function(user){
console.log(user.user._id)
});
http://docs.meteor.com/#/full/accounts_onlogin

Meteor observer with two different applications

Because changed data (inserted by application A) needs to be displayed in application B in realtime we decided to go with .find().observe(...).
It does look like:
App A -> Insert -> mongodb <- observe -> publish -> Display App B
This works fine but it has a delay of about 3-5 seconds between Inserting in A and displaying in B. How can i change this?
Initially i thought, Oplog-Observe-Driver is default in Meteor > Version 1 and does react in realtime. Does it still POLL or is there some other reason for the delay????
Thanks for your expanations.
If you're using Oplog, then the changes will be immediate. If you're using poll then it'll take a few seconds as you wrote.
You need to set MONGO_OPLOG_URL correctly to make this work. (And of course your MongoDB needs to be Oplog enabled.)
Also, you don't need to use find().observe() if you're in a reactive context, find() is enough. On the server though you might need find().observe() depending on what you're doing.
Did you use DDP.connect? You also have to use the onReconnect
Remote = DDP.connect('http://yourremoteserver');
MyCollection = new Mongo.Collection('same_name', Remote);
// do whatever you need with collection
let watchCollection = function (query={}, project={}) {
return MyCollection.find(query, project).observe({
changed: function () { console.log('Something changed!') }
});
}
DDP.onReconnect(watchCollection);