@fluidframework/debugger 2.0.0-internal.3.0.2 → 2.0.0-internal.3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.eslintrc.js +9 -12
  2. package/README.md +33 -33
  3. package/api-extractor.json +2 -2
  4. package/dist/fluidDebugger.d.ts.map +1 -1
  5. package/dist/fluidDebugger.js.map +1 -1
  6. package/dist/fluidDebuggerController.d.ts.map +1 -1
  7. package/dist/fluidDebuggerController.js +5 -2
  8. package/dist/fluidDebuggerController.js.map +1 -1
  9. package/dist/fluidDebuggerUi.d.ts.map +1 -1
  10. package/dist/fluidDebuggerUi.js +11 -5
  11. package/dist/fluidDebuggerUi.js.map +1 -1
  12. package/dist/messageSchema.js.map +1 -1
  13. package/dist/sanitize.js.map +1 -1
  14. package/dist/sanitizer.d.ts.map +1 -1
  15. package/dist/sanitizer.js +9 -5
  16. package/dist/sanitizer.js.map +1 -1
  17. package/lib/fluidDebugger.d.ts.map +1 -1
  18. package/lib/fluidDebugger.js.map +1 -1
  19. package/lib/fluidDebuggerController.d.ts.map +1 -1
  20. package/lib/fluidDebuggerController.js +5 -2
  21. package/lib/fluidDebuggerController.js.map +1 -1
  22. package/lib/fluidDebuggerUi.d.ts.map +1 -1
  23. package/lib/fluidDebuggerUi.js +11 -5
  24. package/lib/fluidDebuggerUi.js.map +1 -1
  25. package/lib/messageSchema.js.map +1 -1
  26. package/lib/sanitize.js.map +1 -1
  27. package/lib/sanitizer.d.ts.map +1 -1
  28. package/lib/sanitizer.js +9 -5
  29. package/lib/sanitizer.js.map +1 -1
  30. package/package.json +33 -32
  31. package/prettier.config.cjs +1 -1
  32. package/src/fluidDebugger.ts +30 -30
  33. package/src/fluidDebuggerController.ts +348 -322
  34. package/src/fluidDebuggerUi.ts +306 -281
  35. package/src/messageSchema.ts +367 -367
  36. package/src/sanitize.ts +29 -29
  37. package/src/sanitizer.ts +699 -638
  38. package/tsconfig.esnext.json +6 -6
  39. package/tsconfig.json +10 -15
@@ -5,23 +5,23 @@
5
5
 
6
6
  import { assert, Deferred } from "@fluidframework/common-utils";
7
7
  import {
8
- IDocumentService,
9
- IDocumentStorageService,
10
- IDocumentDeltaStorageService,
8
+ IDocumentService,
9
+ IDocumentStorageService,
10
+ IDocumentDeltaStorageService,
11
11
  } from "@fluidframework/driver-definitions";
12
12
  import { readAndParse } from "@fluidframework/driver-utils";
13
13
  import {
14
- IDocumentAttributes,
15
- ISequencedDocumentMessage,
16
- ISnapshotTree,
17
- IVersion,
14
+ IDocumentAttributes,
15
+ ISequencedDocumentMessage,
16
+ ISnapshotTree,
17
+ IVersion,
18
18
  } from "@fluidframework/protocol-definitions";
19
19
  import {
20
- FileSnapshotReader,
21
- IFileSnapshot,
22
- ReadDocumentStorageServiceBase,
23
- ReplayController,
24
- SnapshotStorage,
20
+ FileSnapshotReader,
21
+ IFileSnapshot,
22
+ ReadDocumentStorageServiceBase,
23
+ ReplayController,
24
+ SnapshotStorage,
25
25
  } from "@fluidframework/replay-driver";
26
26
  import { IDebuggerController, IDebuggerUI } from "./fluidDebuggerUi";
27
27
  import { Sanitizer } from "./sanitizer";
@@ -32,96 +32,100 @@ export type debuggerUIFactory = (controller: IDebuggerController) => IDebuggerUI
32
32
  * Replay controller that uses pop-up window to control op playback
33
33
  */
34
34
  export class DebugReplayController extends ReplayController implements IDebuggerController {
35
- public static create(
36
- createUi: debuggerUIFactory): DebugReplayController | null {
37
- if (typeof localStorage === "object" && localStorage !== null && localStorage.FluidDebugger) {
38
- const controller = new DebugReplayController();
39
- const ui = createUi(controller);
40
- if (ui) {
41
- return controller;
42
- }
43
- }
44
- return null;
45
- }
46
-
47
- protected static readonly WindowClosedSeq = -1; // Seq# to indicate that user closed window
48
-
49
- protected static async seqFromTree(
50
- documentStorageService: IDocumentStorageService,
51
- tree: ISnapshotTree | null): Promise<number> {
52
- if (!tree) {
53
- return 0;
54
- }
55
-
56
- const attributesHash = tree.trees[".protocol"].blobs.attributes;
57
- const attrib = await readAndParse<IDocumentAttributes>(documentStorageService, attributesHash);
58
- return attrib.sequenceNumber;
59
- }
60
-
61
- protected ui: IDebuggerUI = null as any as IDebuggerUI; // Not to check on every line that it's not null
62
- protected stepsDeferred?: Deferred<number>;
63
- protected startSeqDeferred = new Deferred<number>();
64
-
65
- // True will cause us ping server indefinitely waiting for new ops
66
- protected retryFetchOpsOnEndOfFile = false;
67
-
68
- protected documentService?: IDocumentService;
69
- protected documentStorageService?: IDocumentStorageService;
70
- protected versions: IVersion[] = [];
71
- protected stepsToPlay: number = 0;
72
- protected lastOpReached = false;
73
- protected versionCount = 0;
74
-
75
- protected storage?: ReadDocumentStorageServiceBase;
76
-
77
- // Member to prevent repeated initialization in initStorage(...), which also
78
- // returns if this controller should be used or function as a passthrough
79
- private shouldUseController: boolean | undefined;
80
-
81
- public connectToUi(ui: IDebuggerUI): void {
82
- this.ui = ui;
83
- }
84
-
85
- public onClose() {
86
- this.startSeqDeferred.resolve(DebugReplayController.WindowClosedSeq);
87
- }
88
-
89
- public async onVersionSelection(version: IVersion) {
90
- if (!this.documentStorageService) {
91
- throw new Error("onVersionSelection: no storage");
92
- }
93
-
94
- const tree = await this.documentStorageService.getSnapshotTree(version);
95
- const seq = await DebugReplayController.seqFromTree(this.documentStorageService, tree);
96
- this.resolveStorage(
97
- seq,
98
- new SnapshotStorage(this.documentStorageService, tree),
99
- version);
100
- }
101
-
102
- public onOpButtonClick(steps: number) {
103
- if (this.stepsDeferred && !Number.isNaN(steps) && steps > 0) {
104
- this.stepsDeferred.resolve(steps);
105
- }
106
- }
107
-
108
- public onSnapshotFileSelection(file: File) {
109
- if (!/^snapshot.*\.json/.exec(file.name)) {
110
- alert(`Incorrect file name: ${file.name}`);
111
- return;
112
- }
113
- if (/.*_expanded.*/.exec(file.name)) {
114
- alert(`Incorrect file name - please use non-extended files: ${file.name}`);
115
- return;
116
- }
117
-
118
- const reader = new FileReader();
119
- reader.onload = async () => {
120
- if (this.documentStorageService) {
121
- const text = reader.result as string;
122
- try {
123
- const json: IFileSnapshot = JSON.parse(text) as IFileSnapshot;
124
- /*
35
+ public static create(createUi: debuggerUIFactory): DebugReplayController | null {
36
+ if (
37
+ typeof localStorage === "object" &&
38
+ localStorage !== null &&
39
+ localStorage.FluidDebugger
40
+ ) {
41
+ const controller = new DebugReplayController();
42
+ const ui = createUi(controller);
43
+ if (ui) {
44
+ return controller;
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+
50
+ protected static readonly WindowClosedSeq = -1; // Seq# to indicate that user closed window
51
+
52
+ protected static async seqFromTree(
53
+ documentStorageService: IDocumentStorageService,
54
+ tree: ISnapshotTree | null,
55
+ ): Promise<number> {
56
+ if (!tree) {
57
+ return 0;
58
+ }
59
+
60
+ const attributesHash = tree.trees[".protocol"].blobs.attributes;
61
+ const attrib = await readAndParse<IDocumentAttributes>(
62
+ documentStorageService,
63
+ attributesHash,
64
+ );
65
+ return attrib.sequenceNumber;
66
+ }
67
+
68
+ protected ui: IDebuggerUI = null as any as IDebuggerUI; // Not to check on every line that it's not null
69
+ protected stepsDeferred?: Deferred<number>;
70
+ protected startSeqDeferred = new Deferred<number>();
71
+
72
+ // True will cause us ping server indefinitely waiting for new ops
73
+ protected retryFetchOpsOnEndOfFile = false;
74
+
75
+ protected documentService?: IDocumentService;
76
+ protected documentStorageService?: IDocumentStorageService;
77
+ protected versions: IVersion[] = [];
78
+ protected stepsToPlay: number = 0;
79
+ protected lastOpReached = false;
80
+ protected versionCount = 0;
81
+
82
+ protected storage?: ReadDocumentStorageServiceBase;
83
+
84
+ // Member to prevent repeated initialization in initStorage(...), which also
85
+ // returns if this controller should be used or function as a passthrough
86
+ private shouldUseController: boolean | undefined;
87
+
88
+ public connectToUi(ui: IDebuggerUI): void {
89
+ this.ui = ui;
90
+ }
91
+
92
+ public onClose() {
93
+ this.startSeqDeferred.resolve(DebugReplayController.WindowClosedSeq);
94
+ }
95
+
96
+ public async onVersionSelection(version: IVersion) {
97
+ if (!this.documentStorageService) {
98
+ throw new Error("onVersionSelection: no storage");
99
+ }
100
+
101
+ const tree = await this.documentStorageService.getSnapshotTree(version);
102
+ const seq = await DebugReplayController.seqFromTree(this.documentStorageService, tree);
103
+ this.resolveStorage(seq, new SnapshotStorage(this.documentStorageService, tree), version);
104
+ }
105
+
106
+ public onOpButtonClick(steps: number) {
107
+ if (this.stepsDeferred && !Number.isNaN(steps) && steps > 0) {
108
+ this.stepsDeferred.resolve(steps);
109
+ }
110
+ }
111
+
112
+ public onSnapshotFileSelection(file: File) {
113
+ if (!/^snapshot.*\.json/.exec(file.name)) {
114
+ alert(`Incorrect file name: ${file.name}`);
115
+ return;
116
+ }
117
+ if (/.*_expanded.*/.exec(file.name)) {
118
+ alert(`Incorrect file name - please use non-extended files: ${file.name}`);
119
+ return;
120
+ }
121
+
122
+ const reader = new FileReader();
123
+ reader.onload = async () => {
124
+ if (this.documentStorageService) {
125
+ const text = reader.result as string;
126
+ try {
127
+ const json: IFileSnapshot = JSON.parse(text) as IFileSnapshot;
128
+ /*
125
129
  Const docStorage = this.documentStorageService;
126
130
  const storage = {
127
131
  read: (blobId: string) => this.read(docStorage, blobId),
@@ -131,226 +135,248 @@ export class DebugReplayController extends ReplayController implements IDebugger
131
135
  tree);
132
136
  this.startSeqDeferred.resolve(seq);
133
137
  */
134
- // No ability to load ops, so just say - pick up from infinite op.
135
- this.retryFetchOpsOnEndOfFile = false;
136
- this.lastOpReached = true;
137
- this.resolveStorage(
138
- Number.MAX_SAFE_INTEGER,
139
- new FileSnapshotReader(json),
140
- file.name);
141
- } catch (error) {
142
- alert(`Error parsing file: ${error}`);
143
- return;
144
- }
145
- }
146
- };
147
- reader.readAsText(file, "utf-8");
148
- }
149
-
150
- public async onDownloadOpsButtonClick(anonymize: boolean): Promise<string> {
151
- if (this.documentService === undefined) {
152
- throw new Error("DocumentService required");
153
- }
154
-
155
- const documentDeltaStorageService = await this.documentService.connectToDeltaStorage();
156
- let messages = await this.fetchOpsFromDeltaStorage(documentDeltaStorageService);
157
-
158
- if (anonymize) {
159
- const sanitizer = new Sanitizer(messages, false /* fullScrub */, false /* noBail */);
160
- messages = sanitizer.sanitize();
161
- }
162
-
163
- return JSON.stringify(messages, undefined, 2);
164
- }
165
-
166
- private async fetchOpsFromDeltaStorage(documentDeltaStorageService): Promise<ISequencedDocumentMessage[]> {
167
- const deltaGenerator = generateSequencedMessagesFromDeltaStorage(documentDeltaStorageService);
168
- let messages: ISequencedDocumentMessage[] = [];
169
- for await (const message of deltaGenerator) {
170
- messages = messages.concat(message);
171
- }
172
- return messages;
173
- }
174
-
175
- public fetchTo(currentOp: number): number | undefined {
176
- return undefined;
177
- }
178
-
179
- // Returns true if version / file / ops selections is made.
180
- public isSelectionMade() {
181
- return this.storage !== undefined;
182
- }
183
-
184
- public async downloadVersionInfo(
185
- documentStorageService: IDocumentStorageService,
186
- prevRequest: Promise<void>,
187
- index: number,
188
- version: IVersion): Promise<void> {
189
- if (this.isSelectionMade()) {
190
- return;
191
- }
192
-
193
- await prevRequest;
194
-
195
- const treeV = await documentStorageService.getSnapshotTree(version);
196
- const seqV = await DebugReplayController.seqFromTree(documentStorageService, treeV);
197
-
198
- if (!this.isSelectionMade()) {
199
- this.versionCount--;
200
- this.ui.updateVersionText(this.versionCount);
201
- this.ui.updateVersion(index, version, seqV);
202
- }
203
- }
204
-
205
- public async initStorage(documentService: IDocumentService): Promise<boolean> {
206
- if (this.shouldUseController !== undefined) {
207
- return this.shouldUseController;
208
- }
209
-
210
- assert(!!documentService, 0x080 /* "Invalid document service!" */);
211
- assert(!this.documentService, 0x081 /* "Document service already set!" */);
212
- assert(!this.documentStorageService, 0x082 /* "Document storage service already set!" */);
213
- this.documentService = documentService;
214
- this.documentStorageService = await documentService.connectToStorage();
215
-
216
- // User can chose "file" at any moment in time!
217
- if (!this.isSelectionMade()) {
218
- this.versions = await this.documentStorageService.getVersions("", 50);
219
- if (!this.isSelectionMade()) {
220
- this.ui.addVersions(this.versions);
221
- this.ui.updateVersionText(this.versionCount);
222
- }
223
- }
224
-
225
- this.versionCount = this.versions.length;
226
-
227
- // Download all versions - do 10 downloads in parallel to avoid being throttled
228
- const buckets = 10;
229
- const work: Promise<void>[] = [];
230
- for (let i = 0; i < buckets; i++) {
231
- let prevRequest = Promise.resolve();
232
- for (let index = i; index < this.versions.length; index += buckets) {
233
- const version = this.versions[index];
234
- prevRequest = this.downloadVersionInfo(this.documentStorageService, prevRequest, index, version);
235
- }
236
- work.push(prevRequest);
237
- }
238
-
239
- // Don't wait for stuff to populate.
240
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
241
- Promise.all(work).then(() => {
242
- this.ui.updateVersionText(0);
243
- });
244
-
245
- // This hangs until the user makes a selection or closes the window.
246
- this.shouldUseController = await this.startSeqDeferred.promise !== DebugReplayController.WindowClosedSeq;
247
-
248
- assert(this.isSelectionMade() === this.shouldUseController,
249
- 0x083 /* "User selection status does not match replay controller use status!" */);
250
- return this.shouldUseController;
251
- }
252
-
253
- public async readBlob(blobId: string): Promise<ArrayBufferLike> {
254
- if (this.storage !== undefined) {
255
- return this.storage.readBlob(blobId);
256
- }
257
- throw new Error("Reading blob before storage is setup properly");
258
- }
259
-
260
- public async getVersions(
261
- versionId: string | null,
262
- count: number): Promise<IVersion[]> {
263
- if (this.storage !== undefined) {
264
- return this.storage.getVersions(versionId, count);
265
- }
266
- throw new Error("initStorage() was not called!");
267
- }
268
-
269
- public async getSnapshotTree(versionRequested?: IVersion): Promise<ISnapshotTree | null> {
270
- if (this.storage !== undefined) {
271
- return this.storage.getSnapshotTree(versionRequested);
272
- }
273
- throw new Error("Reading snapshot tree before storage is setup properly");
274
- }
275
-
276
- public async getStartingOpSequence() {
277
- return this.startSeqDeferred.promise;
278
- }
279
-
280
- /**
281
- * Return true if we are done processing ops
282
- */
283
- public isDoneFetch(currentOp: number, lastTimeStamp?: number): boolean {
284
- if (lastTimeStamp === undefined) {
285
- this.lastOpReached = true;
286
- if (currentOp === Number.MAX_SAFE_INTEGER) {
287
- this.ui.updateLastOpText(-1, false);
288
- } else {
289
- this.ui.updateLastOpText(currentOp, false);
290
- }
291
- } else {
292
- this.ui.updateLastOpText(currentOp, true);
293
- }
294
- return this.lastOpReached && !this.retryFetchOpsOnEndOfFile;
295
- }
296
-
297
- public async replay(
298
- emitter: (op: ISequencedDocumentMessage[]) => void,
299
- fetchedOps: ISequencedDocumentMessage[]): Promise<void> {
300
- let _fetchedOps = fetchedOps;
301
- // eslint-disable-next-line no-constant-condition
302
- while (true) {
303
- if (_fetchedOps.length === 0) {
304
- this.ui.updateNextOpText([]);
305
- return;
306
- }
307
-
308
- if (this.stepsToPlay === 0) {
309
- this.ui.disableNextOpButton(false);
310
- this.stepsDeferred = new Deferred<number>();
311
-
312
- this.ui.updateNextOpText(_fetchedOps);
313
-
314
- this.stepsToPlay = await this.stepsDeferred.promise;
315
-
316
- this.stepsDeferred = undefined;
317
- this.ui.disableNextOpButton(true);
318
- }
319
-
320
- let playOps: ISequencedDocumentMessage[];
321
- if (this.stepsToPlay >= _fetchedOps.length) {
322
- playOps = _fetchedOps;
323
- this.stepsToPlay -= _fetchedOps.length;
324
- _fetchedOps = [];
325
- } else {
326
- playOps = _fetchedOps.splice(0, this.stepsToPlay);
327
- this.stepsToPlay = 0;
328
- }
329
- emitter(playOps);
330
- }
331
- }
332
-
333
- protected resolveStorage(
334
- seq: number,
335
- storage: ReadDocumentStorageServiceBase,
336
- version: IVersion | string) {
337
- assert(!this.isSelectionMade(), 0x084 /* "On storage resolve, user selection already made!" */);
338
- assert(!!storage, 0x085 /* "On storage resolve, missing storage!" */);
339
- this.storage = storage;
340
- assert(this.isSelectionMade(), 0x086 /* "After storage resolve, user selection status still false!" */);
341
-
342
- this.ui.versionSelected(seq, version);
343
- this.startSeqDeferred.resolve(seq);
344
- }
138
+ // No ability to load ops, so just say - pick up from infinite op.
139
+ this.retryFetchOpsOnEndOfFile = false;
140
+ this.lastOpReached = true;
141
+ this.resolveStorage(
142
+ Number.MAX_SAFE_INTEGER,
143
+ new FileSnapshotReader(json),
144
+ file.name,
145
+ );
146
+ } catch (error) {
147
+ alert(`Error parsing file: ${error}`);
148
+ return;
149
+ }
150
+ }
151
+ };
152
+ reader.readAsText(file, "utf-8");
153
+ }
154
+
155
+ public async onDownloadOpsButtonClick(anonymize: boolean): Promise<string> {
156
+ if (this.documentService === undefined) {
157
+ throw new Error("DocumentService required");
158
+ }
159
+
160
+ const documentDeltaStorageService = await this.documentService.connectToDeltaStorage();
161
+ let messages = await this.fetchOpsFromDeltaStorage(documentDeltaStorageService);
162
+
163
+ if (anonymize) {
164
+ const sanitizer = new Sanitizer(messages, false /* fullScrub */, false /* noBail */);
165
+ messages = sanitizer.sanitize();
166
+ }
167
+
168
+ return JSON.stringify(messages, undefined, 2);
169
+ }
170
+
171
+ private async fetchOpsFromDeltaStorage(
172
+ documentDeltaStorageService,
173
+ ): Promise<ISequencedDocumentMessage[]> {
174
+ const deltaGenerator = generateSequencedMessagesFromDeltaStorage(
175
+ documentDeltaStorageService,
176
+ );
177
+ let messages: ISequencedDocumentMessage[] = [];
178
+ for await (const message of deltaGenerator) {
179
+ messages = messages.concat(message);
180
+ }
181
+ return messages;
182
+ }
183
+
184
+ public fetchTo(currentOp: number): number | undefined {
185
+ return undefined;
186
+ }
187
+
188
+ // Returns true if version / file / ops selections is made.
189
+ public isSelectionMade() {
190
+ return this.storage !== undefined;
191
+ }
192
+
193
+ public async downloadVersionInfo(
194
+ documentStorageService: IDocumentStorageService,
195
+ prevRequest: Promise<void>,
196
+ index: number,
197
+ version: IVersion,
198
+ ): Promise<void> {
199
+ if (this.isSelectionMade()) {
200
+ return;
201
+ }
202
+
203
+ await prevRequest;
204
+
205
+ const treeV = await documentStorageService.getSnapshotTree(version);
206
+ const seqV = await DebugReplayController.seqFromTree(documentStorageService, treeV);
207
+
208
+ if (!this.isSelectionMade()) {
209
+ this.versionCount--;
210
+ this.ui.updateVersionText(this.versionCount);
211
+ this.ui.updateVersion(index, version, seqV);
212
+ }
213
+ }
214
+
215
+ public async initStorage(documentService: IDocumentService): Promise<boolean> {
216
+ if (this.shouldUseController !== undefined) {
217
+ return this.shouldUseController;
218
+ }
219
+
220
+ assert(!!documentService, 0x080 /* "Invalid document service!" */);
221
+ assert(!this.documentService, 0x081 /* "Document service already set!" */);
222
+ assert(!this.documentStorageService, 0x082 /* "Document storage service already set!" */);
223
+ this.documentService = documentService;
224
+ this.documentStorageService = await documentService.connectToStorage();
225
+
226
+ // User can chose "file" at any moment in time!
227
+ if (!this.isSelectionMade()) {
228
+ this.versions = await this.documentStorageService.getVersions("", 50);
229
+ if (!this.isSelectionMade()) {
230
+ this.ui.addVersions(this.versions);
231
+ this.ui.updateVersionText(this.versionCount);
232
+ }
233
+ }
234
+
235
+ this.versionCount = this.versions.length;
236
+
237
+ // Download all versions - do 10 downloads in parallel to avoid being throttled
238
+ const buckets = 10;
239
+ const work: Promise<void>[] = [];
240
+ for (let i = 0; i < buckets; i++) {
241
+ let prevRequest = Promise.resolve();
242
+ for (let index = i; index < this.versions.length; index += buckets) {
243
+ const version = this.versions[index];
244
+ prevRequest = this.downloadVersionInfo(
245
+ this.documentStorageService,
246
+ prevRequest,
247
+ index,
248
+ version,
249
+ );
250
+ }
251
+ work.push(prevRequest);
252
+ }
253
+
254
+ // Don't wait for stuff to populate.
255
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
256
+ Promise.all(work).then(() => {
257
+ this.ui.updateVersionText(0);
258
+ });
259
+
260
+ // This hangs until the user makes a selection or closes the window.
261
+ this.shouldUseController =
262
+ (await this.startSeqDeferred.promise) !== DebugReplayController.WindowClosedSeq;
263
+
264
+ assert(
265
+ this.isSelectionMade() === this.shouldUseController,
266
+ 0x083 /* "User selection status does not match replay controller use status!" */,
267
+ );
268
+ return this.shouldUseController;
269
+ }
270
+
271
+ public async readBlob(blobId: string): Promise<ArrayBufferLike> {
272
+ if (this.storage !== undefined) {
273
+ return this.storage.readBlob(blobId);
274
+ }
275
+ throw new Error("Reading blob before storage is setup properly");
276
+ }
277
+
278
+ public async getVersions(versionId: string | null, count: number): Promise<IVersion[]> {
279
+ if (this.storage !== undefined) {
280
+ return this.storage.getVersions(versionId, count);
281
+ }
282
+ throw new Error("initStorage() was not called!");
283
+ }
284
+
285
+ public async getSnapshotTree(versionRequested?: IVersion): Promise<ISnapshotTree | null> {
286
+ if (this.storage !== undefined) {
287
+ return this.storage.getSnapshotTree(versionRequested);
288
+ }
289
+ throw new Error("Reading snapshot tree before storage is setup properly");
290
+ }
291
+
292
+ public async getStartingOpSequence() {
293
+ return this.startSeqDeferred.promise;
294
+ }
295
+
296
+ /**
297
+ * Return true if we are done processing ops
298
+ */
299
+ public isDoneFetch(currentOp: number, lastTimeStamp?: number): boolean {
300
+ if (lastTimeStamp === undefined) {
301
+ this.lastOpReached = true;
302
+ if (currentOp === Number.MAX_SAFE_INTEGER) {
303
+ this.ui.updateLastOpText(-1, false);
304
+ } else {
305
+ this.ui.updateLastOpText(currentOp, false);
306
+ }
307
+ } else {
308
+ this.ui.updateLastOpText(currentOp, true);
309
+ }
310
+ return this.lastOpReached && !this.retryFetchOpsOnEndOfFile;
311
+ }
312
+
313
+ public async replay(
314
+ emitter: (op: ISequencedDocumentMessage[]) => void,
315
+ fetchedOps: ISequencedDocumentMessage[],
316
+ ): Promise<void> {
317
+ let _fetchedOps = fetchedOps;
318
+ // eslint-disable-next-line no-constant-condition
319
+ while (true) {
320
+ if (_fetchedOps.length === 0) {
321
+ this.ui.updateNextOpText([]);
322
+ return;
323
+ }
324
+
325
+ if (this.stepsToPlay === 0) {
326
+ this.ui.disableNextOpButton(false);
327
+ this.stepsDeferred = new Deferred<number>();
328
+
329
+ this.ui.updateNextOpText(_fetchedOps);
330
+
331
+ this.stepsToPlay = await this.stepsDeferred.promise;
332
+
333
+ this.stepsDeferred = undefined;
334
+ this.ui.disableNextOpButton(true);
335
+ }
336
+
337
+ let playOps: ISequencedDocumentMessage[];
338
+ if (this.stepsToPlay >= _fetchedOps.length) {
339
+ playOps = _fetchedOps;
340
+ this.stepsToPlay -= _fetchedOps.length;
341
+ _fetchedOps = [];
342
+ } else {
343
+ playOps = _fetchedOps.splice(0, this.stepsToPlay);
344
+ this.stepsToPlay = 0;
345
+ }
346
+ emitter(playOps);
347
+ }
348
+ }
349
+
350
+ protected resolveStorage(
351
+ seq: number,
352
+ storage: ReadDocumentStorageServiceBase,
353
+ version: IVersion | string,
354
+ ) {
355
+ assert(
356
+ !this.isSelectionMade(),
357
+ 0x084 /* "On storage resolve, user selection already made!" */,
358
+ );
359
+ assert(!!storage, 0x085 /* "On storage resolve, missing storage!" */);
360
+ this.storage = storage;
361
+ assert(
362
+ this.isSelectionMade(),
363
+ 0x086 /* "After storage resolve, user selection status still false!" */,
364
+ );
365
+
366
+ this.ui.versionSelected(seq, version);
367
+ this.startSeqDeferred.resolve(seq);
368
+ }
345
369
  }
346
370
 
347
- async function* generateSequencedMessagesFromDeltaStorage(deltaStorage: IDocumentDeltaStorageService) {
348
- const stream = deltaStorage.fetchMessages(1, undefined);
349
- while (true) {
350
- const result = await stream.read();
351
- if (result.done) {
352
- return;
353
- }
354
- yield result.value;
355
- }
371
+ async function* generateSequencedMessagesFromDeltaStorage(
372
+ deltaStorage: IDocumentDeltaStorageService,
373
+ ) {
374
+ const stream = deltaStorage.fetchMessages(1, undefined);
375
+ while (true) {
376
+ const result = await stream.read();
377
+ if (result.done) {
378
+ return;
379
+ }
380
+ yield result.value;
381
+ }
356
382
  }