@fluidframework/debugger 1.4.0-121020 → 2.0.0-dev-rc.1.0.0.225277

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