Skip to content

Commit bb1f4de

Browse files
Extract WebStorage schema (#2444)
1 parent 19c34c6 commit bb1f4de

File tree

2 files changed

+180
-105
lines changed

2 files changed

+180
-105
lines changed

packages/firestore/src/local/shared_client_state.ts

Lines changed: 39 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -37,34 +37,23 @@ import {
3737
QueryTargetState,
3838
SharedClientStateSyncer
3939
} from './shared_client_state_syncer';
40+
import {
41+
CLIENT_STATE_KEY_PREFIX,
42+
ClientStateSchema,
43+
createWebStorageClientStateKey,
44+
createWebStorageMutationBatchKey,
45+
createWebStorageOnlineStateKey,
46+
createWebStorageQueryTargetMetadataKey,
47+
createWebStorageSequenceNumberKey,
48+
MUTATION_BATCH_KEY_PREFIX,
49+
MutationMetadataSchema,
50+
QUERY_TARGET_KEY_PREFIX,
51+
QueryTargetStateSchema,
52+
SharedOnlineStateSchema
53+
} from './shared_client_state_schema';
4054

4155
const LOG_TAG = 'SharedClientState';
4256

43-
// The format of the LocalStorage key that stores the client state is:
44-
// firestore_clients_<persistence_prefix>_<instance_key>
45-
const CLIENT_STATE_KEY_PREFIX = 'firestore_clients';
46-
47-
// The format of the WebStorage key that stores the mutation state is:
48-
// firestore_mutations_<persistence_prefix>_<batch_id>
49-
// (for unauthenticated users)
50-
// or: firestore_mutations_<persistence_prefix>_<batch_id>_<user_uid>
51-
//
52-
// 'user_uid' is last to avoid needing to escape '_' characters that it might
53-
// contain.
54-
const MUTATION_BATCH_KEY_PREFIX = 'firestore_mutations';
55-
56-
// The format of the WebStorage key that stores a query target's metadata is:
57-
// firestore_targets_<persistence_prefix>_<target_id>
58-
const QUERY_TARGET_KEY_PREFIX = 'firestore_targets';
59-
60-
// The WebStorage prefix that stores the primary tab's online state. The
61-
// format of the key is:
62-
// firestore_online_state_<persistence_prefix>
63-
const ONLINE_STATE_KEY_PREFIX = 'firestore_online_state';
64-
// The WebStorage key prefix for the key that stores the last sequence number allocated. The key
65-
// looks like 'firestore_sequence_number_<persistence_prefix>'.
66-
const SEQUENCE_NUMBER_KEY_PREFIX = 'firestore_sequence_number';
67-
6857
/**
6958
* A randomly-generated key assigned to each Firestore instance at startup.
7059
*/
@@ -186,17 +175,6 @@ export interface SharedClientState {
186175
writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void;
187176
}
188177

189-
/**
190-
* The JSON representation of a mutation batch's metadata as used during
191-
* WebStorage serialization. The UserId and BatchId is omitted as it is
192-
* encoded as part of the key.
193-
*/
194-
interface MutationMetadataSchema {
195-
state: MutationBatchState;
196-
error?: { code: string; message: string }; // Only set when state === 'rejected'
197-
updateTimeMs: number;
198-
}
199-
200178
/**
201179
* Holds the state of a mutation batch, including its user ID, batch ID and
202180
* whether the batch is 'pending', 'acknowledged' or 'rejected'.
@@ -280,16 +258,6 @@ export class MutationMetadata {
280258
}
281259
}
282260

283-
/**
284-
* The JSON representation of a query target's state as used during WebStorage
285-
* serialization. The TargetId is omitted as it is encoded as part of the key.
286-
*/
287-
interface QueryTargetStateSchema {
288-
state: QueryTargetState;
289-
error?: { code: string; message: string }; // Only set when state === 'rejected'
290-
updateTimeMs: number;
291-
}
292-
293261
/**
294262
* Holds the state of a query target, including its target ID and whether the
295263
* target is 'not-current', 'current' or 'rejected'.
@@ -370,16 +338,6 @@ export class QueryTargetMetadata {
370338
}
371339
}
372340

373-
/**
374-
* The JSON representation of a clients's metadata as used during WebStorage
375-
* serialization. The ClientId is omitted here as it is encoded as part of the
376-
* key.
377-
*/
378-
interface ClientStateSchema {
379-
activeTargetIds: number[];
380-
updateTimeMs: number;
381-
}
382-
383341
/**
384342
* Metadata state of a single client denoting the query targets it is actively
385343
* listening to.
@@ -434,20 +392,6 @@ class RemoteClientState implements ClientState {
434392
}
435393
}
436394

437-
/**
438-
* The JSON representation of the system's online state, as written by the
439-
* primary client.
440-
*/
441-
export interface SharedOnlineStateSchema {
442-
/**
443-
* The clientId of the client that wrote this onlineState value. Tracked so
444-
* that on startup, clients can check if this client is still active when
445-
* determining whether to apply this value or not.
446-
*/
447-
readonly clientId: string;
448-
readonly onlineState: string;
449-
}
450-
451395
/**
452396
* This class represents the online state for all clients participating in
453397
* multi-tab. The online state is only written to by the primary client, and
@@ -567,10 +511,13 @@ export class WebStorageSharedClientState implements SharedClientState {
567511

568512
this.storage = this.platform.window!.localStorage;
569513
this.currentUser = initialUser;
570-
this.localClientStorageKey = this.toWebStorageClientStateKey(
514+
this.localClientStorageKey = createWebStorageClientStateKey(
515+
this.persistenceKey,
571516
this.localClientId
572517
);
573-
this.sequenceNumberKey = `${SEQUENCE_NUMBER_KEY_PREFIX}_${persistenceKey}`;
518+
this.sequenceNumberKey = createWebStorageSequenceNumberKey(
519+
this.persistenceKey
520+
);
574521
this.activeClients[this.localClientId] = new LocalClientState();
575522

576523
this.clientStateKeyRe = new RegExp(
@@ -583,7 +530,7 @@ export class WebStorageSharedClientState implements SharedClientState {
583530
`^${QUERY_TARGET_KEY_PREFIX}_${escapedPersistenceKey}_(\\d+)$`
584531
);
585532

586-
this.onlineStateKey = `${ONLINE_STATE_KEY_PREFIX}_${persistenceKey}`;
533+
this.onlineStateKey = createWebStorageOnlineStateKey(this.persistenceKey);
587534

588535
// Rather than adding the storage observer during start(), we add the
589536
// storage observer during initialization. This ensures that we collect
@@ -620,7 +567,7 @@ export class WebStorageSharedClientState implements SharedClientState {
620567
}
621568

622569
const storageItem = this.getItem(
623-
this.toWebStorageClientStateKey(clientId)
570+
createWebStorageClientStateKey(this.persistenceKey, clientId)
624571
);
625572
if (storageItem) {
626573
const clientState = RemoteClientState.fromWebStorageEntry(
@@ -707,7 +654,7 @@ export class WebStorageSharedClientState implements SharedClientState {
707654
// by another tab
708655
if (this.isActiveQueryTarget(targetId)) {
709656
const storageItem = this.storage.getItem(
710-
this.toWebStorageQueryTargetMetadataKey(targetId)
657+
createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId)
711658
);
712659

713660
if (storageItem) {
@@ -737,7 +684,9 @@ export class WebStorageSharedClientState implements SharedClientState {
737684
}
738685

739686
clearQueryState(targetId: TargetId): void {
740-
this.removeItem(this.toWebStorageQueryTargetMetadataKey(targetId));
687+
this.removeItem(
688+
createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId)
689+
);
741690
}
742691

743692
updateQueryState(
@@ -891,12 +840,20 @@ export class WebStorageSharedClientState implements SharedClientState {
891840
state,
892841
error
893842
);
894-
const mutationKey = this.toWebStorageMutationBatchKey(batchId);
843+
const mutationKey = createWebStorageMutationBatchKey(
844+
this.persistenceKey,
845+
this.currentUser,
846+
batchId
847+
);
895848
this.setItem(mutationKey, mutationState.toWebStorageJSON());
896849
}
897850

898851
private removeMutationState(batchId: BatchId): void {
899-
const mutationKey = this.toWebStorageMutationBatchKey(batchId);
852+
const mutationKey = createWebStorageMutationBatchKey(
853+
this.persistenceKey,
854+
this.currentUser,
855+
batchId
856+
);
900857
this.removeItem(mutationKey);
901858
}
902859

@@ -913,37 +870,14 @@ export class WebStorageSharedClientState implements SharedClientState {
913870
state: QueryTargetState,
914871
error?: FirestoreError
915872
): void {
916-
const targetKey = this.toWebStorageQueryTargetMetadataKey(targetId);
873+
const targetKey = createWebStorageQueryTargetMetadataKey(
874+
this.persistenceKey,
875+
targetId
876+
);
917877
const targetMetadata = new QueryTargetMetadata(targetId, state, error);
918878
this.setItem(targetKey, targetMetadata.toWebStorageJSON());
919879
}
920880

921-
/** Assembles the key for a client state in WebStorage */
922-
private toWebStorageClientStateKey(clientId: ClientId): string {
923-
assert(
924-
clientId.indexOf('_') === -1,
925-
`Client key cannot contain '_', but was '${clientId}'`
926-
);
927-
928-
return `${CLIENT_STATE_KEY_PREFIX}_${this.persistenceKey}_${clientId}`;
929-
}
930-
931-
/** Assembles the key for a query state in WebStorage */
932-
private toWebStorageQueryTargetMetadataKey(targetId: TargetId): string {
933-
return `${QUERY_TARGET_KEY_PREFIX}_${this.persistenceKey}_${targetId}`;
934-
}
935-
936-
/** Assembles the key for a mutation batch in WebStorage */
937-
private toWebStorageMutationBatchKey(batchId: BatchId): string {
938-
let mutationKey = `${MUTATION_BATCH_KEY_PREFIX}_${this.persistenceKey}_${batchId}`;
939-
940-
if (this.currentUser.isAuthenticated()) {
941-
mutationKey += `_${this.currentUser.uid}`;
942-
}
943-
944-
return mutationKey;
945-
}
946-
947881
/**
948882
* Parses a client state key in WebStorage. Returns null if the key does not
949883
* match the expected key format.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { BatchId, MutationBatchState, TargetId } from '../core/types';
19+
import { QueryTargetState } from './shared_client_state_syncer';
20+
import { assert } from '../util/assert';
21+
import { ClientId } from './shared_client_state';
22+
import { User } from '../auth/user';
23+
24+
// The format of the LocalStorage key that stores the client state is:
25+
// firestore_clients_<persistence_prefix>_<instance_key>
26+
export const CLIENT_STATE_KEY_PREFIX = 'firestore_clients';
27+
28+
/** Assembles the key for a client state in WebStorage */
29+
export function createWebStorageClientStateKey(
30+
persistenceKey: string,
31+
clientId: ClientId
32+
): string {
33+
assert(
34+
clientId.indexOf('_') === -1,
35+
`Client key cannot contain '_', but was '${clientId}'`
36+
);
37+
38+
return `${CLIENT_STATE_KEY_PREFIX}_${persistenceKey}_${clientId}`;
39+
}
40+
41+
/**
42+
* The JSON representation of a clients's metadata as used during WebStorage
43+
* serialization. The ClientId is omitted here as it is encoded as part of the
44+
* key.
45+
*/
46+
export interface ClientStateSchema {
47+
activeTargetIds: number[];
48+
updateTimeMs: number;
49+
}
50+
51+
// The format of the WebStorage key that stores the mutation state is:
52+
// firestore_mutations_<persistence_prefix>_<batch_id>
53+
// (for unauthenticated users)
54+
// or: firestore_mutations_<persistence_prefix>_<batch_id>_<user_uid>
55+
//
56+
// 'user_uid' is last to avoid needing to escape '_' characters that it might
57+
// contain.
58+
export const MUTATION_BATCH_KEY_PREFIX = 'firestore_mutations';
59+
60+
/** Assembles the key for a mutation batch in WebStorage */
61+
export function createWebStorageMutationBatchKey(
62+
persistenceKey: string,
63+
user: User,
64+
batchId: BatchId
65+
): string {
66+
let mutationKey = `${MUTATION_BATCH_KEY_PREFIX}_${persistenceKey}_${batchId}`;
67+
68+
if (user.isAuthenticated()) {
69+
mutationKey += `_${user.uid}`;
70+
}
71+
72+
return mutationKey;
73+
}
74+
75+
/**
76+
* The JSON representation of a mutation batch's metadata as used during
77+
* WebStorage serialization. The UserId and BatchId is omitted as it is
78+
* encoded as part of the key.
79+
*/
80+
export interface MutationMetadataSchema {
81+
state: MutationBatchState;
82+
error?: { code: string; message: string }; // Only set when state === 'rejected'
83+
updateTimeMs: number;
84+
}
85+
86+
// The format of the WebStorage key that stores a query target's metadata is:
87+
// firestore_targets_<persistence_prefix>_<target_id>
88+
export const QUERY_TARGET_KEY_PREFIX = 'firestore_targets';
89+
90+
/** Assembles the key for a query state in WebStorage */
91+
export function createWebStorageQueryTargetMetadataKey(
92+
persistenceKey: string,
93+
targetId: TargetId
94+
): string {
95+
return `${QUERY_TARGET_KEY_PREFIX}_${persistenceKey}_${targetId}`;
96+
}
97+
98+
/**
99+
* The JSON representation of a query target's state as used during WebStorage
100+
* serialization. The TargetId is omitted as it is encoded as part of the key.
101+
*/
102+
export interface QueryTargetStateSchema {
103+
state: QueryTargetState;
104+
error?: { code: string; message: string }; // Only set when state === 'rejected'
105+
updateTimeMs: number;
106+
}
107+
108+
// The WebStorage prefix that stores the primary tab's online state. The
109+
// format of the key is:
110+
// firestore_online_state_<persistence_prefix>
111+
export const ONLINE_STATE_KEY_PREFIX = 'firestore_online_state';
112+
113+
/** Assembles the key for the online state of the primary tab. */
114+
export function createWebStorageOnlineStateKey(persistenceKey: string): string {
115+
return `${ONLINE_STATE_KEY_PREFIX}_${persistenceKey}`;
116+
}
117+
118+
/**
119+
* The JSON representation of the system's online state, as written by the
120+
* primary client.
121+
*/
122+
export interface SharedOnlineStateSchema {
123+
/**
124+
* The clientId of the client that wrote this onlineState value. Tracked so
125+
* that on startup, clients can check if this client is still active when
126+
* determining whether to apply this value or not.
127+
*/
128+
readonly clientId: string;
129+
readonly onlineState: string;
130+
}
131+
132+
// The WebStorage key prefix for the key that stores the last sequence number allocated. The key
133+
// looks like 'firestore_sequence_number_<persistence_prefix>'.
134+
export const SEQUENCE_NUMBER_KEY_PREFIX = 'firestore_sequence_number';
135+
136+
/** Assembles the key for the current sequence number. */
137+
export function createWebStorageSequenceNumberKey(
138+
persistenceKey: string
139+
): string {
140+
return `${SEQUENCE_NUMBER_KEY_PREFIX}_${persistenceKey}`;
141+
}

0 commit comments

Comments
 (0)