@saasquatch/squatch-js 2.8.3-3 → 2.8.3-30

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 (46) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/babelregister.js +0 -1
  3. package/coverage/base.css +224 -0
  4. package/coverage/block-navigation.js +87 -0
  5. package/coverage/clover.xml +996 -0
  6. package/coverage/coverage-final.json +26 -0
  7. package/coverage/favicon.png +0 -0
  8. package/coverage/prettify.css +1 -0
  9. package/coverage/prettify.js +2 -0
  10. package/coverage/sort-arrow-sprite.png +0 -0
  11. package/coverage/sorter.js +210 -0
  12. package/demo/perf-benchmark.ts +363 -0
  13. package/demo/perf-compare.html +870 -0
  14. package/demo/perf-deploy/vercel.json +17 -0
  15. package/demo/perf-frame.html +417 -0
  16. package/demo/perf-server.ts +131 -0
  17. package/dist/ErrorTemplate-DUNm11h9.js +124 -0
  18. package/dist/ErrorTemplate-DUNm11h9.js.map +1 -0
  19. package/dist/ErrorTemplate-DumOlC5f.cjs +109 -0
  20. package/dist/ErrorTemplate-DumOlC5f.cjs.map +1 -0
  21. package/dist/SkeletonTemplate-B3Bk4NFu.cjs +243 -0
  22. package/dist/SkeletonTemplate-B3Bk4NFu.cjs.map +1 -0
  23. package/dist/SkeletonTemplate-Day_0iMM.js +246 -0
  24. package/dist/SkeletonTemplate-Day_0iMM.js.map +1 -0
  25. package/dist/squatch.cjs.js +33 -2523
  26. package/dist/squatch.cjs.js.map +1 -1
  27. package/dist/squatch.esm.js +928 -1956
  28. package/dist/squatch.esm.js.map +1 -1
  29. package/dist/squatch.js +227 -2373
  30. package/dist/squatch.js.map +1 -1
  31. package/dist/squatch.min.js +4 -5
  32. package/dist/types.d.ts +134 -1
  33. package/dist/utils/cookieUtils.d.ts +1 -0
  34. package/dist/utils/logger.d.ts +23 -0
  35. package/dist/widgets/EmbedWidget.d.ts +1 -1
  36. package/dist/widgets/ErrorTemplate.d.ts +9 -0
  37. package/dist/widgets/PopupWidget.d.ts +3 -5
  38. package/dist/widgets/SkeletonTemplate.d.ts +1 -4
  39. package/dist/widgets/Widget.d.ts +29 -2
  40. package/dist/widgets/declarative/DeclarativeWidget.d.ts +9 -1
  41. package/dist/widgets/declarative/DeclarativeWidgets.d.ts +0 -6
  42. package/package.json +11 -15
  43. package/vite-env.d.ts +2 -1
  44. package/vite.config.ts +17 -0
  45. package/babel.config.js +0 -8
  46. package/jest.config.ts +0 -200
@@ -0,0 +1,870 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>squatch-js Performance Comparison</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f1117;
10
+ --surface: #1a1d27;
11
+ --border: #2a2d3a;
12
+ --text: #e1e4ed;
13
+ --text-dim: #8b8fa3;
14
+ --green: #22c55e;
15
+ --red: #ef4444;
16
+ --blue: #3b82f6;
17
+ --orange: #f59e0b;
18
+ --mono: "SF Mono", "Fira Code", "Cascadia Code", Consolas, monospace;
19
+ }
20
+ * { margin: 0; padding: 0; box-sizing: border-box; }
21
+ body {
22
+ font-family: system-ui, -apple-system, sans-serif;
23
+ background: var(--bg);
24
+ color: var(--text);
25
+ min-height: 100vh;
26
+ }
27
+
28
+ /* ── Header ─────────────────────────────────────── */
29
+ header {
30
+ padding: 20px 24px;
31
+ border-bottom: 1px solid var(--border);
32
+ display: flex;
33
+ align-items: center;
34
+ gap: 20px;
35
+ flex-wrap: wrap;
36
+ }
37
+ header h1 {
38
+ font-size: 18px;
39
+ font-weight: 600;
40
+ white-space: nowrap;
41
+ }
42
+ header h1 span { color: var(--text-dim); font-weight: 400; }
43
+ .controls {
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 10px;
47
+ margin-left: auto;
48
+ }
49
+ .controls label {
50
+ font-size: 13px;
51
+ color: var(--text-dim);
52
+ }
53
+ .controls input[type="number"] {
54
+ width: 50px;
55
+ padding: 5px 8px;
56
+ border-radius: 6px;
57
+ border: 1px solid var(--border);
58
+ background: var(--surface);
59
+ color: var(--text);
60
+ font-size: 13px;
61
+ font-family: var(--mono);
62
+ text-align: center;
63
+ }
64
+ button {
65
+ padding: 7px 16px;
66
+ border-radius: 6px;
67
+ border: 1px solid var(--border);
68
+ background: var(--surface);
69
+ color: var(--text);
70
+ font-size: 13px;
71
+ cursor: pointer;
72
+ transition: background 0.15s;
73
+ }
74
+ button:hover { background: var(--border); }
75
+ button.primary {
76
+ background: var(--blue);
77
+ border-color: var(--blue);
78
+ color: #fff;
79
+ font-weight: 500;
80
+ }
81
+ button.primary:hover { background: #2563eb; }
82
+ button:disabled {
83
+ opacity: 0.4;
84
+ cursor: not-allowed;
85
+ }
86
+ #run-status {
87
+ font-size: 13px;
88
+ color: var(--text-dim);
89
+ font-family: var(--mono);
90
+ }
91
+
92
+ /* ── Panels ─────────────────────────────────────── */
93
+ .panels {
94
+ display: grid;
95
+ grid-template-columns: 1fr 1fr;
96
+ gap: 1px;
97
+ background: var(--border);
98
+ }
99
+ .panel {
100
+ background: var(--bg);
101
+ display: flex;
102
+ flex-direction: column;
103
+ }
104
+ .panel-header {
105
+ padding: 12px 16px;
106
+ border-bottom: 1px solid var(--border);
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 10px;
110
+ }
111
+ .panel-header .tag {
112
+ font-size: 11px;
113
+ font-weight: 600;
114
+ text-transform: uppercase;
115
+ letter-spacing: 0.5px;
116
+ padding: 2px 8px;
117
+ border-radius: 4px;
118
+ }
119
+ .panel-header .tag.v2 { background: #422006; color: var(--orange); }
120
+ .panel-header .tag.next { background: #052e16; color: var(--green); }
121
+ .panel-header .version-url {
122
+ font-size: 11px;
123
+ color: var(--text-dim);
124
+ font-family: var(--mono);
125
+ overflow: hidden;
126
+ text-overflow: ellipsis;
127
+ white-space: nowrap;
128
+ }
129
+ .iframe-container {
130
+ height: 400px;
131
+ overflow: auto;
132
+ border-bottom: 1px solid var(--border);
133
+ position: relative;
134
+ }
135
+ .iframe-container iframe {
136
+ width: 100%;
137
+ height: 100%;
138
+ border: none;
139
+ }
140
+ .iframe-placeholder {
141
+ display: flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ height: 100%;
145
+ color: var(--text-dim);
146
+ font-size: 14px;
147
+ }
148
+
149
+ /* ── Metrics ────────────────────────────────────── */
150
+ .metrics-grid {
151
+ padding: 12px 16px;
152
+ display: grid;
153
+ grid-template-columns: repeat(4, 1fr);
154
+ gap: 8px;
155
+ }
156
+ .metric-card {
157
+ background: var(--surface);
158
+ border-radius: 8px;
159
+ padding: 10px 12px;
160
+ border: 1px solid var(--border);
161
+ }
162
+ .metric-card .label {
163
+ font-size: 10px;
164
+ text-transform: uppercase;
165
+ letter-spacing: 0.5px;
166
+ color: var(--text-dim);
167
+ margin-bottom: 4px;
168
+ }
169
+ .metric-card .value {
170
+ font-size: 18px;
171
+ font-weight: 600;
172
+ font-family: var(--mono);
173
+ color: var(--text);
174
+ }
175
+ .metric-card .value.pending { color: var(--text-dim); }
176
+ .metric-card .unit {
177
+ font-size: 11px;
178
+ color: var(--text-dim);
179
+ margin-left: 2px;
180
+ }
181
+
182
+ /* ── Comparison Table ───────────────────────────── */
183
+ .comparison-section {
184
+ padding: 20px 24px;
185
+ border-top: 1px solid var(--border);
186
+ }
187
+ .comparison-section h2 {
188
+ font-size: 15px;
189
+ font-weight: 600;
190
+ margin-bottom: 12px;
191
+ }
192
+ table {
193
+ width: 100%;
194
+ border-collapse: collapse;
195
+ font-size: 13px;
196
+ }
197
+ th, td {
198
+ padding: 8px 12px;
199
+ text-align: right;
200
+ border-bottom: 1px solid var(--border);
201
+ }
202
+ th {
203
+ font-weight: 500;
204
+ color: var(--text-dim);
205
+ font-size: 11px;
206
+ text-transform: uppercase;
207
+ letter-spacing: 0.5px;
208
+ }
209
+ th:first-child, td:first-child { text-align: left; }
210
+ td { font-family: var(--mono); font-size: 13px; }
211
+ td.faster { color: var(--green); }
212
+ td.slower { color: var(--red); }
213
+ td.neutral { color: var(--text-dim); }
214
+ td.metric-name {
215
+ font-family: system-ui, sans-serif;
216
+ font-weight: 500;
217
+ color: var(--text);
218
+ }
219
+ tr.avg-row { background: var(--surface); }
220
+ tr.avg-row td { font-weight: 600; }
221
+
222
+ /* ── Multi-run history ──────────────────────────── */
223
+ .history-section {
224
+ padding: 20px 24px;
225
+ border-top: 1px solid var(--border);
226
+ }
227
+ .history-section h2 {
228
+ font-size: 15px;
229
+ font-weight: 600;
230
+ margin-bottom: 12px;
231
+ }
232
+ .history-table-wrap {
233
+ overflow-x: auto;
234
+ }
235
+ .history-table-wrap table td,
236
+ .history-table-wrap table th {
237
+ white-space: nowrap;
238
+ font-size: 12px;
239
+ }
240
+ .hidden { display: none; }
241
+
242
+ /* ── Editable version URL ────────────────────────── */
243
+ .version-url-wrap {
244
+ display: flex;
245
+ align-items: center;
246
+ gap: 6px;
247
+ flex: 1;
248
+ min-width: 0;
249
+ }
250
+ .version-url-wrap .url-prefix {
251
+ font-size: 11px;
252
+ color: var(--text-dim);
253
+ font-family: var(--mono);
254
+ white-space: nowrap;
255
+ }
256
+ .version-url-wrap input.version-input {
257
+ font-size: 11px;
258
+ font-family: var(--mono);
259
+ color: var(--text);
260
+ background: transparent;
261
+ border: 1px solid transparent;
262
+ border-radius: 4px;
263
+ padding: 2px 6px;
264
+ outline: none;
265
+ cursor: text;
266
+ min-width: 40px;
267
+ width: 80px;
268
+ }
269
+ .version-url-wrap input.version-input:hover {
270
+ border-color: var(--border);
271
+ }
272
+ .version-url-wrap input.version-input:focus {
273
+ border-color: var(--blue);
274
+ background: var(--surface);
275
+ }
276
+ .version-url-wrap .btn-save-version {
277
+ font-size: 10px;
278
+ padding: 2px 8px;
279
+ border-radius: 4px;
280
+ border: 1px solid var(--border);
281
+ background: var(--surface);
282
+ color: var(--text-dim);
283
+ cursor: pointer;
284
+ display: none;
285
+ white-space: nowrap;
286
+ }
287
+ .version-url-wrap .btn-save-version.visible {
288
+ display: inline-block;
289
+ }
290
+ .version-url-wrap .btn-save-version:hover {
291
+ background: var(--border);
292
+ color: var(--text);
293
+ }
294
+
295
+ /* ── Config controls ───────────────────────────── */
296
+ .config-section {
297
+ padding: 0 16px;
298
+ border-bottom: 1px solid var(--border);
299
+ }
300
+ .config-section details {
301
+ padding: 8px 0;
302
+ }
303
+ .config-section summary {
304
+ font-size: 11px;
305
+ color: var(--text-dim);
306
+ cursor: pointer;
307
+ user-select: none;
308
+ letter-spacing: 0.5px;
309
+ text-transform: uppercase;
310
+ }
311
+ .config-section summary:hover { color: var(--text); }
312
+ .config-fields {
313
+ display: grid;
314
+ grid-template-columns: 1fr 1fr;
315
+ gap: 6px 12px;
316
+ padding: 8px 0 4px;
317
+ }
318
+ .config-fields label {
319
+ font-size: 11px;
320
+ color: var(--text-dim);
321
+ display: flex;
322
+ flex-direction: column;
323
+ gap: 3px;
324
+ }
325
+ .config-fields input {
326
+ padding: 5px 8px;
327
+ border-radius: 5px;
328
+ border: 1px solid var(--border);
329
+ background: var(--surface);
330
+ color: var(--text);
331
+ font-size: 12px;
332
+ font-family: var(--mono);
333
+ width: 100%;
334
+ }
335
+ .config-fields input:focus {
336
+ outline: none;
337
+ border-color: var(--blue);
338
+ }
339
+ </style>
340
+ </head>
341
+ <body>
342
+ <header>
343
+ <h1>squatch-js <span>Performance Comparison</span></h1>
344
+ <div class="controls">
345
+ <label for="iterations">Iterations:</label>
346
+ <input type="number" id="iterations" value="5" min="1" max="20" />
347
+ <button class="primary" id="btn-run-n" title="Run multiple iterations">Run Benchmark</button>
348
+ <button id="btn-run-once" title="Single run">Run Once</button>
349
+ <span id="run-status"></span>
350
+ </div>
351
+ </header>
352
+
353
+ <div class="panels">
354
+ <!-- v2 Panel -->
355
+ <div class="panel">
356
+ <div class="panel-header">
357
+ <span class="tag v2">v2 (current)</span>
358
+ <div class="version-url-wrap">
359
+ <span class="url-prefix">https://fast.ssqt.io/squatch-js</span>
360
+ <input class="version-input" id="version-v2" value="@2" />
361
+ <button class="btn-save-version" id="save-version-v2">Save</button>
362
+ </div>
363
+ </div>
364
+ <div class="config-section">
365
+ <details>
366
+ <summary>Configuration</summary>
367
+ <div class="config-fields">
368
+ <label>Widget
369
+ <input type="text" id="cfg-v2-widget" value="p/47253/w/referrerWidget" />
370
+ </label>
371
+ <label>Tenant Alias
372
+ <input type="text" id="cfg-v2-tenant" value="ac52kfybp1tkr" />
373
+ </label>
374
+ <label>JWT
375
+ <input type="text" id="cfg-v2-jwt" value="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklSTVhzWXk2WVlxcTQ2OTQzN21HOEVSUXQ4UW9LRkJhRzEifQ.eyJ1c2VyIjp7ImlkIjoiMGM2YmUwZTcxMGViMDk1OTU2ODMxYTkyOTUxODVlMmU5N2VmMTQzMDhjN2UzNjFmYjBlYzY0NTE0NjUwY2NhOCIsImFjY291bnRJZCI6IjBjNmJlMGU3MTBlYjA5NTk1NjgzMWE5Mjk1MTg1ZTJlOTdlZjE0MzA4YzdlMzYxZmIwZWM2NDUxNDY1MGNjYTgiLCJlbWFpbCI6ImRlcmVrLnNpZW1lbnMrbm93OTJAaW1wYWN0LmNvbSJ9fQ.KOORz1aOIVRPR6PdF3Ss0D1AW19EYTX97WnA6fOVXaI" />
376
+ </label>
377
+ <label>Domain
378
+ <input type="text" id="cfg-v2-domain" value="" placeholder="(auto: local proxy)" />
379
+ </label>
380
+ </div>
381
+ </details>
382
+ </div>
383
+ <div class="iframe-container" id="frame-v2-container">
384
+ <div class="iframe-placeholder">Click "Run" to start</div>
385
+ </div>
386
+ <div class="metrics-grid" id="metrics-v2">
387
+ <div class="metric-card"><div class="label">Bundle Size</div><div class="value pending" data-metric="bundleSize">—</div></div>
388
+ <div class="metric-card"><div class="label">SDK Script Load</div><div class="value pending" data-metric="sdkScriptLoad">—</div></div>
389
+ <div class="metric-card"><div class="label">SDK Ready</div><div class="value pending" data-metric="sdkReady">—</div></div>
390
+ <div class="metric-card"><div class="label">Frame Created</div><div class="value pending" data-metric="frameCreated">—</div></div>
391
+ <div class="metric-card"><div class="label">Meaningful Paint</div><div class="value pending" data-metric="meaningfulPaint">—</div></div>
392
+ <div class="metric-card"><div class="label">Time Unstyled</div><div class="value pending" data-metric="timeUnstyled">—</div></div>
393
+ <div class="metric-card"><div class="label">Network Request Time</div><div class="value pending" data-metric="networkTime">—</div></div>
394
+ <div class="metric-card"><div class="label">Total Load Time</div><div class="value pending" data-metric="totalLoad">—</div></div>
395
+ </div>
396
+ </div>
397
+
398
+ <!-- Next Panel -->
399
+ <div class="panel">
400
+ <div class="panel-header">
401
+ <span class="tag next">next</span>
402
+ <div class="version-url-wrap">
403
+ <span class="url-prefix">https://fast.ssqt.io/squatch-js</span>
404
+ <input class="version-input" id="version-next" value="@next" />
405
+ <button class="btn-save-version" id="save-version-next">Save</button>
406
+ </div>
407
+ </div>
408
+ <div class="config-section">
409
+ <details>
410
+ <summary>Configuration</summary>
411
+ <div class="config-fields">
412
+ <label>Widget
413
+ <input type="text" id="cfg-next-widget" value="p/47253/w/referrerWidget" />
414
+ </label>
415
+ <label>Tenant Alias
416
+ <input type="text" id="cfg-next-tenant" value="ac52kfybp1tkr" />
417
+ </label>
418
+ <label>JWT
419
+ <input type="text" id="cfg-next-jwt" value="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklSTVhzWXk2WVlxcTQ2OTQzN21HOEVSUXQ4UW9LRkJhRzEifQ.eyJ1c2VyIjp7ImlkIjoiMGM2YmUwZTcxMGViMDk1OTU2ODMxYTkyOTUxODVlMmU5N2VmMTQzMDhjN2UzNjFmYjBlYzY0NTE0NjUwY2NhOCIsImFjY291bnRJZCI6IjBjNmJlMGU3MTBlYjA5NTk1NjgzMWE5Mjk1MTg1ZTJlOTdlZjE0MzA4YzdlMzYxZmIwZWM2NDUxNDY1MGNjYTgiLCJlbWFpbCI6ImRlcmVrLnNpZW1lbnMrbm93OTJAaW1wYWN0LmNvbSJ9fQ.KOORz1aOIVRPR6PdF3Ss0D1AW19EYTX97WnA6fOVXaI" />
420
+ </label>
421
+ <label>Domain
422
+ <input type="text" id="cfg-next-domain" value="" placeholder="(auto: local proxy)" />
423
+ </label>
424
+ </div>
425
+ </details>
426
+ </div>
427
+ <div class="iframe-container" id="frame-next-container">
428
+ <div class="iframe-placeholder">Click "Run" to start</div>
429
+ </div>
430
+ <div class="metrics-grid" id="metrics-next">
431
+ <div class="metric-card"><div class="label">Bundle Size</div><div class="value pending" data-metric="bundleSize">—</div></div>
432
+ <div class="metric-card"><div class="label">SDK Script Load</div><div class="value pending" data-metric="sdkScriptLoad">—</div></div>
433
+ <div class="metric-card"><div class="label">SDK Ready</div><div class="value pending" data-metric="sdkReady">—</div></div>
434
+ <div class="metric-card"><div class="label">Frame Created</div><div class="value pending" data-metric="frameCreated">—</div></div>
435
+ <div class="metric-card"><div class="label">Meaningful Paint</div><div class="value pending" data-metric="meaningfulPaint">—</div></div>
436
+ <div class="metric-card"><div class="label">Time Unstyled</div><div class="value pending" data-metric="timeUnstyled">—</div></div>
437
+ <div class="metric-card"><div class="label">Network Request Time</div><div class="value pending" data-metric="networkTime">—</div></div>
438
+ <div class="metric-card"><div class="label">Total Load Time</div><div class="value pending" data-metric="totalLoad">—</div></div>
439
+ </div>
440
+ </div>
441
+ </div>
442
+
443
+ <!-- Comparison Summary -->
444
+ <div class="comparison-section">
445
+ <h2>Comparison Summary</h2>
446
+ <table id="comparison-table">
447
+ <thead>
448
+ <tr>
449
+ <th>Metric</th>
450
+ <th>v2 (current)</th>
451
+ <th>next</th>
452
+ <th>Delta</th>
453
+ <th>Change</th>
454
+ </tr>
455
+ </thead>
456
+ <tbody id="comparison-body">
457
+ <tr><td colspan="5" class="neutral" style="text-align:center;">Run a benchmark to see results</td></tr>
458
+ </tbody>
459
+ </table>
460
+ </div>
461
+
462
+ <!-- Multi-run History -->
463
+ <div class="history-section hidden" id="history-section">
464
+ <h2>Run History</h2>
465
+ <div class="history-table-wrap">
466
+ <table id="history-table">
467
+ <thead id="history-head"></thead>
468
+ <tbody id="history-body"></tbody>
469
+ </table>
470
+ </div>
471
+ </div>
472
+
473
+ <script>
474
+ (function () {
475
+ "use strict";
476
+
477
+ var V2_URL = "https://fast.ssqt.io/squatch-js@2";
478
+ var NEXT_URL = "https://fast.ssqt.io/squatch-js@next";
479
+ var BASE_SDK_URL = "https://fast.ssqt.io/squatch-js";
480
+
481
+ function getV2Url() { return BASE_SDK_URL + document.getElementById("version-v2").value.trim(); }
482
+ function getNextUrl() { return BASE_SDK_URL + document.getElementById("version-next").value.trim(); }
483
+
484
+ var btnRunN = document.getElementById("btn-run-n");
485
+ var btnRunOnce = document.getElementById("btn-run-once");
486
+ var iterInput = document.getElementById("iterations");
487
+ var runStatus = document.getElementById("run-status");
488
+
489
+ var allRuns = []; // Array of { v2: metrics, next: metrics }
490
+ var running = false;
491
+
492
+ // ── Config fields that sync to/from URL params ──
493
+ var CONFIG_FIELDS = [
494
+ "cfg-v2-widget", "cfg-v2-tenant", "cfg-v2-jwt", "cfg-v2-domain",
495
+ "cfg-next-widget", "cfg-next-tenant", "cfg-next-jwt", "cfg-next-domain",
496
+ "version-v2", "version-next"
497
+ ];
498
+
499
+ // Populate inputs from URL query params on load
500
+ (function loadFromUrl() {
501
+ var params = new URLSearchParams(window.location.search);
502
+ CONFIG_FIELDS.forEach(function (id) {
503
+ var val = params.get(id);
504
+ if (val !== null) {
505
+ document.getElementById(id).value = val;
506
+ }
507
+ });
508
+ })();
509
+
510
+ // Sync all config inputs to URL params (replaceState, no reload)
511
+ function syncConfigToUrl() {
512
+ var params = new URLSearchParams();
513
+ CONFIG_FIELDS.forEach(function (id) {
514
+ var val = document.getElementById(id).value.trim();
515
+ var def = document.getElementById(id).defaultValue;
516
+ if (val && val !== def) {
517
+ params.set(id, val);
518
+ }
519
+ });
520
+ var qs = params.toString();
521
+ var newUrl = window.location.pathname + (qs ? "?" + qs : "");
522
+ history.replaceState(null, "", newUrl);
523
+ }
524
+
525
+ // Listen for changes on all config inputs
526
+ CONFIG_FIELDS.forEach(function (id) {
527
+ document.getElementById(id).addEventListener("input", syncConfigToUrl);
528
+ });
529
+
530
+ // ── Metric definitions ──────────────────────────
531
+ var METRICS = [
532
+ { key: "bundleSize", label: "Bundle Size", unit: "KB", format: formatKB, lowerBetter: true },
533
+ { key: "sdkScriptLoad", label: "SDK Script Load", unit: "ms", format: formatMs, lowerBetter: true },
534
+ { key: "sdkReady", label: "SDK Ready", unit: "ms", format: formatMs, lowerBetter: true },
535
+ { key: "frameCreated", label: "Frame Created", unit: "ms", format: formatMs, lowerBetter: true },
536
+ { key: "meaningfulPaint", label: "Meaningful Paint", unit: "ms", format: formatMs, lowerBetter: true },
537
+ { key: "timeUnstyled", label: "Time Unstyled", unit: "ms", format: formatMs, lowerBetter: true },
538
+ { key: "networkTime", label: "Network Request Time", unit: "ms", format: formatMs, lowerBetter: true },
539
+ { key: "totalLoad", label: "Total Load Time", unit: "ms", format: formatMs, lowerBetter: true }
540
+ ];
541
+
542
+ function formatMs(v) { return v > 0 ? v.toFixed(0) + " ms" : "—"; }
543
+ function formatKB(v) { return v > 0 ? (v / 1024).toFixed(1) + " KB" : "—"; }
544
+ function formatInt(v) { return v >= 0 ? String(Math.round(v)) : "—"; }
545
+
546
+ // ── Read config from inputs for a given panel prefix ────
547
+ function getConfig(prefix) {
548
+ return {
549
+ widget: document.getElementById("cfg-" + prefix + "-widget").value.trim(),
550
+ tenant: document.getElementById("cfg-" + prefix + "-tenant").value.trim(),
551
+ jwt: document.getElementById("cfg-" + prefix + "-jwt").value.trim(),
552
+ domain: document.getElementById("cfg-" + prefix + "-domain").value.trim()
553
+ };
554
+ }
555
+
556
+ // ── Load a single frame and wait for metrics ────
557
+ function loadFrame(containerId, sdkUrl, cfgPrefix) {
558
+ return new Promise(function (resolve) {
559
+ var container = document.getElementById(containerId);
560
+ var cfg = getConfig(cfgPrefix);
561
+ var cb = Date.now() + Math.random().toString(36).slice(2, 6);
562
+ // Use "perf-frame" without .html to avoid serve's cleanUrls 301
563
+ // redirect which strips query parameters
564
+ var src = "perf-frame?sdk=" + encodeURIComponent(sdkUrl) + "&cb=" + cb
565
+ + "&widget=" + encodeURIComponent(cfg.widget)
566
+ + "&tenant=" + encodeURIComponent(cfg.tenant)
567
+ + "&jwt=" + encodeURIComponent(cfg.jwt)
568
+ + (cfg.domain ? "&domain=" + encodeURIComponent(cfg.domain) : "");
569
+
570
+ container.innerHTML = "";
571
+ var iframe = document.createElement("iframe");
572
+ iframe.src = src;
573
+ iframe.style.cssText = "width:100%;height:100%;border:none;";
574
+ container.appendChild(iframe);
575
+
576
+ var resolved = false;
577
+
578
+ function done(metrics) {
579
+ if (resolved) return;
580
+ resolved = true;
581
+ window.removeEventListener("message", onMessage);
582
+ clearInterval(pollId);
583
+ // Wait for late resize events to settle — V2 often has
584
+ // multiple resize rounds after the first stable report.
585
+ // After waiting, read the final metrics from the iframe.
586
+ setTimeout(function () {
587
+ try {
588
+ var latest = iframe.contentWindow && iframe.contentWindow.__perfMetrics;
589
+ if (latest && latest.sdkUrl === sdkUrl && latest.stable > 0) {
590
+ resolve(latest);
591
+ return;
592
+ }
593
+ } catch (e) {}
594
+ resolve(metrics);
595
+ }, 1500);
596
+ }
597
+
598
+ // Method 1: Listen for postMessage from child
599
+ function onMessage(e) {
600
+ if (resolved) return;
601
+ if (!e.data || e.data.type !== "perf-metrics") return;
602
+ if (e.data.metrics && e.data.metrics.sdkUrl === sdkUrl) {
603
+ done(e.data.metrics);
604
+ }
605
+ }
606
+ window.addEventListener("message", onMessage);
607
+
608
+ // Method 2: Poll the iframe's window.__perfMetrics directly
609
+ // (fallback in case postMessage is blocked by browser policy)
610
+ var pollId = setInterval(function () {
611
+ if (resolved) { clearInterval(pollId); return; }
612
+ try {
613
+ var m = iframe.contentWindow && iframe.contentWindow.__perfMetrics;
614
+ if (m && m.stable > 0 && m.sdkUrl === sdkUrl) {
615
+ done(m);
616
+ }
617
+ } catch (e) { /* cross-origin — ignore */ }
618
+ }, 500);
619
+
620
+ // Timeout: resolve with partial data after 20s
621
+ setTimeout(function () {
622
+ if (!resolved) {
623
+ // Last-ditch attempt to read metrics directly
624
+ try {
625
+ var m = iframe.contentWindow && iframe.contentWindow.__perfMetrics;
626
+ if (m && m.sdkUrl === sdkUrl) { done(m); return; }
627
+ } catch (e) {}
628
+ done(null);
629
+ }
630
+ }, 20000);
631
+ });
632
+ }
633
+
634
+ // ── Derive totalLoad from stable time ───────────
635
+ function enrichMetrics(m) {
636
+ if (!m) return null;
637
+ m.totalLoad = m.stable || 0;
638
+ m.networkTime = m.networkTime || 0;
639
+ m.bundleSize = m.bundleSizeBytes || 0;
640
+ return m;
641
+ }
642
+
643
+ // ── Update metric cards in a panel ──────────────
644
+ function updatePanel(panelId, m) {
645
+ var grid = document.getElementById(panelId);
646
+ if (!grid || !m) return;
647
+ METRICS.forEach(function (def) {
648
+ var el = grid.querySelector('[data-metric="' + def.key + '"]');
649
+ if (el) {
650
+ el.textContent = def.format(m[def.key] || 0);
651
+ el.classList.remove("pending");
652
+ }
653
+ });
654
+ }
655
+
656
+ // ── Update comparison table ─────────────────────
657
+ function updateComparison(v2, next) {
658
+ var body = document.getElementById("comparison-body");
659
+ body.innerHTML = "";
660
+ METRICS.forEach(function (def) {
661
+ var v2Val = v2 ? (v2[def.key] || 0) : 0;
662
+ var nextVal = next ? (next[def.key] || 0) : 0;
663
+ var delta = nextVal - v2Val;
664
+ var pct = v2Val > 0 ? ((delta / v2Val) * 100) : 0;
665
+
666
+ var isBetter = def.lowerBetter ? delta < 0 : delta > 0;
667
+ var isWorse = def.lowerBetter ? delta > 0 : delta < 0;
668
+ var deltaClass = Math.abs(delta) < 1 ? "neutral" : (isBetter ? "faster" : "slower");
669
+
670
+ var tr = document.createElement("tr");
671
+ tr.innerHTML =
672
+ '<td class="metric-name">' + def.label + "</td>" +
673
+ "<td>" + def.format(v2Val) + "</td>" +
674
+ "<td>" + def.format(nextVal) + "</td>" +
675
+ '<td class="' + deltaClass + '">' + (delta >= 0 ? "+" : "") + def.format(Math.abs(delta)).replace(" ms", "").replace(" KB", "") + " " + def.unit + "</td>" +
676
+ '<td class="' + deltaClass + '">' + (Math.abs(pct) < 0.5 ? "—" : ((pct >= 0 ? "+" : "") + pct.toFixed(1) + "%")) + "</td>";
677
+ body.appendChild(tr);
678
+ });
679
+ }
680
+
681
+ // ── Update comparison with averaged data ────────
682
+ function updateComparisonAveraged() {
683
+ if (allRuns.length === 0) return;
684
+
685
+ var v2Avg = averageMetrics(allRuns.map(function (r) { return r.v2; }));
686
+ var nextAvg = averageMetrics(allRuns.map(function (r) { return r.next; }));
687
+
688
+ updatePanel("metrics-v2", v2Avg);
689
+ updatePanel("metrics-next", nextAvg);
690
+ updateComparison(v2Avg, nextAvg);
691
+ }
692
+
693
+ function averageMetrics(arr) {
694
+ var valid = arr.filter(Boolean);
695
+ if (valid.length === 0) return null;
696
+ var result = {};
697
+ METRICS.forEach(function (def) {
698
+ var sum = 0;
699
+ var count = 0;
700
+ valid.forEach(function (m) {
701
+ if (m[def.key] != null && m[def.key] > 0) {
702
+ sum += m[def.key];
703
+ count++;
704
+ }
705
+ });
706
+ result[def.key] = count > 0 ? sum / count : 0;
707
+ });
708
+ return result;
709
+ }
710
+
711
+ // ── Update run history table ────────────────────
712
+ function updateHistory() {
713
+ var section = document.getElementById("history-section");
714
+ var head = document.getElementById("history-head");
715
+ var body = document.getElementById("history-body");
716
+
717
+ if (allRuns.length < 2) {
718
+ section.classList.add("hidden");
719
+ return;
720
+ }
721
+ section.classList.remove("hidden");
722
+
723
+ // Header row
724
+ head.innerHTML =
725
+ "<tr><th>Run</th>" +
726
+ METRICS.map(function (d) {
727
+ return "<th>v2 " + d.label + "</th><th>next " + d.label + "</th>";
728
+ }).join("") +
729
+ "</tr>";
730
+
731
+ // Data rows
732
+ body.innerHTML = "";
733
+ allRuns.forEach(function (run, i) {
734
+ var tr = document.createElement("tr");
735
+ var cells = "<td>#" + (i + 1) + "</td>";
736
+ METRICS.forEach(function (def) {
737
+ var v2Val = run.v2 ? (run.v2[def.key] || 0) : 0;
738
+ var nextVal = run.next ? (run.next[def.key] || 0) : 0;
739
+ cells += "<td>" + def.format(v2Val) + "</td>";
740
+ cells += "<td>" + def.format(nextVal) + "</td>";
741
+ });
742
+ tr.innerHTML = cells;
743
+ body.appendChild(tr);
744
+ });
745
+
746
+ // Average row
747
+ var v2Avg = averageMetrics(allRuns.map(function (r) { return r.v2; }));
748
+ var nextAvg = averageMetrics(allRuns.map(function (r) { return r.next; }));
749
+ var avgRow = document.createElement("tr");
750
+ avgRow.className = "avg-row";
751
+ var avgCells = "<td>AVG</td>";
752
+ METRICS.forEach(function (def) {
753
+ avgCells += "<td>" + def.format(v2Avg ? v2Avg[def.key] : 0) + "</td>";
754
+ avgCells += "<td>" + def.format(nextAvg ? nextAvg[def.key] : 0) + "</td>";
755
+ });
756
+ avgRow.innerHTML = avgCells;
757
+ body.appendChild(avgRow);
758
+ }
759
+
760
+ // ── Reset panels to pending state ───────────────
761
+ function resetPanels() {
762
+ ["metrics-v2", "metrics-next"].forEach(function (id) {
763
+ var grid = document.getElementById(id);
764
+ grid.querySelectorAll(".value").forEach(function (el) {
765
+ el.textContent = "…";
766
+ el.classList.add("pending");
767
+ });
768
+ });
769
+ }
770
+
771
+ // ── Single run ──────────────────────────────────
772
+ async function runOnce() {
773
+ resetPanels();
774
+ setRunning(true);
775
+ runStatus.textContent = "Loading both versions…";
776
+
777
+ // Load both in parallel
778
+ var results = await Promise.all([
779
+ loadFrame("frame-v2-container", getV2Url(), "v2"),
780
+ loadFrame("frame-next-container", getNextUrl(), "next")
781
+ ]);
782
+
783
+ var v2 = enrichMetrics(results[0]);
784
+ var next = enrichMetrics(results[1]);
785
+
786
+ updatePanel("metrics-v2", v2);
787
+ updatePanel("metrics-next", next);
788
+ updateComparison(v2, next);
789
+
790
+ allRuns.push({ v2: v2, next: next });
791
+ updateHistory();
792
+
793
+ runStatus.textContent = "Done";
794
+ setRunning(false);
795
+ return { v2: v2, next: next };
796
+ }
797
+
798
+ // ── Multi-run ───────────────────────────────────
799
+ async function runMultiple(n) {
800
+ allRuns = [];
801
+ document.getElementById("history-section").classList.add("hidden");
802
+ setRunning(true);
803
+
804
+ for (var i = 0; i < n; i++) {
805
+ runStatus.textContent = "Run " + (i + 1) + " of " + n + "…";
806
+ resetPanels();
807
+
808
+ var results = await Promise.all([
809
+ loadFrame("frame-v2-container", getV2Url(), "v2"),
810
+ loadFrame("frame-next-container", getNextUrl(), "next")
811
+ ]);
812
+
813
+ var v2 = enrichMetrics(results[0]);
814
+ var next = enrichMetrics(results[1]);
815
+
816
+ allRuns.push({ v2: v2, next: next });
817
+ updatePanel("metrics-v2", v2);
818
+ updatePanel("metrics-next", next);
819
+ updateHistory();
820
+ updateComparisonAveraged();
821
+
822
+ // Brief pause between runs to let things settle
823
+ if (i < n - 1) {
824
+ await new Promise(function (r) { setTimeout(r, 500); });
825
+ }
826
+ }
827
+
828
+ updateComparisonAveraged();
829
+ runStatus.textContent = "Done — " + n + " runs averaged";
830
+ setRunning(false);
831
+ }
832
+
833
+ function setRunning(v) {
834
+ running = v;
835
+ btnRunN.disabled = v;
836
+ btnRunOnce.disabled = v;
837
+ }
838
+
839
+ // ── Version input save buttons ───────────────────────
840
+ ["v2", "next"].forEach(function (key) {
841
+ var input = document.getElementById("version-" + key);
842
+ var btn = document.getElementById("save-version-" + key);
843
+ var saved = input.value;
844
+ input.addEventListener("input", function () {
845
+ btn.classList.toggle("visible", input.value.trim() !== saved);
846
+ syncConfigToUrl();
847
+ });
848
+ btn.addEventListener("click", function () {
849
+ saved = input.value.trim();
850
+ btn.classList.remove("visible");
851
+ syncConfigToUrl();
852
+ });
853
+ });
854
+
855
+ // ── Event bindings ──────────────────────────────
856
+ btnRunOnce.addEventListener("click", function () {
857
+ if (!running) runOnce();
858
+ });
859
+ btnRunN.addEventListener("click", function () {
860
+ if (!running) {
861
+ var n = parseInt(iterInput.value, 10);
862
+ if (isNaN(n) || n < 1) n = 5;
863
+ if (n > 20) n = 20;
864
+ runMultiple(n);
865
+ }
866
+ });
867
+ })();
868
+ </script>
869
+ </body>
870
+ </html>