Skip to content

Commit 92ae0fc

Browse files
Merge branch 'master' into mrschmidt-commitversion
2 parents 411c4bd + 9d96d95 commit 92ae0fc

File tree

14 files changed

+169
-41
lines changed

14 files changed

+169
-41
lines changed

.github/CODEOWNERS

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ packages/database @mikelehen @schmidt-sebastian
99
packages/database-types @mikelehen @schmidt-sebastian
1010

1111
# Firestore Code
12-
packages/firestore @mikelehen @schmidt-sebastian @wilhuff
13-
packages/webchannel-wrapper @mikelehen @schmidt-sebastian @wilhuff
14-
packages/firestore-types @mikelehen @schmidt-sebastian @wilhuff
15-
integration/firestore @mikelehen @schmidt-sebastian @wilhuff
12+
packages/firestore @mikelehen @schmidt-sebastian @wilhuff @gsoltis @var-const @rsgowman @zxu123
13+
packages/webchannel-wrapper @mikelehen @schmidt-sebastian @wilhuff @gsoltis @var-const @rsgowman @zxu123
14+
packages/firestore-types @mikelehen @schmidt-sebastian @wilhuff @gsoltis @var-const @rsgowman @zxu123
15+
integration/firestore @mikelehen @schmidt-sebastian @wilhuff @gsoltis @var-const @rsgowman @zxu123
1616

1717
# Storage Code
1818
packages/storage @sphippen

packages/auth/src/error_withcredential.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,9 @@ fireauth.AuthErrorWithCredential.fromPlainObject = function(response) {
130130
credentialInfo.email = response['email'];
131131
} else if (response['phoneNumber']) {
132132
credentialInfo.phoneNumber = response['phoneNumber'];
133-
} else {
134-
// Neither email nor phone number are set; return a generic error.
133+
} else if (!credentialInfo.credential) {
134+
// Neither email, phone number or credentials are set; return a generic
135+
// error.
135136
return new fireauth.AuthError(code, response['message'] || undefined);
136137
}
137138

packages/auth/src/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,7 @@ fireauth.util.setNoReferrer = function() {
14591459

14601460
/** @return {?ServiceWorker} The servicerWorker controller if available. */
14611461
fireauth.util.getServiceWorkerController = function() {
1462+
var navigator = goog.global['navigator'];
14621463
return (navigator &&
14631464
navigator.serviceWorker &&
14641465
navigator.serviceWorker.controller) || null;
@@ -1477,13 +1478,17 @@ fireauth.util.getWorkerGlobalScope = function() {
14771478
* available. If no service worker is supported, it will resolve with null.
14781479
*/
14791480
fireauth.util.getActiveServiceWorker = function() {
1481+
var navigator = goog.global['navigator'];
14801482
if (navigator && navigator.serviceWorker) {
14811483
return goog.Promise.resolve()
14821484
.then(function() {
14831485
return navigator.serviceWorker.ready;
14841486
})
14851487
.then(function(registration) {
14861488
return /** @type {?ServiceWorker} */ (registration.active || null);
1489+
})
1490+
.thenCatch(function(error) {
1491+
return null;
14871492
});
14881493
}
14891494
return goog.Promise.resolve(/** @type {?ServiceWorker} */ (null));

packages/auth/test/error_test.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,26 @@ function testAuthErrorWithCredential_toPlainObject() {
250250
assertObjectEquals(
251251
errorObject3,
252252
error3.toPlainObject());
253+
254+
// AuthErrorWithCredential with just a credential and no email or phoneNumber.
255+
var credential4 = fireauth.FacebookAuthProvider.credential('ACCESS_TOKEN');
256+
var error4 = new fireauth.AuthErrorWithCredential(
257+
fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE,
258+
{
259+
credential: credential4
260+
},
261+
'This credential is already associated with a different user account.');
262+
var errorObject4 = {
263+
'code': 'auth/credential-already-in-use',
264+
'message': 'This credential is already associated with a different user ' +
265+
'account.',
266+
'providerId': 'facebook.com',
267+
'oauthAccessToken': 'ACCESS_TOKEN',
268+
'signInMethod': fireauth.FacebookAuthProvider['FACEBOOK_SIGN_IN_METHOD']
269+
};
270+
assertObjectEquals(
271+
errorObject4,
272+
error4.toPlainObject());
253273
}
254274

255275

@@ -329,6 +349,36 @@ function testAuthErrorWithCredential_fromPlainObject() {
329349
assertObjectEquals(
330350
error3,
331351
fireauth.AuthErrorWithCredential.fromPlainObject(errorObject3NoPrefix));
352+
353+
// AuthErrorWithCredential with just a credential
354+
var credential4 = fireauth.FacebookAuthProvider.credential('ACCESS_TOKEN');
355+
var error4 = new fireauth.AuthErrorWithCredential(
356+
fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE,
357+
{
358+
credential: credential4
359+
},
360+
'This credential is already associated with a different user account.');
361+
var errorObject4 = {
362+
'code': 'auth/credential-already-in-use',
363+
'message': 'This credential is already associated with a different user ' +
364+
'account.',
365+
'providerId': 'facebook.com',
366+
'oauthAccessToken': 'ACCESS_TOKEN'
367+
};
368+
var errorObject4NoPrefix = {
369+
'code': 'credential-already-in-use',
370+
'message': 'This credential is already associated with a different user ' +
371+
'account.',
372+
'providerId': 'facebook.com',
373+
'oauthAccessToken': 'ACCESS_TOKEN'
374+
};
375+
assertObjectEquals(
376+
error4,
377+
fireauth.AuthErrorWithCredential.fromPlainObject(errorObject4));
378+
// If the error code prefix is missing.
379+
assertObjectEquals(
380+
error4,
381+
fireauth.AuthErrorWithCredential.fromPlainObject(errorObject4NoPrefix));
332382
}
333383

334384

packages/firestore/src/core/sync_engine.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -315,11 +315,13 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
315315
);
316316

317317
if (!targetRemainsActive) {
318-
this.sharedClientState.clearQueryState(queryView.targetId);
319-
this.remoteStore.unlisten(queryView.targetId);
320318
await this.localStore
321319
.releaseQuery(query, /*keepPersistedQueryData=*/ false)
322-
.then(() => this.removeAndCleanupQuery(queryView))
320+
.then(() => {
321+
this.sharedClientState.clearQueryState(queryView.targetId);
322+
this.remoteStore.unlisten(queryView.targetId);
323+
return this.removeAndCleanupQuery(queryView);
324+
})
323325
.then(() => this.localStore.collectGarbage())
324326
.catch(err => this.ignoreIfPrimaryLeaseLoss(err));
325327
}
@@ -811,7 +813,6 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
811813
await Promise.all(queriesProcessed);
812814
this.syncEngineListener!.onWatchChange(newSnaps);
813815
this.localStore.notifyLocalViewChanges(docChangesInAllViews);
814-
// TODO(multitab): Multitab garbage collection
815816
if (this.isPrimary) {
816817
await this.localStore
817818
.collectGarbage()
@@ -883,14 +884,22 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
883884
}
884885
} else if (isPrimary === false && this.isPrimary !== false) {
885886
this.isPrimary = false;
886-
const activeQueries = await this.synchronizeQueryViewsAndRaiseSnapshots(
887-
objUtils.indices(this.queryViewsByTarget)
888-
);
887+
888+
const activeTargets: TargetId[] = [];
889+
890+
let p = Promise.resolve();
891+
objUtils.forEachNumber(this.queryViewsByTarget, (targetId, queryView) => {
892+
if (this.sharedClientState.isLocalQueryTarget(targetId)) {
893+
activeTargets.push(targetId);
894+
} else {
895+
p = p.then(() => this.unlisten(queryView.query));
896+
}
897+
this.remoteStore.unlisten(queryView.targetId);
898+
});
899+
await p;
900+
901+
await this.synchronizeQueryViewsAndRaiseSnapshots(activeTargets);
889902
this.resetLimboDocuments();
890-
for (const queryData of activeQueries) {
891-
// TODO(multitab): Remove query views for non-local queries.
892-
this.remoteStore.unlisten(queryData.targetId);
893-
}
894903
await this.remoteStore.applyPrimaryState(false);
895904
}
896905
}
@@ -1039,10 +1048,12 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
10391048
// Check that the query is still active since the query might have been
10401049
// removed if it has been rejected by the backend.
10411050
if (queryView) {
1042-
this.remoteStore.unlisten(targetId);
10431051
await this.localStore
10441052
.releaseQuery(queryView.query, /*keepPersistedQueryData=*/ false)
1045-
.then(() => this.removeAndCleanupQuery(queryView))
1053+
.then(() => {
1054+
this.remoteStore.unlisten(targetId);
1055+
return this.removeAndCleanupQuery(queryView);
1056+
})
10461057
.catch(err => this.ignoreIfPrimaryLeaseLoss(err));
10471058
}
10481059
}

packages/firestore/src/core/view.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ export class View {
207207
while (newDocumentSet.size > this.query.limit!) {
208208
const oldDoc = newDocumentSet.last();
209209
newDocumentSet = newDocumentSet.delete(oldDoc!.key);
210+
newMutatedKeys = newMutatedKeys.delete(oldDoc!.key);
210211
changeSet.track({ type: ChangeType.Removed, doc: oldDoc! });
211212
}
212213
}

packages/firestore/src/local/indexeddb_persistence.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,6 @@ export class IndexedDbPersistence implements Persistence {
907907
*/
908908
private markClientZombied(): void {
909909
try {
910-
// TODO(multitab): Garbage Collect Local Storage
911910
this.window.localStorage.setItem(
912911
this.zombiedClientLocalStorageKey(this.clientId),
913912
String(Date.now())

packages/firestore/src/local/indexeddb_remote_document_cache.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ export class IndexedDbRemoteDocumentCache implements RemoteDocumentCache {
106106
}
107107

108108
if (this.keepDocumentChangeLog) {
109-
// TODO(multitab): GC the documentChanges store.
110109
promises.push(
111110
documentChangesStore(transaction).put({
112111
changes: this.serializer.toDbResourcePaths(changedKeys)

packages/firestore/src/local/persistence.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,6 @@ export type PrimaryStateListener = (isPrimary: boolean) => Promise<void>;
7979
* writes in order to avoid relying on being able to read back uncommitted
8080
* writes.
8181
*/
82-
// TODO(multitab): Instead of marking methods as multi-tab safe, we should
83-
// point out (and maybe enforce) when methods cannot safely be used from
84-
// secondary tabs.
8582
export interface Persistence {
8683
/**
8784
* Whether or not this persistence instance has been started.

packages/firestore/src/local/shared_client_state.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ export interface SharedClientState {
105105
/** Removes the Query Target ID association from the local client. */
106106
removeLocalQueryTarget(targetId: TargetId): void;
107107

108+
/** Checks whether the target is associated with the local client. */
109+
isLocalQueryTarget(targetId: TargetId): boolean;
110+
108111
/**
109112
* Processes an update to a query target.
110113
*
@@ -571,8 +574,6 @@ export class WebStorageSharedClientState implements SharedClientState {
571574
return !!(platform.window && platform.window.localStorage != null);
572575
}
573576

574-
// TOOD(multitab): Register the mutations that are already pending at client
575-
// startup.
576577
async start(): Promise<void> {
577578
assert(!this.started, 'WebStorageSharedClientState already started');
578579
assert(
@@ -702,6 +703,10 @@ export class WebStorageSharedClientState implements SharedClientState {
702703
this.persistClientState();
703704
}
704705

706+
isLocalQueryTarget(targetId: TargetId): boolean {
707+
return this.localClientState.activeTargetIds.has(targetId);
708+
}
709+
705710
clearQueryState(targetId: TargetId): void {
706711
this.removeItem(this.toLocalStorageQueryTargetMetadataKey(targetId));
707712
}
@@ -761,15 +766,15 @@ export class WebStorageSharedClientState implements SharedClientState {
761766

762767
private handleLocalStorageEvent(event: StorageEvent): void {
763768
if (event.storageArea === this.storage) {
764-
// TODO(multitab): This assert will likely become invalid as we add garbage
765-
// collection.
766-
assert(
767-
event.key !== this.localClientStorageKey,
768-
'Received LocalStorage notification for local change.'
769-
);
770-
771769
debug(LOG_TAG, 'EVENT', event.key, event.newValue);
772770

771+
if (event.key === this.localClientStorageKey) {
772+
error(
773+
'Received LocalStorage notification for local change. Another client might have garbage-collected our state'
774+
);
775+
return;
776+
}
777+
773778
this.queue.enqueueAndForget(async () => {
774779
if (!this.started) {
775780
this.earlyEvents.push(event);
@@ -1088,6 +1093,10 @@ export class MemorySharedClientState implements SharedClientState {
10881093
this.localState.removeQueryTarget(targetId);
10891094
}
10901095

1096+
isLocalQueryTarget(targetId: TargetId): boolean {
1097+
return this.localState.activeTargetIds.has(targetId);
1098+
}
1099+
10911100
clearQueryState(targetId: TargetId): void {
10921101
delete this.queryState[targetId];
10931102
}

packages/firestore/src/util/obj.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,6 @@ export function size<V>(obj: Dict<V>): number {
3939
return count;
4040
}
4141

42-
/** Extracts the numeric indices from a dictionary. */
43-
export function indices<V>(obj: { [numberKey: number]: V }): number[] {
44-
return Object.keys(obj).map(key => {
45-
return Number(key);
46-
});
47-
}
48-
4942
/** Returns the given value if it's defined or the defaultValue otherwise. */
5043
export function defaulted<V>(value: V | undefined, defaultValue: V): V {
5144
return value !== undefined ? value : defaultValue;

packages/firestore/test/unit/specs/describe_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const KNOWN_TAGS = [
4545
DURABLE_PERSISTENCE_TAG
4646
];
4747

48-
// TOOD(mrschmidt): Make this configurable with mocha options.
48+
// TODO(mrschmidt): Make this configurable with mocha options.
4949
const RUN_BENCHMARK_TESTS = false;
5050

5151
// The format of one describeSpec written to a JSON file.

packages/firestore/test/unit/specs/limit_spec.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,31 @@ describeSpec('Limits:', [], () => {
4141
});
4242
});
4343

44+
specTest(
45+
"Documents outside of limit don't raise hasPendingWrites",
46+
[],
47+
() => {
48+
const query1 = Query.atPath(path('collection')).withLimit(2);
49+
const doc1 = doc('collection/a', 1000, { key: 'a' });
50+
const doc2 = doc('collection/b', 1000, { key: 'b' });
51+
52+
return spec()
53+
.withGCEnabled(false)
54+
.userListens(query1)
55+
.watchAcksFull(query1, 1000, doc1, doc2)
56+
.expectEvents(query1, {
57+
added: [doc1, doc2]
58+
})
59+
.userUnlistens(query1)
60+
.userSets('collection/c', { key: 'c' })
61+
.userListens(query1, 'resume-token-1000')
62+
.expectEvents(query1, {
63+
added: [doc1, doc2],
64+
fromCache: true
65+
});
66+
}
67+
);
68+
4469
specTest('Deleted Document in limbo in full limit query', [], () => {
4570
const query = Query.atPath(path('collection')).withLimit(2);
4671
const doc1 = doc('collection/a', 1000, { key: 'a' });

packages/firestore/test/unit/specs/listen_spec.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,11 +1101,49 @@ describeSpec('Listens:', [], () => {
11011101
.expectEvents(query, { added: [docC] })
11021102
.client(0)
11031103
.runTimer(TimerId.ClientMetadataRefresh)
1104+
// Client 0 recovers from its lease loss and applies the updates from
1105+
// client 1
11041106
.expectPrimaryState(false)
11051107
.expectEvents(query, { added: [docB, docC] })
11061108
);
11071109
});
11081110

1111+
specTest('Query bounces between primaries', ['multi-client'], () => {
1112+
const query = Query.atPath(path('collection'));
1113+
const docA = doc('collection/a', 1000, { key: 'a' });
1114+
const docB = doc('collection/b', 2000, { key: 'b' });
1115+
const docC = doc('collection/c', 3000, { key: 'c' });
1116+
1117+
// Client 0 listens to a query. Client 1 is the primary when the query is
1118+
// first listened to, then the query switches to client 0 and back to client
1119+
// 1.
1120+
return client(1)
1121+
.expectPrimaryState(true)
1122+
.client(0)
1123+
.userListens(query)
1124+
.client(1)
1125+
.expectListen(query)
1126+
.watchAcksFull(query, 1000, docA)
1127+
.client(0)
1128+
.expectEvents(query, { added: [docA] })
1129+
.client(2)
1130+
.stealPrimaryLease()
1131+
.expectListen(query, 'resume-token-1000')
1132+
.client(1)
1133+
.runTimer(TimerId.ClientMetadataRefresh)
1134+
.expectPrimaryState(false)
1135+
.client(2)
1136+
.watchAcksFull(query, 2000, docB)
1137+
.client(0)
1138+
.expectEvents(query, { added: [docB] })
1139+
.client(1)
1140+
.stealPrimaryLease()
1141+
.expectListen(query, 'resume-token-2000')
1142+
.watchAcksFull(query, 2000, docC)
1143+
.client(0)
1144+
.expectEvents(query, { added: [docC] });
1145+
});
1146+
11091147
specTest(
11101148
'Unresponsive primary ignores watch update',
11111149
['multi-client'],

0 commit comments

Comments
 (0)