Skip to content

Commit 0c062c6

Browse files
authored
Merge 5a4d0f2 into 2bfa185
2 parents 2bfa185 + 5a4d0f2 commit 0c062c6

17 files changed

+139
-23
lines changed

packages/firestore/src/core/firestore_client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import {
5151
import { BundleReader } from '../util/bundle_reader';
5252
import { LoadBundleTask } from '../api/bundle';
5353
import { newConnection } from '../platform/connection';
54-
import { newSerializer } from '../platform/serializer';
54+
import { newSerializer, newTextEncoder } from '../platform/serializer';
5555
import { toByteStreamReader } from '../platform/byte_stream_reader';
5656

5757
const LOG_TAG = 'FirestoreClient';
@@ -529,7 +529,7 @@ export class FirestoreClient {
529529

530530
let content: ReadableStream<Uint8Array> | ArrayBuffer;
531531
if (typeof data === 'string') {
532-
content = new TextEncoder().encode(data);
532+
content = newTextEncoder().encode(data);
533533
} else {
534534
content = data;
535535
}

packages/firestore/src/core/sync_engine.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ class SyncEngineImpl implements SyncEngine {
293293
protected remoteStore: RemoteStore,
294294
protected datastore: Datastore,
295295
// PORTING NOTE: Manages state synchronization in multi-tab environments.
296-
protected sharedClientState: SharedClientState,
296+
public sharedClientState: SharedClientState,
297297
private currentUser: User,
298298
private maxConcurrentLimboResolutions: number
299299
) {}
@@ -1100,6 +1100,12 @@ class MultiTabSyncEngineImpl extends SyncEngineImpl {
11001100
}
11011101
}
11021102

1103+
synchronizeWithChangedDocuments(): Promise<void> {
1104+
return this.localStore
1105+
.getNewDocumentChanges()
1106+
.then(changes => this.emitNewSnapsAndNotifyLocalStore(changes));
1107+
}
1108+
11031109
async applyBatchState(
11041110
batchId: BatchId,
11051111
batchState: MutationBatchState,
@@ -1412,7 +1418,9 @@ export function loadBundle(
14121418
syncEngineImpl.assertSubscribed('loadBundle()');
14131419

14141420
// eslint-disable-next-line @typescript-eslint/no-floating-promises
1415-
loadBundleImpl(syncEngineImpl, bundleReader, task);
1421+
loadBundleImpl(syncEngineImpl, bundleReader, task).then(() => {
1422+
syncEngineImpl.sharedClientState.notifyBundleLoaded();
1423+
});
14161424
}
14171425

14181426
async function loadBundleImpl(

packages/firestore/src/local/shared_client_state.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
import {
4141
CLIENT_STATE_KEY_PREFIX,
4242
ClientStateSchema,
43+
createBundleLoadedKey,
4344
createWebStorageClientStateKey,
4445
createWebStorageMutationBatchKey,
4546
createWebStorageOnlineStateKey,
@@ -173,6 +174,12 @@ export interface SharedClientState {
173174
setOnlineState(onlineState: OnlineState): void;
174175

175176
writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void;
177+
178+
/**
179+
* Notifies other clients when remote documents have changed due to loading
180+
* a bundle.
181+
*/
182+
notifyBundleLoaded(): void;
176183
}
177184

178185
/**
@@ -477,6 +484,7 @@ export class WebStorageSharedClientState implements SharedClientState {
477484
private readonly sequenceNumberKey: string;
478485
private readonly storageListener = this.handleWebStorageEvent.bind(this);
479486
private readonly onlineStateKey: string;
487+
private readonly bundleLoadedKey: string;
480488
private readonly clientStateKeyRe: RegExp;
481489
private readonly mutationBatchKeyRe: RegExp;
482490
private readonly queryTargetKeyRe: RegExp;
@@ -532,6 +540,8 @@ export class WebStorageSharedClientState implements SharedClientState {
532540

533541
this.onlineStateKey = createWebStorageOnlineStateKey(this.persistenceKey);
534542

543+
this.bundleLoadedKey = createBundleLoadedKey(this.persistenceKey);
544+
535545
// Rather than adding the storage observer during start(), we add the
536546
// storage observer during initialization. This ensures that we collect
537547
// events before other components populate their initial state (during their
@@ -711,6 +721,10 @@ export class WebStorageSharedClientState implements SharedClientState {
711721
this.persistOnlineState(onlineState);
712722
}
713723

724+
notifyBundleLoaded(): void {
725+
this.persistBundleLoadedState();
726+
}
727+
714728
shutdown(): void {
715729
if (this.started) {
716730
this.window.removeEventListener('storage', this.storageListener);
@@ -818,6 +832,8 @@ export class WebStorageSharedClientState implements SharedClientState {
818832
if (sequenceNumber !== ListenSequence.INVALID) {
819833
this.sequenceNumberHandler!(sequenceNumber);
820834
}
835+
} else if (storageEvent.key === this.bundleLoadedKey) {
836+
return this.syncEngine!.synchronizeWithChangedDocuments();
821837
}
822838
});
823839
}
@@ -883,6 +899,10 @@ export class WebStorageSharedClientState implements SharedClientState {
883899
this.setItem(targetKey, targetMetadata.toWebStorageJSON());
884900
}
885901

902+
private persistBundleLoadedState(): void {
903+
this.setItem(this.bundleLoadedKey, 'value-not-used');
904+
}
905+
886906
/**
887907
* Parses a client state key in WebStorage. Returns null if the key does not
888908
* match the expected key format.
@@ -1131,4 +1151,8 @@ export class MemorySharedClientState implements SharedClientState {
11311151
shutdown(): void {}
11321152

11331153
writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void {}
1154+
1155+
notifyBundleLoaded(): void {
1156+
// No op.
1157+
}
11341158
}

packages/firestore/src/local/shared_client_state_schema.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,15 @@ export function createWebStorageOnlineStateKey(persistenceKey: string): string {
115115
return `${ONLINE_STATE_KEY_PREFIX}_${persistenceKey}`;
116116
}
117117

118+
// The WebStorage prefix that plays as a event to indicate the remote documents
119+
// might have changed due to some secondary tabs loading a bundle.
120+
// format of the key is:
121+
// firestore_remote_documents_changed_<persistenceKey>
122+
export const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded';
123+
export function createBundleLoadedKey(persistenceKey: string): string {
124+
return `${BUNDLE_LOADED_KEY_PREFIX}_${persistenceKey}`;
125+
}
126+
118127
/**
119128
* The JSON representation of the system's online state, as written by the
120129
* primary client.

packages/firestore/src/local/shared_client_state_syncer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,10 @@ export interface SharedClientStateSyncer {
4949

5050
/** Returns the IDs of the clients that are currently active. */
5151
getActiveClients(): Promise<ClientId[]>;
52+
53+
/**
54+
* Retrieves newly changed documents from remote document cache and raises
55+
* snapshots if needed.
56+
*/
57+
synchronizeWithChangedDocuments(): Promise<void>;
5258
}

packages/firestore/src/platform/browser/serializer.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,17 @@ import { JsonProtoSerializer } from '../../remote/serializer';
2222
export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer {
2323
return new JsonProtoSerializer(databaseId, /* useProto3Json= */ true);
2424
}
25+
26+
/**
27+
* An instance of the Platform's 'TextEncoder' implementation.
28+
*/
29+
export function newTextEncoder(): TextEncoder {
30+
return new TextEncoder();
31+
}
32+
33+
/**
34+
* An instance of the Platform's 'TextDecoder' implementation.
35+
*/
36+
export function newTextDecoder(): TextDecoder {
37+
return new TextDecoder('utf-8');
38+
}

packages/firestore/src/platform/node/serializer.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,22 @@
1818
/** Return the Platform-specific serializer monitor. */
1919
import { JsonProtoSerializer } from '../../remote/serializer';
2020
import { DatabaseId } from '../../core/database_info';
21+
import { TextDecoder, TextEncoder } from 'util';
2122

2223
export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer {
2324
return new JsonProtoSerializer(databaseId, /* useProto3Json= */ false);
2425
}
26+
27+
/**
28+
* An instance of the Platform's 'TextEncoder' implementation.
29+
*/
30+
export function newTextEncoder(): TextEncoder {
31+
return new TextEncoder();
32+
}
33+
34+
/**
35+
* An instance of the Platform's 'TextDecoder' implementation.
36+
*/
37+
export function newTextDecoder(): TextDecoder {
38+
return new TextDecoder('utf-8');
39+
}

packages/firestore/src/platform/rn/serializer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@
1515
* limitations under the License.
1616
*/
1717

18-
export { newSerializer } from '../browser/serializer';
18+
export {
19+
newSerializer,
20+
newTextEncoder,
21+
newTextDecoder
22+
} from '../browser/serializer';

packages/firestore/src/platform/serializer.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
*/
1717

1818
import { isNode, isReactNative } from '@firebase/util';
19+
import { DatabaseId } from '../core/database_info';
20+
import { JsonProtoSerializer } from '../remote/serializer';
1921
import * as node from './node/serializer';
2022
import * as rn from './rn/serializer';
2123
import * as browser from './browser/serializer';
22-
import { DatabaseId } from '../core/database_info';
23-
import { JsonProtoSerializer } from '../remote/serializer';
2424

2525
export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer {
2626
if (isNode()) {
@@ -31,3 +31,29 @@ export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer {
3131
return browser.newSerializer(databaseId);
3232
}
3333
}
34+
35+
/**
36+
* An instance of the Platform's 'TextEncoder' implementation.
37+
*/
38+
export function newTextEncoder(): TextEncoder {
39+
if (isNode()) {
40+
return node.newTextEncoder();
41+
} else if (isReactNative()) {
42+
return rn.newTextEncoder();
43+
} else {
44+
return browser.newTextEncoder();
45+
}
46+
}
47+
48+
/**
49+
* An instance of the Platform's 'TextDecoder' implementation.
50+
*/
51+
export function newTextDecoder(): TextDecoder {
52+
if (isNode()) {
53+
return node.newTextDecoder() as TextDecoder;
54+
} else if (isReactNative()) {
55+
return rn.newTextDecoder();
56+
} else {
57+
return browser.newTextDecoder();
58+
}
59+
}

packages/firestore/src/util/bundle_reader.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { Deferred } from './promise';
2323
import { debugAssert } from './assert';
2424
import { toByteStreamReader } from '../platform/byte_stream_reader';
25+
import { newTextDecoder } from '../platform/serializer';
2526

2627
/**
2728
* A complete element in the bundle stream, together with the byte length it
@@ -67,7 +68,7 @@ export class BundleReader {
6768
*/
6869
private buffer: Uint8Array = new Uint8Array();
6970
/** The decoder used to parse binary data into strings. */
70-
private textDecoder = new TextDecoder('utf-8');
71+
private textDecoder: TextDecoder;
7172

7273
static fromBundleSource(source: BundleSource): BundleReader {
7374
return new BundleReader(toByteStreamReader(source, BYTES_PER_READ));
@@ -77,6 +78,7 @@ export class BundleReader {
7778
/** The reader to read from underlying binary bundle data source. */
7879
private reader: ReadableStreamReader<Uint8Array>
7980
) {
81+
this.textDecoder = newTextDecoder();
8082
// Read the metadata (which is the first element).
8183
this.nextElementImpl().then(
8284
element => {

packages/firestore/test/integration/api_internal/bundle.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import { DatabaseId } from '../../../src/core/database_info';
2727
import { key } from '../../util/helpers';
2828
import { EventsAccumulator } from '../util/events_accumulator';
2929
import { TestBundleBuilder } from '../../unit/util/bundle_data';
30+
import { newTextEncoder } from '../../../src/platform/serializer';
31+
32+
export const encoder = newTextEncoder();
3033

3134
function verifySuccessProgress(p: firestore.LoadBundleTaskProgress): void {
3235
expect(p.taskState).to.equal('Success');
@@ -45,7 +48,6 @@ function verifyInProgress(
4548
}
4649

4750
apiDescribe('Bundles', (persistence: boolean) => {
48-
const encoder = new TextEncoder();
4951
const testDocs: { [key: string]: firestore.DocumentData } = {
5052
a: { k: { stringValue: 'a' }, bar: { integerValue: 1 } },
5153
b: { k: { stringValue: 'b' }, bar: { integerValue: 2 } }

packages/firestore/test/unit/local/persistence_test_helpers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ class NoOpSharedClientStateSyncer implements SharedClientStateSyncer {
170170
removed: TargetId[]
171171
): Promise<void> {}
172172
applyOnlineStateChange(onlineState: OnlineState): void {}
173+
174+
async synchronizeWithChangedDocuments(): Promise<void> {}
173175
}
174176
/**
175177
* Populates Web Storage with instance data from a pre-existing client.

packages/firestore/test/unit/local/web_storage_shared_client_state.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ class TestSharedClientSyncer implements SharedClientStateSyncer {
150150
applyOnlineStateChange(onlineState: OnlineState): void {
151151
this.onlineState = onlineState;
152152
}
153+
154+
async synchronizeWithChangedDocuments(): Promise<void> {}
153155
}
154156

155157
describe('WebStorageSharedClientState', () => {

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,12 +293,9 @@ describeSpec('Bundles:', ['no-ios', 'no-android'], () => {
293293
.client(1)
294294
.loadBundle(bundleString1)
295295
.client(0)
296-
.becomeVisible();
297-
// TODO(wuandy): Loading from secondary client does not notify other
298-
// clients for now. We need to fix it and uncomment below.
299-
// .expectEvents(query, {
300-
// modified: [doc('collection/a', 500, { value: 'b' })],
301-
// })
296+
.expectEvents(query, {
297+
modified: [doc('collection/a', 500, { value: 'b' })]
298+
});
302299
}
303300
);
304301

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ import {
126126
} from '../../util/test_platform';
127127
import { toByteStreamReader } from '../../../src/platform/byte_stream_reader';
128128
import { logWarn } from '../../../src/util/log';
129+
import { newTextEncoder } from '../../../src/platform/serializer';
129130

130131
const ARBITRARY_SEQUENCE_NUMBER = 2;
131132

@@ -456,7 +457,7 @@ abstract class TestRunner {
456457

457458
private async doLoadBundle(bundle: string): Promise<void> {
458459
const reader = new BundleReader(
459-
toByteStreamReader(new TextEncoder().encode(bundle))
460+
toByteStreamReader(newTextEncoder().encode(bundle))
460461
);
461462
const task = new LoadBundleTask();
462463
return this.queue.enqueue(async () => {

packages/firestore/test/unit/util/bundle.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ import {
3939
doc1,
4040
doc2
4141
} from './bundle_data';
42+
import { newTextEncoder } from '../../../src/platform/serializer';
4243

4344
use(chaiAsPromised);
4445

46+
const encoder = newTextEncoder();
47+
4548
/**
4649
* Create a `ReadableStream` from a string.
4750
*
@@ -53,14 +56,13 @@ export function byteStreamReaderFromString(
5356
content: string,
5457
bytesPerRead: number
5558
): ReadableStreamReader<Uint8Array> {
56-
const data = new TextEncoder().encode(content);
59+
const data = encoder.encode(content);
5760
return toByteStreamReader(data, bytesPerRead);
5861
}
5962

6063
// Testing readableStreamFromString() is working as expected.
6164
describe('byteStreamReaderFromString()', () => {
6265
it('returns a reader stepping readable stream', async () => {
63-
const encoder = new TextEncoder();
6466
const r = byteStreamReaderFromString('0123456789', 4);
6567

6668
let result = await r.read();
@@ -93,8 +95,6 @@ function genericBundleReadingTests(bytesPerRead: number): void {
9395
return new BundleReader(byteStreamReaderFromString(s, bytesPerRead));
9496
}
9597

96-
const encoder = new TextEncoder();
97-
9898
async function getAllElements(
9999
bundle: BundleReader
100100
): Promise<SizedBundleElement[]> {

0 commit comments

Comments
 (0)