@fluidframework/debugger 2.0.0-internal.3.0.0 → 2.0.0-internal.3.1.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.
- package/.eslintrc.js +9 -12
- package/README.md +33 -33
- package/api-extractor.json +2 -2
- package/dist/fluidDebugger.d.ts.map +1 -1
- package/dist/fluidDebugger.js.map +1 -1
- package/dist/fluidDebuggerController.d.ts.map +1 -1
- package/dist/fluidDebuggerController.js +5 -2
- package/dist/fluidDebuggerController.js.map +1 -1
- package/dist/fluidDebuggerUi.d.ts.map +1 -1
- package/dist/fluidDebuggerUi.js +11 -5
- package/dist/fluidDebuggerUi.js.map +1 -1
- package/dist/messageSchema.js.map +1 -1
- package/dist/sanitize.js.map +1 -1
- package/dist/sanitizer.d.ts.map +1 -1
- package/dist/sanitizer.js +9 -5
- package/dist/sanitizer.js.map +1 -1
- package/lib/fluidDebugger.d.ts.map +1 -1
- package/lib/fluidDebugger.js.map +1 -1
- package/lib/fluidDebuggerController.d.ts.map +1 -1
- package/lib/fluidDebuggerController.js +5 -2
- package/lib/fluidDebuggerController.js.map +1 -1
- package/lib/fluidDebuggerUi.d.ts.map +1 -1
- package/lib/fluidDebuggerUi.js +11 -5
- package/lib/fluidDebuggerUi.js.map +1 -1
- package/lib/messageSchema.js.map +1 -1
- package/lib/sanitize.js.map +1 -1
- package/lib/sanitizer.d.ts.map +1 -1
- package/lib/sanitizer.js +9 -5
- package/lib/sanitizer.js.map +1 -1
- package/package.json +68 -67
- package/prettier.config.cjs +1 -1
- package/src/fluidDebugger.ts +30 -30
- package/src/fluidDebuggerController.ts +348 -322
- package/src/fluidDebuggerUi.ts +306 -281
- package/src/messageSchema.ts +367 -367
- package/src/sanitize.ts +29 -29
- package/src/sanitizer.ts +699 -638
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +10 -15
|
@@ -5,23 +5,23 @@
|
|
|
5
5
|
|
|
6
6
|
import { assert, Deferred } from "@fluidframework/common-utils";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
IDocumentService,
|
|
9
|
+
IDocumentStorageService,
|
|
10
|
+
IDocumentDeltaStorageService,
|
|
11
11
|
} from "@fluidframework/driver-definitions";
|
|
12
12
|
import { readAndParse } from "@fluidframework/driver-utils";
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
IDocumentAttributes,
|
|
15
|
+
ISequencedDocumentMessage,
|
|
16
|
+
ISnapshotTree,
|
|
17
|
+
IVersion,
|
|
18
18
|
} from "@fluidframework/protocol-definitions";
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
}
|