@fluidframework/debugger 1.4.0-115997 → 2.0.0-dev-rc.1.0.0.224419

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 (110) hide show
  1. package/.eslintrc.js +9 -10
  2. package/CHANGELOG.md +117 -0
  3. package/README.md +33 -33
  4. package/api-extractor-lint.json +4 -0
  5. package/api-extractor.json +2 -2
  6. package/api-report/debugger.api.md +157 -0
  7. package/dist/debugger-alpha.d.ts +36 -0
  8. package/dist/debugger-beta.d.ts +35 -0
  9. package/dist/debugger-public.d.ts +35 -0
  10. package/dist/debugger-untrimmed.d.ts +193 -0
  11. package/dist/{fluidDebugger.js → fluidDebugger.cjs} +12 -4
  12. package/dist/fluidDebugger.cjs.map +1 -0
  13. package/dist/fluidDebugger.d.ts +8 -2
  14. package/dist/fluidDebugger.d.ts.map +1 -1
  15. package/dist/{fluidDebuggerController.js → fluidDebuggerController.cjs} +29 -56
  16. package/dist/fluidDebuggerController.cjs.map +1 -0
  17. package/dist/fluidDebuggerController.d.ts +6 -2
  18. package/dist/fluidDebuggerController.d.ts.map +1 -1
  19. package/dist/{fluidDebuggerUi.js → fluidDebuggerUi.cjs} +45 -50
  20. package/dist/fluidDebuggerUi.cjs.map +1 -0
  21. package/dist/fluidDebuggerUi.d.ts +9 -0
  22. package/dist/fluidDebuggerUi.d.ts.map +1 -1
  23. package/dist/index.cjs +14 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/{messageSchema.js → messageSchema.cjs} +1 -12
  28. package/dist/messageSchema.cjs.map +1 -0
  29. package/dist/messageSchema.d.ts.map +1 -1
  30. package/dist/{sanitize.js → sanitize.cjs} +2 -2
  31. package/dist/sanitize.cjs.map +1 -0
  32. package/dist/{sanitizer.js → sanitizer.cjs} +29 -34
  33. package/dist/sanitizer.cjs.map +1 -0
  34. package/dist/sanitizer.d.ts.map +1 -1
  35. package/dist/tsdoc-metadata.json +11 -0
  36. package/lib/debugger-alpha.d.mts +36 -0
  37. package/lib/debugger-beta.d.mts +35 -0
  38. package/lib/debugger-public.d.mts +35 -0
  39. package/lib/debugger-untrimmed.d.mts +193 -0
  40. package/lib/{fluidDebugger.d.ts → fluidDebugger.d.mts} +8 -2
  41. package/lib/fluidDebugger.d.mts.map +1 -0
  42. package/lib/{fluidDebugger.js → fluidDebugger.mjs} +11 -3
  43. package/lib/fluidDebugger.mjs.map +1 -0
  44. package/lib/{fluidDebuggerController.d.ts → fluidDebuggerController.d.mts} +7 -3
  45. package/lib/fluidDebuggerController.d.mts.map +1 -0
  46. package/lib/{fluidDebuggerController.js → fluidDebuggerController.mjs} +20 -47
  47. package/lib/fluidDebuggerController.mjs.map +1 -0
  48. package/lib/{fluidDebuggerUi.d.ts → fluidDebuggerUi.d.mts} +9 -0
  49. package/lib/fluidDebuggerUi.d.mts.map +1 -0
  50. package/lib/{fluidDebuggerUi.js → fluidDebuggerUi.mjs} +44 -49
  51. package/lib/fluidDebuggerUi.mjs.map +1 -0
  52. package/lib/index.d.mts +8 -0
  53. package/lib/index.d.mts.map +1 -0
  54. package/lib/index.mjs +8 -0
  55. package/lib/index.mjs.map +1 -0
  56. package/lib/messageSchema.d.mts.map +1 -0
  57. package/lib/{messageSchema.js → messageSchema.mjs} +1 -12
  58. package/lib/messageSchema.mjs.map +1 -0
  59. package/lib/{sanitize.js → sanitize.mjs} +2 -16
  60. package/lib/sanitize.mjs.map +1 -0
  61. package/lib/{sanitizer.d.ts → sanitizer.d.mts} +0 -14
  62. package/lib/sanitizer.d.mts.map +1 -0
  63. package/lib/{sanitizer.js → sanitizer.mjs} +19 -42
  64. package/lib/sanitizer.mjs.map +1 -0
  65. package/lib/test/types/validateDebuggerPrevious.generated.d.mts +2 -0
  66. package/lib/test/types/validateDebuggerPrevious.generated.d.mts.map +1 -0
  67. package/lib/test/types/{validateDebuggerPrevious.js → validateDebuggerPrevious.generated.mjs} +5 -5
  68. package/lib/test/types/validateDebuggerPrevious.generated.mjs.map +1 -0
  69. package/package.json +76 -44
  70. package/prettier.config.cjs +8 -0
  71. package/src/fluidDebugger.ts +38 -30
  72. package/src/fluidDebuggerController.ts +353 -323
  73. package/src/fluidDebuggerUi.ts +316 -293
  74. package/src/index.ts +3 -3
  75. package/src/messageSchema.ts +356 -367
  76. package/src/sanitize.ts +29 -29
  77. package/src/sanitizer.ts +702 -651
  78. package/tsconfig.json +13 -15
  79. package/dist/fluidDebugger.js.map +0 -1
  80. package/dist/fluidDebuggerController.js.map +0 -1
  81. package/dist/fluidDebuggerUi.js.map +0 -1
  82. package/dist/index.js +0 -20
  83. package/dist/index.js.map +0 -1
  84. package/dist/messageSchema.js.map +0 -1
  85. package/dist/sanitize.js.map +0 -1
  86. package/dist/sanitizer.js.map +0 -1
  87. package/images/Screenshot1.jpg +0 -0
  88. package/images/Screenshot2.jpg +0 -0
  89. package/lib/fluidDebugger.d.ts.map +0 -1
  90. package/lib/fluidDebugger.js.map +0 -1
  91. package/lib/fluidDebuggerController.d.ts.map +0 -1
  92. package/lib/fluidDebuggerController.js.map +0 -1
  93. package/lib/fluidDebuggerUi.d.ts.map +0 -1
  94. package/lib/fluidDebuggerUi.js.map +0 -1
  95. package/lib/index.d.ts +0 -8
  96. package/lib/index.d.ts.map +0 -1
  97. package/lib/index.js +0 -8
  98. package/lib/index.js.map +0 -1
  99. package/lib/messageSchema.d.ts.map +0 -1
  100. package/lib/messageSchema.js.map +0 -1
  101. package/lib/sanitize.js.map +0 -1
  102. package/lib/sanitizer.d.ts.map +0 -1
  103. package/lib/sanitizer.js.map +0 -1
  104. package/lib/test/types/validateDebuggerPrevious.d.ts +0 -2
  105. package/lib/test/types/validateDebuggerPrevious.d.ts.map +0 -1
  106. package/lib/test/types/validateDebuggerPrevious.js.map +0 -1
  107. package/tsconfig.esnext.json +0 -7
  108. /package/lib/{messageSchema.d.ts → messageSchema.d.mts} +0 -0
  109. /package/lib/{sanitize.d.ts → sanitize.d.mts} +0 -0
  110. /package/lib/{sanitize.d.ts.map → sanitize.d.mts.map} +0 -0
@@ -3,102 +3,107 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { assert } from "@fluidframework/common-utils";
6
+ import { assert } from "@fluidframework/core-utils";
7
7
  import { ISequencedDocumentMessage, IVersion } from "@fluidframework/protocol-definitions";
8
8
 
9
+ /**
10
+ * @internal
11
+ */
9
12
  export interface IDebuggerUI {
10
- /**
11
- * Version information is provided.
12
- * Expect updates (information about seq#, timestamp) through updateVersion() calls
13
- */
14
- addVersions(version: IVersion[]): void;
15
-
16
- /**
17
- * Call when new version is downloaded from storage
18
- * Expect multiple callbacks.
19
- */
20
- updateVersion(index: number, version: IVersion, seqNumber: number): void;
21
-
22
- /**
23
- * Called in response to successful onVersionSelection() or onSnapshotFileSelection() call
24
- * and provides extra information about selection.
25
- * It expected that UI layer would change its mode as result of this call, i.e. switch to
26
- * displaying op playback controls (if this is supported)
27
- * Note: There maybe no call to versionSelected() in response to onSnapshotFileSelection() call
28
- * if file does not exist, has wrong name of wrong format.
29
- * @param version - version, file name, or undefined if playing ops.
30
- */
31
- versionSelected(seqNumber: number, version: IVersion | string): void;
32
-
33
- /**
34
- * Called by controller in response to new ops being downloaded
35
- * Called with disable = true if there are no (currently) ops to play
36
- */
37
- disableNextOpButton(disable: boolean): void;
38
-
39
- /**
40
- * Called by controller when new ops arrive (or we are done playing previous batch)
41
- * Indicates next batch of ops that would be played when UI calls controller's onOpButtonClick()
42
- * Called with ops=[] when there are no ops to play.
43
- */
44
- updateNextOpText(ops: ISequencedDocumentMessage[]): void;
45
-
46
- /**
47
- * Called periodically when new versions are downloaded from server
48
- */
49
- updateVersionText(versionsLeft: number): void;
50
-
51
- /**
52
- * Called periodically to notify about last known op
53
- * @param lastKnownOp - seq number of last known op. -1 if can't play ops in this mode (load from file)
54
- * @param stillLoading - true if we did not reach yet the end of the stream
55
- */
56
- updateLastOpText(lastKnownOp: number, stillLoading: boolean): void;
13
+ /**
14
+ * Version information is provided.
15
+ * Expect updates (information about seq#, timestamp) through updateVersion() calls
16
+ */
17
+ addVersions(version: IVersion[]): void;
18
+
19
+ /**
20
+ * Call when new version is downloaded from storage
21
+ * Expect multiple callbacks.
22
+ */
23
+ updateVersion(index: number, version: IVersion, seqNumber: number): void;
24
+
25
+ /**
26
+ * Called in response to successful onVersionSelection() or onSnapshotFileSelection() call
27
+ * and provides extra information about selection.
28
+ * It expected that UI layer would change its mode as result of this call, i.e. switch to
29
+ * displaying op playback controls (if this is supported)
30
+ * Note: There maybe no call to versionSelected() in response to onSnapshotFileSelection() call
31
+ * if file does not exist, has wrong name of wrong format.
32
+ * @param version - version, file name, or undefined if playing ops.
33
+ */
34
+ versionSelected(seqNumber: number, version: IVersion | string): void;
35
+
36
+ /**
37
+ * Called by controller in response to new ops being downloaded
38
+ * Called with disable = true if there are no (currently) ops to play
39
+ */
40
+ disableNextOpButton(disable: boolean): void;
41
+
42
+ /**
43
+ * Called by controller when new ops arrive (or we are done playing previous batch)
44
+ * Indicates next batch of ops that would be played when UI calls controller's onOpButtonClick()
45
+ * Called with ops=[] when there are no ops to play.
46
+ */
47
+ updateNextOpText(ops: ISequencedDocumentMessage[]): void;
48
+
49
+ /**
50
+ * Called periodically when new versions are downloaded from server
51
+ */
52
+ updateVersionText(versionsLeft: number): void;
53
+
54
+ /**
55
+ * Called periodically to notify about last known op
56
+ * @param lastKnownOp - seq number of last known op. -1 if can't play ops in this mode (load from file)
57
+ * @param stillLoading - true if we did not reach yet the end of the stream
58
+ */
59
+ updateLastOpText(lastKnownOp: number, stillLoading: boolean): void;
57
60
  }
58
61
 
62
+ /**
63
+ * @internal
64
+ */
59
65
  export interface IDebuggerController {
60
- /**
61
- * Initialization. UI layers calls into controller to connect the two.
62
- * @param ui - UI layer
63
- */
64
- connectToUi(ui: IDebuggerUI);
65
-
66
- /**
67
- * Called by UI layer when debugger window is closed by user
68
- * If called before user makes selection of snapshot/file, original
69
- * document service is returned to loader (instead of debugger service) and normal document load continues.
70
- */
71
- onClose(): void;
72
-
73
- /**
74
- * UI Layer notifies about selection of version to continue.
75
- * On successful load, versionSelected() is called.
76
- * @param version - Snapshot version to start from.
77
- */
78
- onVersionSelection(version: IVersion): void;
79
-
80
- /**
81
- * UI Layer notifies about selection of version to continue.
82
- * On successful load, versionSelected() is called.
83
- * @param version - File to load snapshot from
84
- */
85
- onSnapshotFileSelection(file: File): void;
86
-
87
- /**
88
- * "next op" button is clicked in the UI
89
- * @param steps - number of ops to play.
90
- */
91
- onOpButtonClick(steps: number): void;
92
-
93
- /**
94
- * "Download ops" option is clicked in the UI. Returns JSON of the full opStream when available.
95
- * @param anonymize - anonymize the ops json using the sanitization tool
96
- */
97
- onDownloadOpsButtonClick(anonymize: boolean): Promise<string>;
66
+ /**
67
+ * Initialization. UI layers calls into controller to connect the two.
68
+ * @param ui - UI layer
69
+ */
70
+ connectToUi(ui: IDebuggerUI);
71
+
72
+ /**
73
+ * Called by UI layer when debugger window is closed by user
74
+ * If called before user makes selection of snapshot/file, original
75
+ * document service is returned to loader (instead of debugger service) and normal document load continues.
76
+ */
77
+ onClose(): void;
78
+
79
+ /**
80
+ * UI Layer notifies about selection of version to continue.
81
+ * On successful load, versionSelected() is called.
82
+ * @param version - Snapshot version to start from.
83
+ */
84
+ onVersionSelection(version: IVersion): void;
85
+
86
+ /**
87
+ * UI Layer notifies about selection of version to continue.
88
+ * On successful load, versionSelected() is called.
89
+ * @param version - File to load snapshot from
90
+ */
91
+ onSnapshotFileSelection(file: File): void;
92
+
93
+ /**
94
+ * "next op" button is clicked in the UI
95
+ * @param steps - number of ops to play.
96
+ */
97
+ onOpButtonClick(steps: number): void;
98
+
99
+ /**
100
+ * "Download ops" option is clicked in the UI. Returns JSON of the full opStream when available.
101
+ * @param anonymize - anonymize the ops json using the sanitization tool
102
+ */
103
+ onDownloadOpsButtonClick(anonymize: boolean): Promise<string>;
98
104
  }
99
105
 
100
- const debuggerWindowHtml =
101
- `<Title>Fluid Debugger</Title>
106
+ const debuggerWindowHtml = `<Title>Fluid Debugger</Title>
102
107
  <body>
103
108
  <h3>Fluid Debugger</h3>
104
109
  Please select snapshot or file to start with<br/>
@@ -116,8 +121,7 @@ Close debugger window to proceed to live document<br/><br/>
116
121
  <br/><br/><div id='versionText'></div>
117
122
  </body>`;
118
123
 
119
- const debuggerWindowHtml2 =
120
- `<Title>Fluid Debugger</Title>
124
+ const debuggerWindowHtml2 = `<Title>Fluid Debugger</Title>
121
125
  <body>
122
126
  <h3>Fluid Debugger</h3>
123
127
  <div id='versionText'></div>
@@ -134,208 +138,227 @@ Step to move: <input type='number' id='steps' value='1' min='1' style='width:50p
134
138
  <button type='button' id='downloadOps'>Download ops</button>
135
139
  </body>`;
136
140
 
141
+ /**
142
+ * @internal
143
+ */
137
144
  export class DebuggerUI {
138
- public static create(controller: IDebuggerController): DebuggerUI | null {
139
- if (
140
- typeof window !== "object" ||
141
- window === null ||
142
- typeof window.document !== "object" ||
143
- window.document == null) {
144
- console.log("Can't create debugger window - not running in browser!");
145
- return null;
146
- }
147
-
148
- const debuggerWindow = window.open(
149
- "",
150
- "",
151
- "width=400,height=400,resizable=yes,location=no,menubar=no,titlebar=no,status=no,toolbar=no");
152
- if (!debuggerWindow) {
153
- console.error("Can't create debugger window - please enable pop-up windows in your browser!");
154
- return null;
155
- }
156
-
157
- return new DebuggerUI(controller, debuggerWindow);
158
- }
159
-
160
- private static formatDate(date: number) {
161
- // Alternative - without timezone
162
- // new Date().toLocaleString('default', { timeZone: 'UTC'}));
163
- // new Date().toLocaleString('default', { year: 'numeric', month: 'short',
164
- // day: 'numeric', hour: '2-digit', minute: 'numeric', second: 'numeric' }));
165
- return new Date(date).toUTCString();
166
- }
167
-
168
- protected selector?: HTMLSelectElement;
169
- protected versionText: HTMLDivElement;
170
-
171
- protected buttonOps?: HTMLButtonElement;
172
- protected text1?: HTMLDivElement;
173
- protected text2?: HTMLDivElement;
174
- protected text3?: HTMLDivElement;
175
- protected lastOpText?: HTMLDivElement;
176
- protected wasVersionSelected = false;
177
- protected versions: IVersion[] = [];
178
-
179
- protected documentClosed = false;
180
-
181
- protected constructor(private readonly controller: IDebuggerController, private readonly debuggerWindow: Window) {
182
- const doc = this.debuggerWindow.document;
183
- doc.write(debuggerWindowHtml);
184
-
185
- window.addEventListener("beforeunload", (e) => {
186
- this.documentClosed = true;
187
- this.debuggerWindow.close();
188
- }, false);
189
-
190
- this.debuggerWindow.addEventListener("beforeunload", (e) => {
191
- if (!this.documentClosed) {
192
- this.controller.onClose();
193
- }
194
- }, false);
195
-
196
- this.selector = doc.getElementById("selector") as HTMLSelectElement;
197
-
198
- const buttonVers = doc.getElementById("buttonVers") as HTMLDivElement;
199
- buttonVers.onclick = () => {
200
- const index = this.selector!.selectedIndex;
201
- controller.onVersionSelection(this.versions[index]);
202
- };
203
-
204
- const fileSnapshot = doc.getElementById("file") as HTMLInputElement;
205
- fileSnapshot.addEventListener("change", () => {
206
- const files = fileSnapshot.files;
207
- if (files) {
208
- controller.onSnapshotFileSelection(files[0]);
209
- }
210
- }, false);
211
-
212
- const opDownloadButton = doc.getElementById("downloadOps") as HTMLElement;
213
- const anonymizeCheckbox = doc.getElementById("anonymize") as HTMLInputElement;
214
- this.attachDownloadOpsListener(opDownloadButton, anonymizeCheckbox);
215
-
216
- this.versionText = doc.getElementById("versionText") as HTMLDivElement;
217
- this.versionText.textContent = "Fetching snapshots, please wait...";
218
-
219
- controller.connectToUi(this);
220
- }
221
-
222
- private attachDownloadOpsListener(element: HTMLElement, anonymize: HTMLInputElement) {
223
- element.addEventListener("click", () => {
224
- this.controller.onDownloadOpsButtonClick(anonymize.checked).then((opJson) => {
225
- this.download("opStream.json", opJson);
226
- }).catch((error) => { console.log(`Error downloading ops: ${error}`); });
227
- });
228
- }
229
-
230
- public addVersions(versions: IVersion[]) {
231
- if (this.selector) {
232
- this.versions = versions;
233
- for (const version of versions) {
234
- const option = document.createElement("option");
235
- if (version.date !== undefined) {
236
- option.text = `id = ${version.id}, time = ${version.date}`;
237
- } else {
238
- option.text = `id = ${version.id}`;
239
- }
240
- this.selector.add(option);
241
- }
242
- }
243
- }
244
-
245
- public updateVersion(index: number, version: IVersion, seqNumber: number) {
246
- if (this.selector) {
247
- const option = this.selector[index] as HTMLOptionElement;
248
- option.text = `${option.text}, seq = ${seqNumber}`;
249
- this.selector[index] = option;
250
- }
251
- }
252
-
253
- public versionSelected(seqNumber: number, version: IVersion | string) {
254
- let text: string;
255
- if (typeof version === "string") {
256
- text = `Playing ${version} file`;
257
- } else {
258
- text = `Playing from ${version.id}, seq# ${seqNumber}`;
259
- }
260
-
261
- this.wasVersionSelected = true;
262
- this.selector = undefined;
263
-
264
- const doc = this.debuggerWindow.document;
265
- doc.open();
266
- doc.write(debuggerWindowHtml2);
267
- doc.close();
268
-
269
- this.lastOpText = doc.getElementById("lastOp") as HTMLDivElement;
270
- this.text1 = doc.getElementById("text1") as HTMLDivElement;
271
- this.text2 = doc.getElementById("text2") as HTMLDivElement;
272
- this.text3 = doc.getElementById("text3") as HTMLDivElement;
273
-
274
- const steps = doc.getElementById("steps") as HTMLInputElement;
275
- this.buttonOps = doc.getElementById("buttonOps") as HTMLButtonElement;
276
- this.buttonOps.disabled = true;
277
- this.buttonOps.onclick = () => {
278
- this.controller.onOpButtonClick(Number(steps.value));
279
- };
280
-
281
- this.versionText = doc.getElementById("versionText") as HTMLDivElement;
282
- this.versionText.textContent = text;
283
-
284
- const opDownloadButton = doc.getElementById("downloadOps") as HTMLElement;
285
- const anonymizeCheckbox = doc.getElementById("anonymize") as HTMLInputElement;
286
- this.attachDownloadOpsListener(opDownloadButton, anonymizeCheckbox);
287
- }
288
-
289
- public disableNextOpButton(disable: boolean) {
290
- assert(!!this.buttonOps, 0x088 /* "Missing button ops button!" */);
291
- this.buttonOps.disabled = disable;
292
- }
293
-
294
- public updateNextOpText(ops: ISequencedDocumentMessage[]) {
295
- if (ops.length === 0) {
296
- this.text1!.textContent = "";
297
- this.text2!.textContent = "";
298
- this.text3!.textContent = "";
299
- } else {
300
- const op = ops[0];
301
- const seq = op.sequenceNumber;
302
- const date = DebuggerUI.formatDate(op.timestamp);
303
- this.text1!.textContent = `Next op seq#: ${seq}`;
304
- this.text2!.textContent = `Type: ${op.type}`;
305
- this.text3!.textContent = `${date}`;
306
- }
307
- }
308
-
309
- public updateVersionText(versionCount: number) {
310
- if (!this.wasVersionSelected) {
311
- const text = versionCount === 0 ? "" : `Fetching information about ${versionCount} snapshots...`;
312
- this.versionText.textContent = text;
313
- }
314
- }
315
-
316
- public updateLastOpText(lastKnownOp: number, stillLoading: boolean) {
317
- let text: string;
318
- if (lastKnownOp < 0) {
319
- text = `FluidDebugger can't play ops in this mode`;
320
- }
321
- if (stillLoading) {
322
- text = `Last op (still loading): ${lastKnownOp}`;
323
- } else {
324
- text = `Document's last op seq#: ${lastKnownOp}`;
325
- }
326
- this.lastOpText!.textContent = text;
327
- }
328
-
329
- private download(filename: string, data: string): void {
330
- const element = document.createElement("a");
331
- element.setAttribute("href", `data:text/plain;charset=utf-8,${ encodeURIComponent(data) }`);
332
- element.setAttribute("download", filename);
333
-
334
- element.style.display = "none";
335
- document.body.appendChild(element);
336
-
337
- element.click();
338
-
339
- document.body.removeChild(element);
340
- }
145
+ public static create(controller: IDebuggerController): DebuggerUI | null {
146
+ if (
147
+ typeof window !== "object" ||
148
+ window === null ||
149
+ typeof window.document !== "object" ||
150
+ window.document == null
151
+ ) {
152
+ console.log("Can't create debugger window - not running in browser!");
153
+ return null;
154
+ }
155
+
156
+ const debuggerWindow = window.open(
157
+ "",
158
+ "",
159
+ "width=400,height=400,resizable=yes,location=no,menubar=no,titlebar=no,status=no,toolbar=no",
160
+ );
161
+ if (!debuggerWindow) {
162
+ console.error(
163
+ "Can't create debugger window - please enable pop-up windows in your browser!",
164
+ );
165
+ return null;
166
+ }
167
+
168
+ return new DebuggerUI(controller, debuggerWindow);
169
+ }
170
+
171
+ private static formatDate(date: number) {
172
+ // Alternative - without timezone
173
+ // new Date().toLocaleString('default', { timeZone: 'UTC'}));
174
+ // new Date().toLocaleString('default', { year: 'numeric', month: 'short',
175
+ // day: 'numeric', hour: '2-digit', minute: 'numeric', second: 'numeric' }));
176
+ return new Date(date).toUTCString();
177
+ }
178
+
179
+ protected selector?: HTMLSelectElement;
180
+ protected versionText: HTMLDivElement;
181
+
182
+ protected buttonOps?: HTMLButtonElement;
183
+ protected text1?: HTMLDivElement;
184
+ protected text2?: HTMLDivElement;
185
+ protected text3?: HTMLDivElement;
186
+ protected lastOpText?: HTMLDivElement;
187
+ protected wasVersionSelected = false;
188
+ protected versions: IVersion[] = [];
189
+
190
+ protected documentClosed = false;
191
+
192
+ protected constructor(
193
+ private readonly controller: IDebuggerController,
194
+ private readonly debuggerWindow: Window,
195
+ ) {
196
+ const doc = this.debuggerWindow.document;
197
+ doc.write(debuggerWindowHtml);
198
+
199
+ window.addEventListener(
200
+ "beforeunload",
201
+ (e) => {
202
+ this.documentClosed = true;
203
+ this.debuggerWindow.close();
204
+ },
205
+ false,
206
+ );
207
+
208
+ this.debuggerWindow.addEventListener(
209
+ "beforeunload",
210
+ (e) => {
211
+ if (!this.documentClosed) {
212
+ this.controller.onClose();
213
+ }
214
+ },
215
+ false,
216
+ );
217
+
218
+ this.selector = doc.getElementById("selector") as HTMLSelectElement;
219
+
220
+ const buttonVers = doc.getElementById("buttonVers") as HTMLDivElement;
221
+ buttonVers.onclick = () => {
222
+ const index = this.selector!.selectedIndex;
223
+ controller.onVersionSelection(this.versions[index]);
224
+ };
225
+
226
+ const fileSnapshot = doc.getElementById("file") as HTMLInputElement;
227
+ fileSnapshot.addEventListener(
228
+ "change",
229
+ () => {
230
+ const files = fileSnapshot.files;
231
+ if (files) {
232
+ controller.onSnapshotFileSelection(files[0]);
233
+ }
234
+ },
235
+ false,
236
+ );
237
+
238
+ const opDownloadButton = doc.getElementById("downloadOps") as HTMLElement;
239
+ const anonymizeCheckbox = doc.getElementById("anonymize") as HTMLInputElement;
240
+ this.attachDownloadOpsListener(opDownloadButton, anonymizeCheckbox);
241
+
242
+ this.versionText = doc.getElementById("versionText") as HTMLDivElement;
243
+ this.versionText.textContent = "Fetching snapshots, please wait...";
244
+
245
+ controller.connectToUi(this);
246
+ }
247
+
248
+ private attachDownloadOpsListener(element: HTMLElement, anonymize: HTMLInputElement) {
249
+ element.addEventListener("click", () => {
250
+ this.controller
251
+ .onDownloadOpsButtonClick(anonymize.checked)
252
+ .then((opJson) => {
253
+ this.download("opStream.json", opJson);
254
+ })
255
+ .catch((error) => {
256
+ console.log(`Error downloading ops: ${error}`);
257
+ });
258
+ });
259
+ }
260
+
261
+ public addVersions(versions: IVersion[]) {
262
+ if (this.selector) {
263
+ this.versions = versions;
264
+ for (const version of versions) {
265
+ const option = document.createElement("option");
266
+ option.text =
267
+ version.date !== undefined
268
+ ? `id = ${version.id}, time = ${version.date}`
269
+ : `id = ${version.id}`;
270
+ this.selector.add(option);
271
+ }
272
+ }
273
+ }
274
+
275
+ public updateVersion(index: number, version: IVersion, seqNumber: number) {
276
+ if (this.selector) {
277
+ const option = this.selector[index] as HTMLOptionElement;
278
+ option.text = `${option.text}, seq = ${seqNumber}`;
279
+ this.selector[index] = option;
280
+ }
281
+ }
282
+
283
+ public versionSelected(seqNumber: number, version: IVersion | string) {
284
+ const text =
285
+ typeof version === "string"
286
+ ? `Playing ${version} file`
287
+ : `Playing from ${version.id}, seq# ${seqNumber}`;
288
+
289
+ this.wasVersionSelected = true;
290
+ this.selector = undefined;
291
+
292
+ const doc = this.debuggerWindow.document;
293
+ doc.open();
294
+ doc.write(debuggerWindowHtml2);
295
+ doc.close();
296
+
297
+ this.lastOpText = doc.getElementById("lastOp") as HTMLDivElement;
298
+ this.text1 = doc.getElementById("text1") as HTMLDivElement;
299
+ this.text2 = doc.getElementById("text2") as HTMLDivElement;
300
+ this.text3 = doc.getElementById("text3") as HTMLDivElement;
301
+
302
+ const steps = doc.getElementById("steps") as HTMLInputElement;
303
+ this.buttonOps = doc.getElementById("buttonOps") as HTMLButtonElement;
304
+ this.buttonOps.disabled = true;
305
+ this.buttonOps.onclick = () => {
306
+ this.controller.onOpButtonClick(Number(steps.value));
307
+ };
308
+
309
+ this.versionText = doc.getElementById("versionText") as HTMLDivElement;
310
+ this.versionText.textContent = text;
311
+
312
+ const opDownloadButton = doc.getElementById("downloadOps") as HTMLElement;
313
+ const anonymizeCheckbox = doc.getElementById("anonymize") as HTMLInputElement;
314
+ this.attachDownloadOpsListener(opDownloadButton, anonymizeCheckbox);
315
+ }
316
+
317
+ public disableNextOpButton(disable: boolean) {
318
+ assert(!!this.buttonOps, 0x088 /* "Missing button ops button!" */);
319
+ this.buttonOps.disabled = disable;
320
+ }
321
+
322
+ public updateNextOpText(ops: ISequencedDocumentMessage[]) {
323
+ if (ops.length === 0) {
324
+ this.text1!.textContent = "";
325
+ this.text2!.textContent = "";
326
+ this.text3!.textContent = "";
327
+ } else {
328
+ const op = ops[0];
329
+ const seq = op.sequenceNumber;
330
+ const date = DebuggerUI.formatDate(op.timestamp);
331
+ this.text1!.textContent = `Next op seq#: ${seq}`;
332
+ this.text2!.textContent = `Type: ${op.type}`;
333
+ this.text3!.textContent = `${date}`;
334
+ }
335
+ }
336
+
337
+ public updateVersionText(versionCount: number) {
338
+ if (!this.wasVersionSelected) {
339
+ const text =
340
+ versionCount === 0 ? "" : `Fetching information about ${versionCount} snapshots...`;
341
+ this.versionText.textContent = text;
342
+ }
343
+ }
344
+
345
+ public updateLastOpText(lastKnownOp: number, stillLoading: boolean) {
346
+ const text = stillLoading
347
+ ? `Last op (still loading): ${lastKnownOp}`
348
+ : `Document's last op seq#: ${lastKnownOp}`;
349
+ this.lastOpText!.textContent = text;
350
+ }
351
+
352
+ private download(filename: string, data: string): void {
353
+ const element = document.createElement("a");
354
+ element.setAttribute("href", `data:text/plain;charset=utf-8,${encodeURIComponent(data)}`);
355
+ element.setAttribute("download", filename);
356
+
357
+ element.style.display = "none";
358
+ document.body.appendChild(element);
359
+
360
+ element.click();
361
+
362
+ document.body.removeChild(element);
363
+ }
341
364
  }
package/src/index.ts CHANGED
@@ -3,6 +3,6 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- export * from "./fluidDebuggerController";
7
- export * from "./fluidDebuggerUi";
8
- export * from "./fluidDebugger";
6
+ export { FluidDebugger } from "./fluidDebugger";
7
+ export { debuggerUIFactory, DebugReplayController } from "./fluidDebuggerController";
8
+ export { DebuggerUI, IDebuggerController, IDebuggerUI } from "./fluidDebuggerUi";