@offscript/sdk 0.1.2 → 0.1.6
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/README.md +18 -115
- package/dist/adapters/nuxt.cjs +3 -3
- package/dist/adapters/nuxt.js +2 -2
- package/dist/adapters/vanilla.cjs +5 -5
- package/dist/adapters/vanilla.d.ts +2 -3
- package/dist/adapters/vanilla.js +3 -3
- package/dist/adapters/vue.cjs +3 -3
- package/dist/adapters/vue.js +2 -2
- package/dist/{chunk-IBEPWI7S.js → chunk-5ZBR6FI5.js} +522 -1231
- package/dist/chunk-5ZBR6FI5.js.map +1 -0
- package/dist/{chunk-WUFW7TEL.js → chunk-KHKIWCLM.js} +3 -5
- package/dist/chunk-KHKIWCLM.js.map +1 -0
- package/dist/{chunk-KQVK5EJR.cjs → chunk-MZXF2LF2.cjs} +3 -5
- package/dist/chunk-MZXF2LF2.cjs.map +1 -0
- package/dist/{chunk-I6JPZAQA.cjs → chunk-PCJRHW66.cjs} +525 -1239
- package/dist/chunk-PCJRHW66.cjs.map +1 -0
- package/dist/{chunk-HECPMGDC.js → chunk-R2PPPR4K.js} +5 -5
- package/dist/chunk-R2PPPR4K.js.map +1 -0
- package/dist/chunk-RAUSXUGS.cjs +20 -0
- package/dist/chunk-RAUSXUGS.cjs.map +1 -0
- package/dist/core/LifecycleManager.d.ts +1 -0
- package/dist/core/OffscriptSDK.d.ts +41 -70
- package/dist/core/RecordingStore.d.ts +26 -0
- package/dist/index.cjs +20 -40
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/playwright/PlaywrightDataContract.d.ts +0 -7
- package/dist/playwright.cjs +13 -13
- package/dist/playwright.js +1 -1
- package/dist/recorders/ActionRecorder.d.ts +1 -1
- package/dist/recorders/ContextRecorder.d.ts +2 -8
- package/dist/recorders/HTTPRecorder.d.ts +7 -2
- package/dist/types/config.d.ts +9 -45
- package/dist/types/events.d.ts +2 -23
- package/dist/types/recording.d.ts +20 -0
- package/dist/ui/RecordingControls.d.ts +6 -4
- package/dist/utils/errors.d.ts +1 -18
- package/docs/api-contract.md +4 -53
- package/docs/architecture.md +5 -34
- package/docs/event-contract.md +3 -24
- package/docs/playwright-contract.md +3 -37
- package/package.json +2 -2
- package/dist/chunk-HECPMGDC.js.map +0 -1
- package/dist/chunk-I6JPZAQA.cjs.map +0 -1
- package/dist/chunk-IBEPWI7S.js.map +0 -1
- package/dist/chunk-KQVK5EJR.cjs.map +0 -1
- package/dist/chunk-TBPD2LMI.cjs +0 -20
- package/dist/chunk-TBPD2LMI.cjs.map +0 -1
- package/dist/chunk-WUFW7TEL.js.map +0 -1
- package/dist/core/ApiClient.d.ts +0 -24
- package/dist/core/SessionManager.d.ts +0 -45
- package/dist/transport/EventQueue.d.ts +0 -28
- package/dist/transport/EventTransport.d.ts +0 -84
- package/dist/transport/PersistentBuffer.d.ts +0 -60
- package/dist/transport/RetryManager.d.ts +0 -9
- package/dist/types/session.d.ts +0 -77
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkMZXF2LF2_cjs = require('./chunk-MZXF2LF2.cjs');
|
|
4
4
|
|
|
5
5
|
// src/types/config.ts
|
|
6
6
|
var DEFAULT_RECORDING = {
|
|
@@ -34,22 +34,7 @@ var DEFAULT_PRIVACY = {
|
|
|
34
34
|
redactKeys: [],
|
|
35
35
|
captureSensitiveValues: false,
|
|
36
36
|
maxBodyBytes: 16 * 1024,
|
|
37
|
-
textContentTypes: [/^text\//i, /json/i, /xml/i, /x-www-form-urlencoded/i, /javascript/i]
|
|
38
|
-
localRetentionMs: 24 * 36e5
|
|
39
|
-
};
|
|
40
|
-
var DEFAULT_TRANSPORT = {
|
|
41
|
-
batchMaxEvents: 200,
|
|
42
|
-
batchMaxBytes: 512 * 1024,
|
|
43
|
-
flushIntervalMs: 3e3,
|
|
44
|
-
maxMemoryEvents: 5e3,
|
|
45
|
-
maxBufferBytes: 64 * 1024 * 1024,
|
|
46
|
-
maxRetries: 6,
|
|
47
|
-
retryBaseMs: 500,
|
|
48
|
-
retryMaxMs: 3e4,
|
|
49
|
-
compress: false,
|
|
50
|
-
keepaliveMaxBytes: 60 * 1024,
|
|
51
|
-
persist: true,
|
|
52
|
-
requestTimeoutMs: 15e3
|
|
37
|
+
textContentTypes: [/^text\//i, /json/i, /xml/i, /x-www-form-urlencoded/i, /javascript/i]
|
|
53
38
|
};
|
|
54
39
|
|
|
55
40
|
// src/utils/misc.ts
|
|
@@ -88,13 +73,6 @@ function debounce(fn, ms) {
|
|
|
88
73
|
};
|
|
89
74
|
return d;
|
|
90
75
|
}
|
|
91
|
-
function approxSize(value) {
|
|
92
|
-
try {
|
|
93
|
-
return JSON.stringify(value)?.length ?? 0;
|
|
94
|
-
} catch {
|
|
95
|
-
return 0;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
76
|
function deepMerge(base, patch) {
|
|
99
77
|
const out = { ...base };
|
|
100
78
|
if (!patch) return out;
|
|
@@ -309,37 +287,12 @@ var ConfigError = class extends OffscriptError {
|
|
|
309
287
|
this.name = "ConfigError";
|
|
310
288
|
}
|
|
311
289
|
};
|
|
312
|
-
var AuthError = class extends OffscriptError {
|
|
313
|
-
constructor(message, status) {
|
|
314
|
-
super("AUTH_FAILED", message, { status });
|
|
315
|
-
this.name = "AuthError";
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
var SessionExpiredError = class extends OffscriptError {
|
|
319
|
-
constructor(message = "Sess\xE3o expirada") {
|
|
320
|
-
super("SESSION_EXPIRED", message, { status: 410 });
|
|
321
|
-
this.name = "SessionExpiredError";
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
var SessionDisabledError = class extends OffscriptError {
|
|
325
|
-
constructor(message = "Grava\xE7\xE3o desativada pelo backend") {
|
|
326
|
-
super("SESSION_DISABLED", message);
|
|
327
|
-
this.name = "SessionDisabledError";
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
290
|
var InvalidStateError = class extends OffscriptError {
|
|
331
291
|
constructor(message) {
|
|
332
292
|
super("INVALID_STATE", message);
|
|
333
293
|
this.name = "InvalidStateError";
|
|
334
294
|
}
|
|
335
295
|
};
|
|
336
|
-
var TransportError = class extends OffscriptError {
|
|
337
|
-
constructor(message, opts) {
|
|
338
|
-
super("TRANSPORT_FAILED", message, opts);
|
|
339
|
-
this.name = "TransportError";
|
|
340
|
-
this.retryable = opts.retryable;
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
296
|
var Emitter = class {
|
|
344
297
|
constructor() {
|
|
345
298
|
this.handlers = /* @__PURE__ */ new Map();
|
|
@@ -366,591 +319,6 @@ var Emitter = class {
|
|
|
366
319
|
}
|
|
367
320
|
};
|
|
368
321
|
|
|
369
|
-
// src/core/ApiClient.ts
|
|
370
|
-
var ORIGINAL_FETCH = "__offscriptOriginal";
|
|
371
|
-
function nativeFetch() {
|
|
372
|
-
const f = globalThis.fetch;
|
|
373
|
-
if (!f) throw new TransportError("fetch indispon\xEDvel", { retryable: false });
|
|
374
|
-
return (f[ORIGINAL_FETCH] ?? f).bind(globalThis);
|
|
375
|
-
}
|
|
376
|
-
var ApiClient = class {
|
|
377
|
-
constructor(apiUrl, getToken, timeoutMs) {
|
|
378
|
-
this.apiUrl = apiUrl;
|
|
379
|
-
this.getToken = getToken;
|
|
380
|
-
this.timeoutMs = timeoutMs;
|
|
381
|
-
}
|
|
382
|
-
get baseUrl() {
|
|
383
|
-
return this.apiUrl;
|
|
384
|
-
}
|
|
385
|
-
createSession(body) {
|
|
386
|
-
return this.json("POST", "/sdk/sessions", body);
|
|
387
|
-
}
|
|
388
|
-
heartbeat(sessionId, body) {
|
|
389
|
-
return this.json("POST", `/sdk/sessions/${encodeURIComponent(sessionId)}/heartbeat`, body);
|
|
390
|
-
}
|
|
391
|
-
complete(sessionId, body) {
|
|
392
|
-
return this.json("POST", `/sdk/sessions/${encodeURIComponent(sessionId)}/complete`, body);
|
|
393
|
-
}
|
|
394
|
-
status(sessionId) {
|
|
395
|
-
return this.json("GET", `/sdk/sessions/${encodeURIComponent(sessionId)}/status`);
|
|
396
|
-
}
|
|
397
|
-
async sendEvents(batch, opts = {}) {
|
|
398
|
-
const path = `/sdk/sessions/${encodeURIComponent(batch.sessionId)}/events`;
|
|
399
|
-
return this.json("POST", path, batch, { "Idempotency-Key": batch.batchId }, opts);
|
|
400
|
-
}
|
|
401
|
-
async json(method, path, body, extraHeaders = {}, opts = {}) {
|
|
402
|
-
const headers = {
|
|
403
|
-
Accept: "application/json",
|
|
404
|
-
Authorization: `Bearer ${this.getToken()}`,
|
|
405
|
-
"X-Offscript-Sdk": chunkKQVK5EJR_cjs.SDK_VERSION,
|
|
406
|
-
...extraHeaders
|
|
407
|
-
};
|
|
408
|
-
let payload;
|
|
409
|
-
if (body !== void 0) {
|
|
410
|
-
headers["Content-Type"] = "application/json";
|
|
411
|
-
const text = JSON.stringify(body);
|
|
412
|
-
payload = text;
|
|
413
|
-
if (opts.compress && !opts.keepalive && text.length > 1024) {
|
|
414
|
-
const gz = await gzip(text);
|
|
415
|
-
if (gz) {
|
|
416
|
-
payload = gz;
|
|
417
|
-
headers["Content-Encoding"] = "gzip";
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const ctrl = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
422
|
-
const timer = ctrl && !opts.keepalive ? setTimeout(() => ctrl.abort(), this.timeoutMs) : void 0;
|
|
423
|
-
let res;
|
|
424
|
-
try {
|
|
425
|
-
res = await nativeFetch()(this.apiUrl + path, {
|
|
426
|
-
method,
|
|
427
|
-
headers,
|
|
428
|
-
body: payload,
|
|
429
|
-
keepalive: opts.keepalive,
|
|
430
|
-
credentials: "omit",
|
|
431
|
-
mode: "cors",
|
|
432
|
-
signal: ctrl?.signal
|
|
433
|
-
});
|
|
434
|
-
} catch (e) {
|
|
435
|
-
throw new TransportError(`Falha de rede: ${e?.message ?? e}`, { retryable: true, cause: e });
|
|
436
|
-
} finally {
|
|
437
|
-
if (timer) clearTimeout(timer);
|
|
438
|
-
}
|
|
439
|
-
if (res.ok) {
|
|
440
|
-
const text = await res.text();
|
|
441
|
-
return text ? JSON.parse(text) : {};
|
|
442
|
-
}
|
|
443
|
-
const detail = await res.text().catch(() => "");
|
|
444
|
-
const msg = `HTTP ${res.status} ${method} ${path}${detail ? `: ${detail.slice(0, 200)}` : ""}`;
|
|
445
|
-
if (res.status === 401 || res.status === 403) throw new AuthError(msg, res.status);
|
|
446
|
-
if (res.status === 410) throw new SessionExpiredError(msg);
|
|
447
|
-
const retryAfter = Number(res.headers.get("retry-after"));
|
|
448
|
-
const err = new TransportError(msg, {
|
|
449
|
-
status: res.status,
|
|
450
|
-
retryable: res.status === 408 || res.status === 429 || res.status >= 500
|
|
451
|
-
});
|
|
452
|
-
if (retryAfter > 0) err.retryAfterMs = retryAfter * 1e3;
|
|
453
|
-
throw err;
|
|
454
|
-
}
|
|
455
|
-
};
|
|
456
|
-
async function gzip(text) {
|
|
457
|
-
try {
|
|
458
|
-
const CS = globalThis.CompressionStream;
|
|
459
|
-
if (!CS) return null;
|
|
460
|
-
const body = new Response(text).body;
|
|
461
|
-
if (!body) return null;
|
|
462
|
-
return new Uint8Array(await new Response(body.pipeThrough(new CS("gzip"))).arrayBuffer());
|
|
463
|
-
} catch {
|
|
464
|
-
return null;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// src/transport/EventQueue.ts
|
|
469
|
-
var EventQueue = class {
|
|
470
|
-
constructor(maxEvents, maxBytes) {
|
|
471
|
-
this.maxEvents = maxEvents;
|
|
472
|
-
this.maxBytes = maxBytes;
|
|
473
|
-
this.staged = [];
|
|
474
|
-
this.pendingCount = 0;
|
|
475
|
-
this.pendingBytes = 0;
|
|
476
|
-
this.dropped = 0;
|
|
477
|
-
}
|
|
478
|
-
canAccept(priority) {
|
|
479
|
-
const factor = priority === "critical" ? 2 : priority === "normal" ? 1 : 0.5;
|
|
480
|
-
return this.pendingCount < this.maxEvents * factor && this.pendingBytes < this.maxBytes * factor;
|
|
481
|
-
}
|
|
482
|
-
noteDropped() {
|
|
483
|
-
this.dropped++;
|
|
484
|
-
}
|
|
485
|
-
push(e, size) {
|
|
486
|
-
this.staged.push(e);
|
|
487
|
-
this.pendingCount++;
|
|
488
|
-
this.pendingBytes += size;
|
|
489
|
-
}
|
|
490
|
-
/** Devolve eventos ao staging sem recontar. */
|
|
491
|
-
restore(events) {
|
|
492
|
-
this.staged = [...events, ...this.staged];
|
|
493
|
-
}
|
|
494
|
-
takeStaged() {
|
|
495
|
-
const s = this.staged;
|
|
496
|
-
this.staged = [];
|
|
497
|
-
return s;
|
|
498
|
-
}
|
|
499
|
-
peekStaged() {
|
|
500
|
-
return this.staged;
|
|
501
|
-
}
|
|
502
|
-
get stagedCount() {
|
|
503
|
-
return this.staged.length;
|
|
504
|
-
}
|
|
505
|
-
acked(count, bytes) {
|
|
506
|
-
this.pendingCount = Math.max(0, this.pendingCount - count);
|
|
507
|
-
this.pendingBytes = Math.max(0, this.pendingBytes - bytes);
|
|
508
|
-
}
|
|
509
|
-
reset(count, bytes) {
|
|
510
|
-
this.pendingCount = count;
|
|
511
|
-
this.pendingBytes = bytes;
|
|
512
|
-
}
|
|
513
|
-
get pending() {
|
|
514
|
-
return this.pendingCount;
|
|
515
|
-
}
|
|
516
|
-
get bytes() {
|
|
517
|
-
return this.pendingBytes;
|
|
518
|
-
}
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
// src/transport/RetryManager.ts
|
|
522
|
-
var RetryManager = class {
|
|
523
|
-
constructor(baseMs, maxMs, random = Math.random) {
|
|
524
|
-
this.baseMs = baseMs;
|
|
525
|
-
this.maxMs = maxMs;
|
|
526
|
-
this.random = random;
|
|
527
|
-
}
|
|
528
|
-
delay(attempt) {
|
|
529
|
-
const cap = Math.min(this.maxMs, this.baseMs * 2 ** Math.max(0, attempt));
|
|
530
|
-
return Math.round(this.random() * cap);
|
|
531
|
-
}
|
|
532
|
-
sleep(ms) {
|
|
533
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
534
|
-
}
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
// src/transport/EventTransport.ts
|
|
538
|
-
var EventTransport = class extends Emitter {
|
|
539
|
-
constructor(cfg, api, buffer) {
|
|
540
|
-
super();
|
|
541
|
-
this.cfg = cfg;
|
|
542
|
-
this.api = api;
|
|
543
|
-
this.buffer = buffer;
|
|
544
|
-
this.identity = null;
|
|
545
|
-
this.running = null;
|
|
546
|
-
this.again = false;
|
|
547
|
-
this.failures = 0;
|
|
548
|
-
this.stopped = true;
|
|
549
|
-
this.fatal = false;
|
|
550
|
-
this.online = true;
|
|
551
|
-
this.lastDroppedReported = 0;
|
|
552
|
-
this.queue = new EventQueue(cfg.maxMemoryEvents, cfg.maxBufferBytes);
|
|
553
|
-
this.retry = new RetryManager(cfg.retryBaseMs, cfg.retryMaxMs);
|
|
554
|
-
}
|
|
555
|
-
get bufferKind() {
|
|
556
|
-
return this.buffer.kind;
|
|
557
|
-
}
|
|
558
|
-
/** Inicia o transporte para uma sessão/aba e recupera o que ficou pendente no buffer. */
|
|
559
|
-
async start(identity) {
|
|
560
|
-
this.identity = identity;
|
|
561
|
-
this.stopped = false;
|
|
562
|
-
this.fatal = false;
|
|
563
|
-
this.failures = 0;
|
|
564
|
-
const st = await this.buffer.stats(identity.sessionId).catch(() => ({ count: 0, bytes: 0 }));
|
|
565
|
-
this.queue.reset(st.count, st.bytes);
|
|
566
|
-
this.scheduleFlush();
|
|
567
|
-
}
|
|
568
|
-
setOnline(online) {
|
|
569
|
-
this.online = online;
|
|
570
|
-
if (online && !this.stopped) void this.flush();
|
|
571
|
-
}
|
|
572
|
-
canAccept(priority) {
|
|
573
|
-
return !this.stopped && !this.fatal && this.queue.canAccept(priority);
|
|
574
|
-
}
|
|
575
|
-
noteDropped() {
|
|
576
|
-
this.queue.noteDropped();
|
|
577
|
-
if (this.queue.dropped - this.lastDroppedReported >= 1) {
|
|
578
|
-
this.lastDroppedReported = this.queue.dropped;
|
|
579
|
-
this.emit("overflow", { dropped: this.queue.dropped });
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
get pending() {
|
|
583
|
-
return this.queue.pending;
|
|
584
|
-
}
|
|
585
|
-
enqueue(event) {
|
|
586
|
-
if (this.stopped) return;
|
|
587
|
-
this.queue.push(event, approxSize(event));
|
|
588
|
-
if (this.queue.stagedCount >= 50) void this.commit();
|
|
589
|
-
else if (!this.commitTimer) this.commitTimer = setTimeout(() => void this.commit(), 100);
|
|
590
|
-
}
|
|
591
|
-
async commit() {
|
|
592
|
-
if (this.commitTimer) clearTimeout(this.commitTimer);
|
|
593
|
-
this.commitTimer = void 0;
|
|
594
|
-
const staged = this.queue.takeStaged();
|
|
595
|
-
if (!staged.length) return;
|
|
596
|
-
try {
|
|
597
|
-
await this.buffer.put(staged);
|
|
598
|
-
} catch {
|
|
599
|
-
this.queue.restore(staged);
|
|
600
|
-
throw new TransportError("Falha ao persistir eventos localmente", { retryable: true });
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
scheduleFlush() {
|
|
604
|
-
if (this.flushTimer || this.stopped) return;
|
|
605
|
-
this.flushTimer = setTimeout(() => {
|
|
606
|
-
this.flushTimer = void 0;
|
|
607
|
-
void this.flush().finally(() => this.scheduleFlush());
|
|
608
|
-
}, this.cfg.flushIntervalMs);
|
|
609
|
-
}
|
|
610
|
-
/** Flush de melhor esforço. Retorna true se tudo foi confirmado pelo backend. */
|
|
611
|
-
flush(opts = { inlineRetries: 0 }) {
|
|
612
|
-
if (this.running) {
|
|
613
|
-
this.again = true;
|
|
614
|
-
return this.running;
|
|
615
|
-
}
|
|
616
|
-
this.running = (async () => {
|
|
617
|
-
try {
|
|
618
|
-
let ok = await this.drain(opts);
|
|
619
|
-
while (this.again && ok) {
|
|
620
|
-
this.again = false;
|
|
621
|
-
ok = await this.drain(opts);
|
|
622
|
-
}
|
|
623
|
-
return ok;
|
|
624
|
-
} finally {
|
|
625
|
-
this.running = null;
|
|
626
|
-
this.again = false;
|
|
627
|
-
}
|
|
628
|
-
})();
|
|
629
|
-
return this.running;
|
|
630
|
-
}
|
|
631
|
-
/** Flush final com retries inline e prazo máximo. */
|
|
632
|
-
async flushFinal(maxWaitMs = 1e4) {
|
|
633
|
-
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
634
|
-
this.retryTimer = void 0;
|
|
635
|
-
const running = this.running;
|
|
636
|
-
if (running) await running.catch(() => false);
|
|
637
|
-
const ok = await this.flush({ inlineRetries: this.cfg.maxRetries, deadline: Date.now() + maxWaitMs });
|
|
638
|
-
return { ok, pending: this.queue.pending };
|
|
639
|
-
}
|
|
640
|
-
async drain(opts) {
|
|
641
|
-
const id = this.identity;
|
|
642
|
-
if (!id || this.fatal) return false;
|
|
643
|
-
for (; ; ) {
|
|
644
|
-
try {
|
|
645
|
-
await this.commit();
|
|
646
|
-
const { items } = await this.buffer.peek(id.sessionId, id.tabId, this.cfg.batchMaxEvents, this.cfg.batchMaxBytes);
|
|
647
|
-
if (!items.length) {
|
|
648
|
-
this.failures = 0;
|
|
649
|
-
return true;
|
|
650
|
-
}
|
|
651
|
-
const events = items.map((i) => i.event);
|
|
652
|
-
await this.sendWithRetry(events, opts);
|
|
653
|
-
await this.buffer.remove(items.map((i) => i.key));
|
|
654
|
-
this.queue.acked(items.length, items.reduce((n, i) => n + i.size, 0));
|
|
655
|
-
this.failures = 0;
|
|
656
|
-
} catch (error) {
|
|
657
|
-
return this.onFailure(error);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
onFailure(error) {
|
|
662
|
-
if (error instanceof AuthError || error instanceof SessionExpiredError) {
|
|
663
|
-
this.fatal = true;
|
|
664
|
-
this.emit("fatal", { error });
|
|
665
|
-
return false;
|
|
666
|
-
}
|
|
667
|
-
this.failures++;
|
|
668
|
-
const retryable = !(error instanceof TransportError) || error.retryable;
|
|
669
|
-
const willRetryInMs = retryable ? this.retry.delay(this.failures) : void 0;
|
|
670
|
-
this.emit("failed", { error, willRetryInMs });
|
|
671
|
-
if (retryable && !this.stopped) {
|
|
672
|
-
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
673
|
-
this.retryTimer = setTimeout(() => {
|
|
674
|
-
this.retryTimer = void 0;
|
|
675
|
-
void this.flush();
|
|
676
|
-
}, willRetryInMs);
|
|
677
|
-
}
|
|
678
|
-
return false;
|
|
679
|
-
}
|
|
680
|
-
async sendWithRetry(events, opts) {
|
|
681
|
-
const batch = this.buildBatch(events);
|
|
682
|
-
for (let attempt = 0; ; attempt++) {
|
|
683
|
-
try {
|
|
684
|
-
if (!this.online) throw new TransportError("Offline", { retryable: true });
|
|
685
|
-
const ack = await this.api.sendEvents(batch, { compress: this.cfg.compress });
|
|
686
|
-
this.emit("flushed", { events: events.length, batchId: batch.batchId, duplicate: !!ack.duplicate });
|
|
687
|
-
return;
|
|
688
|
-
} catch (e) {
|
|
689
|
-
if (e instanceof TransportError && e.status === 413 && events.length > 1) {
|
|
690
|
-
const mid = Math.ceil(events.length / 2);
|
|
691
|
-
await this.sendWithRetry(events.slice(0, mid), opts);
|
|
692
|
-
await this.sendWithRetry(events.slice(mid), opts);
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
const retryable = !(e instanceof TransportError) || e.retryable;
|
|
696
|
-
if (!retryable || attempt >= opts.inlineRetries) throw e;
|
|
697
|
-
const wait = Math.max(this.retry.delay(attempt), e.retryAfterMs ?? 0);
|
|
698
|
-
if (opts.deadline && Date.now() + wait > opts.deadline) throw e;
|
|
699
|
-
await this.retry.sleep(wait);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
buildBatch(events) {
|
|
704
|
-
const id = this.identity;
|
|
705
|
-
const first = events[0].sequence;
|
|
706
|
-
const last = events[events.length - 1].sequence;
|
|
707
|
-
return {
|
|
708
|
-
schemaVersion: chunkKQVK5EJR_cjs.SCHEMA_VERSION,
|
|
709
|
-
batchId: `${id.sessionId}.${id.tabId}.${first}-${last}.${events.length}`,
|
|
710
|
-
sessionId: id.sessionId,
|
|
711
|
-
executionId: id.executionId,
|
|
712
|
-
tabId: id.tabId,
|
|
713
|
-
sequenceStart: first,
|
|
714
|
-
sequenceEnd: last,
|
|
715
|
-
createdAt: Date.now(),
|
|
716
|
-
sdkVersion: chunkKQVK5EJR_cjs.SDK_VERSION,
|
|
717
|
-
events
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* Chamado em pagehide/beforeunload. Persiste o que está em staging (best effort, assíncrono) e
|
|
722
|
-
* tenta enviar o final da fila com fetch keepalive (limite ~64KB). Sem ACK: os eventos continuam
|
|
723
|
-
* no IndexedDB e serão reenviados (de forma idempotente) na próxima página.
|
|
724
|
-
* Não há garantia de entrega se o navegador for encerrado abruptamente.
|
|
725
|
-
*/
|
|
726
|
-
flushOnUnload() {
|
|
727
|
-
const id = this.identity;
|
|
728
|
-
if (!id || this.stopped || this.fatal) return;
|
|
729
|
-
const staged = this.queue.takeStaged();
|
|
730
|
-
if (!staged.length) return;
|
|
731
|
-
safe(() => void this.buffer.put(staged).catch(() => void 0));
|
|
732
|
-
const events = [];
|
|
733
|
-
let bytes = 0;
|
|
734
|
-
for (const e of staged) {
|
|
735
|
-
const s = approxSize(e);
|
|
736
|
-
if (bytes + s > this.cfg.keepaliveMaxBytes) break;
|
|
737
|
-
events.push(e);
|
|
738
|
-
bytes += s;
|
|
739
|
-
}
|
|
740
|
-
if (!events.length) return;
|
|
741
|
-
safe(() => void this.api.sendEvents(this.buildBatch(events), { keepalive: true }).catch(() => void 0));
|
|
742
|
-
}
|
|
743
|
-
/** Para timers; eventos não confirmados permanecem no buffer. */
|
|
744
|
-
stop() {
|
|
745
|
-
this.stopped = true;
|
|
746
|
-
for (const t of [this.commitTimer, this.flushTimer, this.retryTimer]) if (t) clearTimeout(t);
|
|
747
|
-
this.commitTimer = this.flushTimer = this.retryTimer = void 0;
|
|
748
|
-
}
|
|
749
|
-
async clearSession(sessionId) {
|
|
750
|
-
await this.buffer.clearSession(sessionId).catch(() => void 0);
|
|
751
|
-
this.queue.reset(0, 0);
|
|
752
|
-
}
|
|
753
|
-
async purgeExpired(retentionMs) {
|
|
754
|
-
await this.buffer.purgeOlderThan(Date.now() - retentionMs).catch(() => void 0);
|
|
755
|
-
}
|
|
756
|
-
close() {
|
|
757
|
-
this.stop();
|
|
758
|
-
this.buffer.close();
|
|
759
|
-
}
|
|
760
|
-
};
|
|
761
|
-
|
|
762
|
-
// src/transport/PersistentBuffer.ts
|
|
763
|
-
var eventKey = (sessionId, tabId, seq) => `${sessionId}|${tabId}|${String(seq).padStart(12, "0")}`;
|
|
764
|
-
var toStored = (e) => ({
|
|
765
|
-
key: eventKey(e.sessionId, e.tabId, e.sequence),
|
|
766
|
-
sessionId: e.sessionId,
|
|
767
|
-
tabId: e.tabId,
|
|
768
|
-
sequence: e.sequence,
|
|
769
|
-
storedAt: Date.now(),
|
|
770
|
-
size: approxSize(e),
|
|
771
|
-
event: e
|
|
772
|
-
});
|
|
773
|
-
var MemoryBuffer = class {
|
|
774
|
-
constructor() {
|
|
775
|
-
this.kind = "memory";
|
|
776
|
-
this.map = /* @__PURE__ */ new Map();
|
|
777
|
-
}
|
|
778
|
-
async put(events) {
|
|
779
|
-
for (const e of events) {
|
|
780
|
-
const s = toStored(e);
|
|
781
|
-
this.map.set(s.key, s);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
async peek(sessionId, tabId, maxEvents, maxBytes) {
|
|
785
|
-
const prefix = `${sessionId}|${tabId}|`;
|
|
786
|
-
const keys = Array.from(this.map.keys()).filter((k) => k.startsWith(prefix)).sort();
|
|
787
|
-
const items = [];
|
|
788
|
-
let bytes = 0;
|
|
789
|
-
for (const k of keys) {
|
|
790
|
-
const s = this.map.get(k);
|
|
791
|
-
if (items.length >= maxEvents || items.length > 0 && bytes + s.size > maxBytes) break;
|
|
792
|
-
items.push(s);
|
|
793
|
-
bytes += s.size;
|
|
794
|
-
}
|
|
795
|
-
return { items, bytes };
|
|
796
|
-
}
|
|
797
|
-
async remove(keys) {
|
|
798
|
-
for (const k of keys) this.map.delete(k);
|
|
799
|
-
}
|
|
800
|
-
async stats(sessionId) {
|
|
801
|
-
let count = 0;
|
|
802
|
-
let bytes = 0;
|
|
803
|
-
for (const s of this.map.values()) {
|
|
804
|
-
if (sessionId && s.sessionId !== sessionId) continue;
|
|
805
|
-
count++;
|
|
806
|
-
bytes += s.size;
|
|
807
|
-
}
|
|
808
|
-
return { count, bytes };
|
|
809
|
-
}
|
|
810
|
-
async purgeOlderThan(ts) {
|
|
811
|
-
let n = 0;
|
|
812
|
-
for (const [k, s] of this.map) if (s.storedAt < ts) this.map.delete(k), n++;
|
|
813
|
-
return n;
|
|
814
|
-
}
|
|
815
|
-
async clearSession(sessionId) {
|
|
816
|
-
for (const [k, s] of this.map) if (s.sessionId === sessionId) this.map.delete(k);
|
|
817
|
-
}
|
|
818
|
-
close() {
|
|
819
|
-
}
|
|
820
|
-
};
|
|
821
|
-
var STORE = "events";
|
|
822
|
-
function req(r) {
|
|
823
|
-
return new Promise((resolve, reject) => {
|
|
824
|
-
r.onsuccess = () => resolve(r.result);
|
|
825
|
-
r.onerror = () => reject(r.error);
|
|
826
|
-
});
|
|
827
|
-
}
|
|
828
|
-
function done(tx) {
|
|
829
|
-
return new Promise((resolve, reject) => {
|
|
830
|
-
tx.oncomplete = () => resolve();
|
|
831
|
-
tx.onerror = () => reject(tx.error);
|
|
832
|
-
tx.onabort = () => reject(tx.error);
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
var IndexedDBBuffer = class _IndexedDBBuffer {
|
|
836
|
-
constructor(db) {
|
|
837
|
-
this.db = db;
|
|
838
|
-
this.kind = "indexeddb";
|
|
839
|
-
}
|
|
840
|
-
static async open(name) {
|
|
841
|
-
if (typeof indexedDB === "undefined") throw new Error("IndexedDB indispon\xEDvel");
|
|
842
|
-
const db = await new Promise((resolve, reject) => {
|
|
843
|
-
const r = indexedDB.open(name, 1);
|
|
844
|
-
r.onupgradeneeded = () => {
|
|
845
|
-
const store = r.result.createObjectStore(STORE, { keyPath: "key" });
|
|
846
|
-
store.createIndex("bySession", "sessionId");
|
|
847
|
-
store.createIndex("byStoredAt", "storedAt");
|
|
848
|
-
};
|
|
849
|
-
r.onsuccess = () => resolve(r.result);
|
|
850
|
-
r.onerror = () => reject(r.error);
|
|
851
|
-
r.onblocked = () => reject(new Error("IndexedDB bloqueado"));
|
|
852
|
-
});
|
|
853
|
-
return new _IndexedDBBuffer(db);
|
|
854
|
-
}
|
|
855
|
-
async put(events) {
|
|
856
|
-
if (!events.length) return;
|
|
857
|
-
const tx = this.db.transaction(STORE, "readwrite");
|
|
858
|
-
const store = tx.objectStore(STORE);
|
|
859
|
-
for (const e of events) store.put(toStored(e));
|
|
860
|
-
await done(tx);
|
|
861
|
-
}
|
|
862
|
-
async peek(sessionId, tabId, maxEvents, maxBytes) {
|
|
863
|
-
const prefix = `${sessionId}|${tabId}|`;
|
|
864
|
-
const range = IDBKeyRange.bound(prefix, prefix + "\uFFFF");
|
|
865
|
-
const tx = this.db.transaction(STORE, "readonly");
|
|
866
|
-
const items = [];
|
|
867
|
-
let bytes = 0;
|
|
868
|
-
await new Promise((resolve, reject) => {
|
|
869
|
-
const cur = tx.objectStore(STORE).openCursor(range);
|
|
870
|
-
cur.onsuccess = () => {
|
|
871
|
-
const c = cur.result;
|
|
872
|
-
if (!c) return resolve();
|
|
873
|
-
const s = c.value;
|
|
874
|
-
if (items.length >= maxEvents || items.length > 0 && bytes + s.size > maxBytes) return resolve();
|
|
875
|
-
items.push(s);
|
|
876
|
-
bytes += s.size;
|
|
877
|
-
c.continue();
|
|
878
|
-
};
|
|
879
|
-
cur.onerror = () => reject(cur.error);
|
|
880
|
-
});
|
|
881
|
-
return { items, bytes };
|
|
882
|
-
}
|
|
883
|
-
async remove(keys) {
|
|
884
|
-
if (!keys.length) return;
|
|
885
|
-
const tx = this.db.transaction(STORE, "readwrite");
|
|
886
|
-
const store = tx.objectStore(STORE);
|
|
887
|
-
for (const k of keys) store.delete(k);
|
|
888
|
-
await done(tx);
|
|
889
|
-
}
|
|
890
|
-
async stats(sessionId) {
|
|
891
|
-
const tx = this.db.transaction(STORE, "readonly");
|
|
892
|
-
const store = tx.objectStore(STORE);
|
|
893
|
-
if (!sessionId) return { count: await req(store.count()), bytes: 0 };
|
|
894
|
-
let count = 0;
|
|
895
|
-
let bytes = 0;
|
|
896
|
-
await new Promise((resolve, reject) => {
|
|
897
|
-
const cur = store.index("bySession").openCursor(IDBKeyRange.only(sessionId));
|
|
898
|
-
cur.onsuccess = () => {
|
|
899
|
-
const c = cur.result;
|
|
900
|
-
if (!c) return resolve();
|
|
901
|
-
count++;
|
|
902
|
-
bytes += c.value.size;
|
|
903
|
-
c.continue();
|
|
904
|
-
};
|
|
905
|
-
cur.onerror = () => reject(cur.error);
|
|
906
|
-
});
|
|
907
|
-
return { count, bytes };
|
|
908
|
-
}
|
|
909
|
-
async purgeOlderThan(ts) {
|
|
910
|
-
const tx = this.db.transaction(STORE, "readwrite");
|
|
911
|
-
let n = 0;
|
|
912
|
-
await new Promise((resolve, reject) => {
|
|
913
|
-
const cur = tx.objectStore(STORE).index("byStoredAt").openCursor(IDBKeyRange.upperBound(ts, true));
|
|
914
|
-
cur.onsuccess = () => {
|
|
915
|
-
const c = cur.result;
|
|
916
|
-
if (!c) return resolve();
|
|
917
|
-
c.delete();
|
|
918
|
-
n++;
|
|
919
|
-
c.continue();
|
|
920
|
-
};
|
|
921
|
-
cur.onerror = () => reject(cur.error);
|
|
922
|
-
});
|
|
923
|
-
await done(tx);
|
|
924
|
-
return n;
|
|
925
|
-
}
|
|
926
|
-
async clearSession(sessionId) {
|
|
927
|
-
const tx = this.db.transaction(STORE, "readwrite");
|
|
928
|
-
await new Promise((resolve, reject) => {
|
|
929
|
-
const cur = tx.objectStore(STORE).index("bySession").openCursor(IDBKeyRange.only(sessionId));
|
|
930
|
-
cur.onsuccess = () => {
|
|
931
|
-
const c = cur.result;
|
|
932
|
-
if (!c) return resolve();
|
|
933
|
-
c.delete();
|
|
934
|
-
c.continue();
|
|
935
|
-
};
|
|
936
|
-
cur.onerror = () => reject(cur.error);
|
|
937
|
-
});
|
|
938
|
-
await done(tx);
|
|
939
|
-
}
|
|
940
|
-
close() {
|
|
941
|
-
safe(() => this.db.close());
|
|
942
|
-
}
|
|
943
|
-
};
|
|
944
|
-
async function openBuffer(name, persist) {
|
|
945
|
-
if (persist) {
|
|
946
|
-
try {
|
|
947
|
-
return await IndexedDBBuffer.open(name);
|
|
948
|
-
} catch {
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
return new MemoryBuffer();
|
|
952
|
-
}
|
|
953
|
-
|
|
954
322
|
// src/recorders/types.ts
|
|
955
323
|
function guard(sink, name, fn) {
|
|
956
324
|
try {
|
|
@@ -979,7 +347,7 @@ var ActionRecorder = class {
|
|
|
979
347
|
this.scrollState = /* @__PURE__ */ new Map();
|
|
980
348
|
this.move = [];
|
|
981
349
|
const r = sink.config.recording;
|
|
982
|
-
this.gen = new
|
|
350
|
+
this.gen = new chunkMZXF2LF2_cjs.SelectorGenerator({ testIdAttributes: r.testIdAttributes, stableAttributes: r.stableAttributes });
|
|
983
351
|
}
|
|
984
352
|
start() {
|
|
985
353
|
const d = this.d;
|
|
@@ -1043,7 +411,7 @@ var ActionRecorder = class {
|
|
|
1043
411
|
emit(draft, el, extra) {
|
|
1044
412
|
const s = this.sink.sanitizer;
|
|
1045
413
|
const data = {
|
|
1046
|
-
schemaVersion:
|
|
414
|
+
schemaVersion: chunkMZXF2LF2_cjs.ACTION_SCHEMA_VERSION,
|
|
1047
415
|
type: draft.type,
|
|
1048
416
|
url: s.sanitizeUrl(location.href),
|
|
1049
417
|
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
@@ -1280,7 +648,7 @@ function parseBrowser(ua) {
|
|
|
1280
648
|
}
|
|
1281
649
|
return {};
|
|
1282
650
|
}
|
|
1283
|
-
function collectContext(
|
|
651
|
+
function collectContext(reason, captureSensitiveValues = false) {
|
|
1284
652
|
const nav = navigator;
|
|
1285
653
|
const ua = nav.userAgent ?? "";
|
|
1286
654
|
let browser = parseBrowser(ua);
|
|
@@ -1304,18 +672,13 @@ function collectContext(id, reason, appVersion, captureSensitiveValues = false)
|
|
|
1304
672
|
timezone: safe(() => Intl.DateTimeFormat().resolvedOptions().timeZone),
|
|
1305
673
|
online: nav.onLine,
|
|
1306
674
|
visibility: document.visibilityState,
|
|
1307
|
-
|
|
1308
|
-
testCaseId: id.testCaseId,
|
|
1309
|
-
executionId: id.executionId,
|
|
1310
|
-
sdkVersion: chunkKQVK5EJR_cjs.SDK_VERSION,
|
|
1311
|
-
appVersion,
|
|
675
|
+
sdkVersion: chunkMZXF2LF2_cjs.SDK_VERSION,
|
|
1312
676
|
captureSensitiveValues
|
|
1313
677
|
};
|
|
1314
678
|
}
|
|
1315
679
|
var ContextRecorder = class {
|
|
1316
|
-
constructor(sink
|
|
680
|
+
constructor(sink) {
|
|
1317
681
|
this.sink = sink;
|
|
1318
|
-
this.identity = identity;
|
|
1319
682
|
this.name = "context";
|
|
1320
683
|
this.d = new Disposer();
|
|
1321
684
|
}
|
|
@@ -1328,7 +691,7 @@ var ContextRecorder = class {
|
|
|
1328
691
|
}
|
|
1329
692
|
record(reason) {
|
|
1330
693
|
guard(this.sink, this.name, () => {
|
|
1331
|
-
const data = collectContext(
|
|
694
|
+
const data = collectContext(reason, this.sink.config.privacy.captureSensitiveValues);
|
|
1332
695
|
data.url = this.sink.sanitizer.sanitizeUrl(data.url);
|
|
1333
696
|
if (data.referrer) data.referrer = this.sink.sanitizer.sanitizeUrl(data.referrer);
|
|
1334
697
|
this.sink.emit("context", data, { priority: "normal" });
|
|
@@ -1487,23 +850,28 @@ function uid(prefix = "") {
|
|
|
1487
850
|
}
|
|
1488
851
|
|
|
1489
852
|
// src/recorders/HTTPRecorder.ts
|
|
1490
|
-
var XHR_PATCHED = "__offscriptXhrOriginal";
|
|
1491
853
|
var HTTPRecorder = class {
|
|
1492
|
-
constructor(sink
|
|
854
|
+
constructor(sink) {
|
|
1493
855
|
this.sink = sink;
|
|
1494
|
-
this.ownApiUrl = ownApiUrl;
|
|
1495
856
|
this.name = "http";
|
|
1496
857
|
this.inflight = 0;
|
|
1497
858
|
this.active = false;
|
|
859
|
+
this.delivering = false;
|
|
860
|
+
this.pending = /* @__PURE__ */ new Set();
|
|
1498
861
|
this.restore = [];
|
|
1499
862
|
}
|
|
1500
863
|
start() {
|
|
1501
864
|
if (this.active) return;
|
|
1502
865
|
this.active = true;
|
|
866
|
+
this.delivering = true;
|
|
1503
867
|
this.patchFetch();
|
|
1504
868
|
this.patchXhr();
|
|
1505
869
|
}
|
|
1506
870
|
stop() {
|
|
871
|
+
this.delivering = false;
|
|
872
|
+
this.detach();
|
|
873
|
+
}
|
|
874
|
+
detach() {
|
|
1507
875
|
this.active = false;
|
|
1508
876
|
for (const r of this.restore.splice(0).reverse()) {
|
|
1509
877
|
try {
|
|
@@ -1512,8 +880,30 @@ var HTTPRecorder = class {
|
|
|
1512
880
|
}
|
|
1513
881
|
}
|
|
1514
882
|
}
|
|
883
|
+
/** Stops new capture and allows up to two seconds for pending HTTP/body capture. */
|
|
884
|
+
async drain() {
|
|
885
|
+
this.detach();
|
|
886
|
+
let timer;
|
|
887
|
+
const timeout = new Promise((resolve) => {
|
|
888
|
+
timer = setTimeout(resolve, 2e3);
|
|
889
|
+
});
|
|
890
|
+
const settle = async () => {
|
|
891
|
+
while (this.delivering && this.pending.size) {
|
|
892
|
+
await Promise.allSettled([...this.pending]);
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
try {
|
|
896
|
+
await Promise.race([settle(), timeout]);
|
|
897
|
+
} finally {
|
|
898
|
+
clearTimeout(timer);
|
|
899
|
+
this.delivering = false;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
track(promise) {
|
|
903
|
+
const tracked = promise.catch(() => void 0).finally(() => this.pending.delete(tracked));
|
|
904
|
+
this.pending.add(tracked);
|
|
905
|
+
}
|
|
1515
906
|
isIgnored(url) {
|
|
1516
|
-
if (this.ownApiUrl && url.startsWith(this.ownApiUrl)) return true;
|
|
1517
907
|
return this.sink.sanitizer.isIgnoredUrl(url);
|
|
1518
908
|
}
|
|
1519
909
|
// ---------------------------------------------------------------- fetch
|
|
@@ -1539,14 +929,13 @@ var HTTPRecorder = class {
|
|
|
1539
929
|
}
|
|
1540
930
|
if (ctx) {
|
|
1541
931
|
const c = ctx;
|
|
1542
|
-
p.then(
|
|
932
|
+
self.track(p.then(
|
|
1543
933
|
(res) => self.endFetch(c, res),
|
|
1544
934
|
(err) => self.failFetch(c, err)
|
|
1545
|
-
)
|
|
935
|
+
));
|
|
1546
936
|
}
|
|
1547
937
|
return p;
|
|
1548
938
|
};
|
|
1549
|
-
patched[ORIGINAL_FETCH] = orig[ORIGINAL_FETCH] ?? orig;
|
|
1550
939
|
globalThis.fetch = patched;
|
|
1551
940
|
this.restore.push(() => {
|
|
1552
941
|
if (globalThis.fetch === patched) globalThis.fetch = orig;
|
|
@@ -1594,13 +983,13 @@ var HTTPRecorder = class {
|
|
|
1594
983
|
base.responseBody = { kind: "omitted", reason: "body-used" };
|
|
1595
984
|
return void this.emit(base);
|
|
1596
985
|
}
|
|
1597
|
-
this.readBounded(clone).then(({ text, size, truncated }) => {
|
|
986
|
+
this.track(this.readBounded(clone).then(({ text, size, truncated }) => {
|
|
1598
987
|
const b = this.sink.sanitizer.sanitizeTextBody(text, ct, size);
|
|
1599
988
|
b.truncated = truncated || b.truncated;
|
|
1600
989
|
base.responseBody = b;
|
|
1601
990
|
}).catch(() => {
|
|
1602
991
|
base.responseBody = { kind: "omitted", reason: "read-failed" };
|
|
1603
|
-
}).finally(() => guard(this.sink, this.name, () => this.emit(base)));
|
|
992
|
+
}).finally(() => guard(this.sink, this.name, () => this.emit(base))));
|
|
1604
993
|
});
|
|
1605
994
|
}
|
|
1606
995
|
async readBounded(res) {
|
|
@@ -1611,8 +1000,8 @@ var HTTPRecorder = class {
|
|
|
1611
1000
|
let size = 0;
|
|
1612
1001
|
let truncated = false;
|
|
1613
1002
|
for (; ; ) {
|
|
1614
|
-
const { done
|
|
1615
|
-
if (
|
|
1003
|
+
const { done, value } = await reader.read();
|
|
1004
|
+
if (done) break;
|
|
1616
1005
|
size += value.byteLength;
|
|
1617
1006
|
if (text.length < max) text += dec.decode(value, { stream: true });
|
|
1618
1007
|
if (size > max * 4) {
|
|
@@ -1669,7 +1058,6 @@ var HTTPRecorder = class {
|
|
|
1669
1058
|
}
|
|
1670
1059
|
return origSend.call(this, body);
|
|
1671
1060
|
};
|
|
1672
|
-
open[XHR_PATCHED] = origOpen;
|
|
1673
1061
|
proto.open = open;
|
|
1674
1062
|
proto.setRequestHeader = setHeader;
|
|
1675
1063
|
proto.send = send;
|
|
@@ -1691,9 +1079,14 @@ var HTTPRecorder = class {
|
|
|
1691
1079
|
this.inflight++;
|
|
1692
1080
|
xhr.addEventListener("abort", () => m.aborted = true);
|
|
1693
1081
|
xhr.addEventListener("timeout", () => m.timedOut = true);
|
|
1082
|
+
let finishCapture;
|
|
1083
|
+
this.track(new Promise((resolve) => {
|
|
1084
|
+
finishCapture = resolve;
|
|
1085
|
+
}));
|
|
1694
1086
|
xhr.addEventListener("loadend", () => {
|
|
1695
1087
|
this.inflight = Math.max(0, this.inflight - 1);
|
|
1696
1088
|
guard(this.sink, this.name, () => this.endXhr(xhr, m, ctx));
|
|
1089
|
+
finishCapture();
|
|
1697
1090
|
});
|
|
1698
1091
|
}
|
|
1699
1092
|
endXhr(xhr, m, ctx) {
|
|
@@ -1737,6 +1130,7 @@ var HTTPRecorder = class {
|
|
|
1737
1130
|
};
|
|
1738
1131
|
}
|
|
1739
1132
|
emit(data) {
|
|
1133
|
+
if (!this.delivering) return;
|
|
1740
1134
|
this.sink.emit("http", data, { priority: "normal", at: data.startedAt });
|
|
1741
1135
|
}
|
|
1742
1136
|
describeBody(body, contentType) {
|
|
@@ -1986,11 +1380,12 @@ var LifecycleManager = class {
|
|
|
1986
1380
|
this.lastUrl = location.href;
|
|
1987
1381
|
const d = this.d;
|
|
1988
1382
|
d.listen(window, "pagehide", () => safe(() => this.h.onPageHide()), { capture: true });
|
|
1989
|
-
d.listen(window, "
|
|
1383
|
+
d.listen(window, "pageshow", (event) => {
|
|
1384
|
+
if (event.persisted) safe(() => this.h.onPageShow?.());
|
|
1385
|
+
});
|
|
1990
1386
|
d.listen(document, "visibilitychange", () => {
|
|
1991
1387
|
const hidden = document.visibilityState === "hidden";
|
|
1992
1388
|
safe(() => this.h.onVisibility(hidden));
|
|
1993
|
-
if (hidden) safe(() => this.h.onPageHide());
|
|
1994
1389
|
});
|
|
1995
1390
|
d.listen(window, "online", () => safe(() => this.h.onConnectivity(true)));
|
|
1996
1391
|
d.listen(window, "offline", () => safe(() => this.h.onConnectivity(false)));
|
|
@@ -2027,282 +1422,120 @@ var LifecycleManager = class {
|
|
|
2027
1422
|
}
|
|
2028
1423
|
};
|
|
2029
1424
|
|
|
2030
|
-
// src/core/
|
|
2031
|
-
var
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
this.
|
|
2035
|
-
this.
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
return
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
try {
|
|
2060
|
-
const s = JSON.parse(raw);
|
|
2061
|
-
if (s.version !== STATE_VERSION || s.platformId !== this.cfg.platformId || !s.sessionId || !s.sessionToken) return null;
|
|
2062
|
-
if (s.expiresAt && s.expiresAt < Date.now()) return null;
|
|
2063
|
-
const seq = Number(safe(() => sessionStorage.getItem(this.seqKey)) ?? 0);
|
|
2064
|
-
s.sequence = Math.max(s.sequence ?? 0, Number.isFinite(seq) ? seq : 0);
|
|
2065
|
-
return s;
|
|
2066
|
-
} catch {
|
|
2067
|
-
return null;
|
|
1425
|
+
// src/core/RecordingStore.ts
|
|
1426
|
+
var RecordingStore = class _RecordingStore {
|
|
1427
|
+
constructor(database) {
|
|
1428
|
+
this.database = database;
|
|
1429
|
+
this.pending = /* @__PURE__ */ new Set();
|
|
1430
|
+
this.uncommitted = /* @__PURE__ */ new Map();
|
|
1431
|
+
}
|
|
1432
|
+
static async open(key) {
|
|
1433
|
+
const database = await new Promise((resolve, reject) => {
|
|
1434
|
+
const request = indexedDB.open(`offscript-recording-${key}`, 1);
|
|
1435
|
+
request.onupgradeneeded = () => {
|
|
1436
|
+
request.result.createObjectStore("events", { keyPath: "sequence" });
|
|
1437
|
+
request.result.createObjectStore("state");
|
|
1438
|
+
};
|
|
1439
|
+
request.onsuccess = () => resolve(request.result);
|
|
1440
|
+
request.onerror = () => reject(request.error);
|
|
1441
|
+
});
|
|
1442
|
+
return new _RecordingStore(database);
|
|
1443
|
+
}
|
|
1444
|
+
async load() {
|
|
1445
|
+
await this.flush();
|
|
1446
|
+
const checkpoint = sessionStorage.getItem(this.checkpointKey);
|
|
1447
|
+
if (checkpoint) {
|
|
1448
|
+
const saved = JSON.parse(checkpoint);
|
|
1449
|
+
await this.write(["events", "state"], (transaction) => {
|
|
1450
|
+
for (const event of saved.events) transaction.objectStore("events").put(event);
|
|
1451
|
+
transaction.objectStore("state").put(saved.status, "status");
|
|
1452
|
+
});
|
|
1453
|
+
sessionStorage.removeItem(this.checkpointKey);
|
|
2068
1454
|
}
|
|
1455
|
+
return new Promise((resolve, reject) => {
|
|
1456
|
+
const transaction = this.database.transaction(["events", "state"], "readonly");
|
|
1457
|
+
const events = transaction.objectStore("events").getAll();
|
|
1458
|
+
const state = transaction.objectStore("state").get("status");
|
|
1459
|
+
transaction.oncomplete = () => {
|
|
1460
|
+
this.status = state.result;
|
|
1461
|
+
resolve(state.result ? { status: state.result, events: events.result } : null);
|
|
1462
|
+
};
|
|
1463
|
+
transaction.onabort = () => reject(transaction.error);
|
|
1464
|
+
});
|
|
2069
1465
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
this.
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
testCaseId: this.info.testCaseId,
|
|
2078
|
-
platformId: this.cfg.platformId,
|
|
2079
|
-
sessionToken: this.token,
|
|
2080
|
-
status,
|
|
2081
|
-
sequence: this.seq,
|
|
2082
|
-
pageCount: this.state?.pageCount ?? 0,
|
|
2083
|
-
heartbeatIntervalMs: this.state?.heartbeatIntervalMs,
|
|
2084
|
-
expiresAt: this.state?.expiresAt,
|
|
2085
|
-
updatedAt: Date.now(),
|
|
2086
|
-
...extra
|
|
2087
|
-
};
|
|
2088
|
-
safe(() => sessionStorage.setItem(this.storageKey, JSON.stringify(this.state)));
|
|
1466
|
+
append(event) {
|
|
1467
|
+
this.uncommitted.set(event.sequence, event);
|
|
1468
|
+
return this.write(["events"], (transaction) => {
|
|
1469
|
+
transaction.objectStore("events").put(event);
|
|
1470
|
+
}).then(() => {
|
|
1471
|
+
this.uncommitted.delete(event.sequence);
|
|
1472
|
+
});
|
|
2089
1473
|
}
|
|
2090
1474
|
setStatus(status) {
|
|
2091
|
-
this.
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
safe(() => sessionStorage.removeItem(this.storageKey));
|
|
2095
|
-
safe(() => sessionStorage.removeItem(this.seqKey));
|
|
2096
|
-
this.state = null;
|
|
2097
|
-
}
|
|
2098
|
-
// ---- sequência ----------------------------------------------------------
|
|
2099
|
-
nextSequence() {
|
|
2100
|
-
this.seq++;
|
|
2101
|
-
safe(() => sessionStorage.setItem(this.seqKey, String(this.seq)));
|
|
2102
|
-
return this.seq;
|
|
2103
|
-
}
|
|
2104
|
-
get lastSequence() {
|
|
2105
|
-
return this.seq;
|
|
2106
|
-
}
|
|
2107
|
-
/** Sobe a sequência até pelo menos `n` (ex.: maior sequência encontrada no buffer). */
|
|
2108
|
-
ensureSequenceAtLeast(n) {
|
|
2109
|
-
if (n > this.seq) this.seq = n;
|
|
2110
|
-
}
|
|
2111
|
-
newPage() {
|
|
2112
|
-
if (!this.info) throw new InvalidStateError("Sem sess\xE3o ativa");
|
|
2113
|
-
this.info = { ...this.info, pageId: uid("pg_") };
|
|
2114
|
-
this.persist(this.state?.status ?? "recording", { pageCount: (this.state?.pageCount ?? 0) + 1 });
|
|
2115
|
-
return this.info.pageId;
|
|
2116
|
-
}
|
|
2117
|
-
get pageCount() {
|
|
2118
|
-
return this.state?.pageCount ?? 0;
|
|
2119
|
-
}
|
|
2120
|
-
// ---- ciclo de vida ------------------------------------------------------
|
|
2121
|
-
/** Cria (ou obtém, de forma idempotente) a sessão no backend. */
|
|
2122
|
-
async create(opts, context) {
|
|
2123
|
-
const queryToken = consumeQueryToken();
|
|
2124
|
-
const sessionToken = opts?.sessionToken || this.token || queryToken;
|
|
2125
|
-
if (!sessionToken || !opts.executionId || !opts.testCaseId)
|
|
2126
|
-
throw new AuthError("sessionToken, executionId e testCaseId s\xE3o obrigat\xF3rios");
|
|
2127
|
-
this.token = sessionToken;
|
|
2128
|
-
const tabId = await resolveTabId(this.cfg.storagePrefix, this.loadPersisted()?.tabId);
|
|
2129
|
-
const res = await this.api.createSession({
|
|
2130
|
-
platformId: this.cfg.platformId,
|
|
2131
|
-
testCaseId: opts.testCaseId,
|
|
2132
|
-
executionId: opts.executionId,
|
|
2133
|
-
tabId,
|
|
2134
|
-
sdkVersion: chunkKQVK5EJR_cjs.SDK_VERSION,
|
|
2135
|
-
appVersion: this.cfg.appVersion,
|
|
2136
|
-
context
|
|
1475
|
+
this.status = status;
|
|
1476
|
+
return this.write(["state"], (transaction) => {
|
|
1477
|
+
transaction.objectStore("state").put(status, "status");
|
|
2137
1478
|
});
|
|
2138
|
-
if (!res.recording?.enabled) {
|
|
2139
|
-
this.disabledReason = res.recording?.reason;
|
|
2140
|
-
throw new SessionDisabledError(res.recording?.reason);
|
|
2141
|
-
}
|
|
2142
|
-
if (res.sessionToken) this.token = res.sessionToken;
|
|
2143
|
-
this.seq = 0;
|
|
2144
|
-
this.info = {
|
|
2145
|
-
sessionId: res.sessionId,
|
|
2146
|
-
executionId: opts.executionId,
|
|
2147
|
-
testCaseId: opts.testCaseId,
|
|
2148
|
-
platformId: this.cfg.platformId,
|
|
2149
|
-
tabId,
|
|
2150
|
-
pageId: uid("pg_"),
|
|
2151
|
-
startedAt: Date.now()
|
|
2152
|
-
};
|
|
2153
|
-
safe(() => sessionStorage.removeItem(this.seqKey));
|
|
2154
|
-
this.persist("recording", {
|
|
2155
|
-
heartbeatIntervalMs: res.heartbeatIntervalMs,
|
|
2156
|
-
expiresAt: res.expiresAt,
|
|
2157
|
-
pageCount: 1,
|
|
2158
|
-
sequence: 0
|
|
2159
|
-
});
|
|
2160
|
-
return res;
|
|
2161
|
-
}
|
|
2162
|
-
/** Reidrata sessão persistida (reload/navegação MPA) após validar no backend. */
|
|
2163
|
-
async resume(state) {
|
|
2164
|
-
this.token = state.sessionToken;
|
|
2165
|
-
const tabId = await resolveTabId(this.cfg.storagePrefix, state.tabId);
|
|
2166
|
-
const res = await this.api.status(state.sessionId);
|
|
2167
|
-
if (res.status === "expired") throw new SessionExpiredError();
|
|
2168
|
-
if (res.status === "disabled") throw new SessionDisabledError();
|
|
2169
|
-
if (res.sessionToken) this.token = res.sessionToken;
|
|
2170
|
-
this.state = { ...state, tabId };
|
|
2171
|
-
this.seq = tabId === state.tabId ? state.sequence : 0;
|
|
2172
|
-
this.info = {
|
|
2173
|
-
sessionId: state.sessionId,
|
|
2174
|
-
executionId: state.executionId,
|
|
2175
|
-
testCaseId: state.testCaseId,
|
|
2176
|
-
platformId: state.platformId,
|
|
2177
|
-
tabId,
|
|
2178
|
-
pageId: uid("pg_"),
|
|
2179
|
-
startedAt: Date.now()
|
|
2180
|
-
};
|
|
2181
|
-
this.persist(state.status, { sessionToken: this.token, expiresAt: res.expiresAt ?? state.expiresAt, pageCount: state.pageCount + 1, tabId });
|
|
2182
|
-
return res;
|
|
2183
|
-
}
|
|
2184
|
-
startHeartbeat(onResult, onError, pending, status) {
|
|
2185
|
-
this.stopHeartbeat();
|
|
2186
|
-
const every = this.state?.heartbeatIntervalMs ?? this.cfg.heartbeatIntervalMs;
|
|
2187
|
-
if (!every || every <= 0) return;
|
|
2188
|
-
this.heartbeatTimer = setInterval(async () => {
|
|
2189
|
-
if (!this.info) return;
|
|
2190
|
-
try {
|
|
2191
|
-
const r = await this.api.heartbeat(this.info.sessionId, {
|
|
2192
|
-
tabId: this.info.tabId,
|
|
2193
|
-
pageId: this.info.pageId,
|
|
2194
|
-
status: status(),
|
|
2195
|
-
pending: pending(),
|
|
2196
|
-
lastSequence: this.seq
|
|
2197
|
-
});
|
|
2198
|
-
if (r.sessionToken) {
|
|
2199
|
-
this.token = r.sessionToken;
|
|
2200
|
-
this.persist(this.state?.status ?? "recording", { sessionToken: r.sessionToken, expiresAt: r.expiresAt });
|
|
2201
|
-
}
|
|
2202
|
-
onResult(r);
|
|
2203
|
-
} catch (e) {
|
|
2204
|
-
if (e instanceof AuthError || e instanceof SessionExpiredError) onError(e);
|
|
2205
|
-
}
|
|
2206
|
-
}, every);
|
|
2207
|
-
}
|
|
2208
|
-
stopHeartbeat() {
|
|
2209
|
-
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
2210
|
-
this.heartbeatTimer = void 0;
|
|
2211
1479
|
}
|
|
2212
|
-
async
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
1480
|
+
async clear() {
|
|
1481
|
+
await this.write(["events", "state"], (transaction) => {
|
|
1482
|
+
transaction.objectStore("events").clear();
|
|
1483
|
+
transaction.objectStore("state").clear();
|
|
1484
|
+
});
|
|
1485
|
+
this.failure = void 0;
|
|
1486
|
+
this.status = void 0;
|
|
1487
|
+
this.uncommitted.clear();
|
|
1488
|
+
sessionStorage.removeItem(this.checkpointKey);
|
|
2216
1489
|
}
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
this.
|
|
2220
|
-
this.
|
|
2221
|
-
this.token = "";
|
|
2222
|
-
this.seq = 0;
|
|
1490
|
+
/** Synchronous handoff of the last uncommitted events before document unload. */
|
|
1491
|
+
checkpoint() {
|
|
1492
|
+
if (!this.status) return;
|
|
1493
|
+
sessionStorage.setItem(this.checkpointKey, JSON.stringify({ status: this.status, events: [...this.uncommitted.values()] }));
|
|
2223
1494
|
}
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
if (typeof window === "undefined") return "";
|
|
2227
|
-
try {
|
|
2228
|
-
const token = readQueryToken();
|
|
2229
|
-
if (!token) return "";
|
|
2230
|
-
const url = new URL(window.location.href);
|
|
2231
|
-
url.searchParams.delete("offscript_token");
|
|
2232
|
-
window.history.replaceState(window.history.state, typeof document === "undefined" ? "" : document.title, `${url.pathname}${url.search}${url.hash}`);
|
|
2233
|
-
return token;
|
|
2234
|
-
} catch {
|
|
2235
|
-
return "";
|
|
1495
|
+
get checkpointKey() {
|
|
1496
|
+
return `${this.database.name}-handoff`;
|
|
2236
1497
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
try {
|
|
2241
|
-
return new URL(window.location.href).searchParams.get("offscript_token")?.trim() ?? "";
|
|
2242
|
-
} catch {
|
|
2243
|
-
return "";
|
|
1498
|
+
async flush() {
|
|
1499
|
+
await Promise.allSettled([...this.pending]);
|
|
1500
|
+
if (this.failure) throw this.failure;
|
|
2244
1501
|
}
|
|
2245
|
-
|
|
2246
|
-
var heldLocks = [];
|
|
2247
|
-
async function resolveTabId(prefix, previous) {
|
|
2248
|
-
const candidate = previous ?? uid("tab_");
|
|
2249
|
-
const locks = globalThis.navigator?.locks;
|
|
2250
|
-
if (!locks || !previous) {
|
|
2251
|
-
if (locks) void claim(locks, `${prefix}:tab:${candidate}`);
|
|
2252
|
-
return candidate;
|
|
2253
|
-
}
|
|
2254
|
-
if (heldLocks.includes(candidate)) return candidate;
|
|
2255
|
-
const ok = await claim(locks, `${prefix}:tab:${candidate}`);
|
|
2256
|
-
if (ok) return candidate;
|
|
2257
|
-
const fresh = uid("tab_");
|
|
2258
|
-
void claim(locks, `${prefix}:tab:${fresh}`);
|
|
2259
|
-
return fresh;
|
|
2260
|
-
}
|
|
2261
|
-
function claim(locks, name) {
|
|
2262
|
-
return new Promise((resolve) => {
|
|
1502
|
+
async close() {
|
|
2263
1503
|
try {
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
return void 0;
|
|
2268
|
-
}
|
|
2269
|
-
heldLocks.push(name.split(":tab:")[1]);
|
|
2270
|
-
resolve(true);
|
|
2271
|
-
return new Promise(() => void 0);
|
|
2272
|
-
}).catch(() => resolve(true));
|
|
2273
|
-
} catch {
|
|
2274
|
-
resolve(true);
|
|
1504
|
+
await this.flush();
|
|
1505
|
+
} finally {
|
|
1506
|
+
this.database.close();
|
|
2275
1507
|
}
|
|
2276
|
-
}
|
|
2277
|
-
|
|
1508
|
+
}
|
|
1509
|
+
write(stores, apply) {
|
|
1510
|
+
const operation = new Promise((resolve, reject) => {
|
|
1511
|
+
const transaction = this.database.transaction(stores, "readwrite");
|
|
1512
|
+
transaction.oncomplete = () => resolve();
|
|
1513
|
+
transaction.onabort = () => reject(transaction.error ?? new Error("Falha ao salvar grava\xE7\xE3o local"));
|
|
1514
|
+
try {
|
|
1515
|
+
apply(transaction);
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
transaction.abort();
|
|
1518
|
+
reject(error);
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
this.pending.add(operation);
|
|
1522
|
+
void operation.catch((error) => {
|
|
1523
|
+
this.failure = error instanceof Error ? error : new Error(String(error));
|
|
1524
|
+
}).finally(() => this.pending.delete(operation));
|
|
1525
|
+
return operation;
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
2278
1528
|
|
|
2279
1529
|
// src/core/config.ts
|
|
2280
1530
|
function resolveConfig(input) {
|
|
2281
|
-
if (!input || typeof input !== "object") throw new ConfigError("Configura\xE7\xE3o ausente");
|
|
2282
|
-
if (!input.platformId) throw new ConfigError("platformId \xE9 obrigat\xF3rio");
|
|
2283
|
-
if (!input.apiUrl) throw new ConfigError("apiUrl \xE9 obrigat\xF3rio");
|
|
2284
|
-
let url;
|
|
2285
|
-
try {
|
|
2286
|
-
url = new URL(input.apiUrl);
|
|
2287
|
-
} catch {
|
|
2288
|
-
throw new ConfigError(`apiUrl inv\xE1lida: ${input.apiUrl}`);
|
|
2289
|
-
}
|
|
2290
|
-
const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
2291
|
-
if (url.protocol !== "https:" && !(url.protocol === "http:" && local))
|
|
2292
|
-
throw new ConfigError("apiUrl deve usar HTTPS (HTTP s\xF3 \xE9 aceito em localhost)");
|
|
2293
1531
|
return {
|
|
2294
|
-
apiUrl: url.origin + url.pathname.replace(/\/+$/, ""),
|
|
2295
|
-
platformId: input.platformId,
|
|
2296
|
-
appVersion: input.appVersion,
|
|
2297
1532
|
recording: deepMerge(DEFAULT_RECORDING, input.recording),
|
|
2298
1533
|
privacy: resolvePrivacy(input.privacy),
|
|
2299
|
-
transport: deepMerge(DEFAULT_TRANSPORT, input.transport),
|
|
2300
1534
|
correlationWindowMs: input.correlationWindowMs ?? 3e3,
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
controls: input.controls ?? true
|
|
1535
|
+
controls: input.controls ?? true,
|
|
1536
|
+
endpoint: input.endpoint,
|
|
1537
|
+
persistenceKey: input.persistenceKey,
|
|
1538
|
+
onComplete: input.onComplete
|
|
2306
1539
|
};
|
|
2307
1540
|
}
|
|
2308
1541
|
|
|
@@ -2371,6 +1604,22 @@ var RecordingControls = class {
|
|
|
2371
1604
|
this.render();
|
|
2372
1605
|
this.startEyes();
|
|
2373
1606
|
}
|
|
1607
|
+
/** Independent notice so the host may destroy the controls after successful upload. */
|
|
1608
|
+
showSuccess() {
|
|
1609
|
+
const notice = document.createElement("div");
|
|
1610
|
+
notice.setAttribute("data-offscript-success", "");
|
|
1611
|
+
notice.setAttribute("data-offscript-ignore", "");
|
|
1612
|
+
notice.className = "offscript-ignore";
|
|
1613
|
+
notice.style.cssText = "position:fixed;right:16px;bottom:16px;max-width:calc(100vw - 32px);z-index:2147483647;";
|
|
1614
|
+
const shadow = notice.attachShadow({ mode: "closed" });
|
|
1615
|
+
shadow.innerHTML = SUCCESS_TEMPLATE;
|
|
1616
|
+
document.body.appendChild(notice);
|
|
1617
|
+
const timer = window.setTimeout(() => notice.remove(), 8e3);
|
|
1618
|
+
shadow.querySelector("button").addEventListener("click", () => {
|
|
1619
|
+
window.clearTimeout(timer);
|
|
1620
|
+
notice.remove();
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
2374
1623
|
unmount() {
|
|
2375
1624
|
this.unsubscribe?.();
|
|
2376
1625
|
this.unsubscribe = void 0;
|
|
@@ -2422,15 +1671,15 @@ var RecordingControls = class {
|
|
|
2422
1671
|
} else if (action === "close") {
|
|
2423
1672
|
this.expanded = false;
|
|
2424
1673
|
this.render();
|
|
2425
|
-
} else if (action === "record") void this.run(() => this.sdk.
|
|
1674
|
+
} else if (action === "record") void this.run(() => this.sdk.startRecording());
|
|
2426
1675
|
else if (action === "pause") void this.run(() => this.sdk.pauseRecording());
|
|
2427
1676
|
else if (action === "resume") void this.run(() => this.sdk.resumeRecording());
|
|
2428
|
-
else if (action === "stop") void this.run(() => this.sdk.
|
|
1677
|
+
else if (action === "stop") void this.run(() => this.sdk.finishRecording());
|
|
2429
1678
|
else if (action === "new") this.openConfirm();
|
|
2430
1679
|
else if (action === "cancel") this.closeConfirm();
|
|
2431
1680
|
else if (action === "confirm") {
|
|
2432
1681
|
this.closeConfirm();
|
|
2433
|
-
void this.run(() => this.sdk.
|
|
1682
|
+
void this.run(() => this.sdk.discardRecording());
|
|
2434
1683
|
} else if (action === "privacy-show") {
|
|
2435
1684
|
this.privacyOpen = true;
|
|
2436
1685
|
this.render();
|
|
@@ -2498,23 +1747,24 @@ var RecordingControls = class {
|
|
|
2498
1747
|
}
|
|
2499
1748
|
render() {
|
|
2500
1749
|
this.place();
|
|
2501
|
-
const status = this.sdk.getState()
|
|
1750
|
+
const { status, pending } = this.sdk.getState();
|
|
2502
1751
|
const shell = this.shadow?.querySelector("[data-shell]");
|
|
2503
1752
|
shell?.setAttribute("data-open", this.expanded ? "true" : "false");
|
|
2504
1753
|
shell?.setAttribute("data-state", status);
|
|
2505
1754
|
if (this.note) this.note.textContent = this.failure;
|
|
2506
1755
|
const live = status === "recording" || status === "paused";
|
|
1756
|
+
const canFinish = live || status === "stopped" && pending > 0;
|
|
2507
1757
|
if (!live) this.closeConfirm();
|
|
2508
1758
|
this.paintPrivacy();
|
|
2509
1759
|
this.setHidden("record", live || status === "initializing" || status === "stopping");
|
|
2510
1760
|
this.setHidden("pause", status !== "recording");
|
|
2511
1761
|
this.setHidden("resume", status !== "paused");
|
|
2512
1762
|
this.setHidden("new", !live);
|
|
2513
|
-
this.setHidden("stop", !
|
|
1763
|
+
this.setHidden("stop", !canFinish);
|
|
2514
1764
|
this.setEnabled("record", !this.busy && !live && status !== "initializing" && status !== "stopping");
|
|
2515
1765
|
this.setEnabled("pause", !this.busy && status === "recording");
|
|
2516
1766
|
this.setEnabled("resume", !this.busy && status === "paused");
|
|
2517
|
-
this.setEnabled("stop", !this.busy &&
|
|
1767
|
+
this.setEnabled("stop", !this.busy && canFinish);
|
|
2518
1768
|
this.setEnabled("new", !this.busy && live);
|
|
2519
1769
|
}
|
|
2520
1770
|
paintPrivacy() {
|
|
@@ -2904,7 +2154,7 @@ var TEMPLATE = `
|
|
|
2904
2154
|
<button type="button" class="text record" data-action="record">Iniciar</button>
|
|
2905
2155
|
<button type="button" data-action="pause" aria-label="Pausar" title="Pausar">${ICON.pause}</button>
|
|
2906
2156
|
<button type="button" data-action="resume" aria-label="Retomar" title="Retomar">${ICON.play}</button>
|
|
2907
|
-
<button type="button" class="text" data-action="new" title="Descarta o que foi gravado e volta ao in\xEDcio">Nova
|
|
2157
|
+
<button type="button" class="text" data-action="new" title="Descarta o que foi gravado e volta ao in\xEDcio">Nova grava\xE7\xE3o</button>
|
|
2908
2158
|
<button type="button" class="text finish" data-action="stop" title="Encerra a coleta e envia o que foi gravado">Finalizar</button>
|
|
2909
2159
|
<button type="button" data-action="close" aria-label="Fechar" title="Fechar">${ICON.close}</button>
|
|
2910
2160
|
</div>
|
|
@@ -2912,7 +2162,7 @@ var TEMPLATE = `
|
|
|
2912
2162
|
<div class="speak" data-confirm hidden>
|
|
2913
2163
|
<div class="bubble" role="dialog" aria-labelledby="offscript-confirm-title">
|
|
2914
2164
|
${mark("offscript-trail-speak")}
|
|
2915
|
-
<p id="offscript-confirm-title">Descartar esta
|
|
2165
|
+
<p id="offscript-confirm-title">Descartar esta grava\xE7\xE3o? O que foi gravado ser\xE1 ignorado.</p>
|
|
2916
2166
|
<div class="choices">
|
|
2917
2167
|
<button type="button" class="choice secondary" data-action="cancel">Cancelar</button>
|
|
2918
2168
|
<button type="button" class="choice danger" data-action="confirm">Descartar</button>
|
|
@@ -2922,325 +2172,387 @@ var TEMPLATE = `
|
|
|
2922
2172
|
<p class="note" data-note></p>
|
|
2923
2173
|
</div>
|
|
2924
2174
|
`;
|
|
2175
|
+
var SUCCESS_TEMPLATE = `
|
|
2176
|
+
<style>
|
|
2177
|
+
:host { color-scheme: dark; }
|
|
2178
|
+
.success {
|
|
2179
|
+
display: flex;
|
|
2180
|
+
align-items: center;
|
|
2181
|
+
gap: 12px;
|
|
2182
|
+
padding: 12px 14px;
|
|
2183
|
+
border: 1px solid #377c65;
|
|
2184
|
+
border-radius: 16px;
|
|
2185
|
+
background: #152c25;
|
|
2186
|
+
color: #dcfce7;
|
|
2187
|
+
box-shadow: 0 12px 32px rgba(3, 5, 12, 0.35);
|
|
2188
|
+
font: 500 13px/1.5 "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif;
|
|
2189
|
+
}
|
|
2190
|
+
p { margin: 0; }
|
|
2191
|
+
button {
|
|
2192
|
+
flex: none;
|
|
2193
|
+
display: grid;
|
|
2194
|
+
place-items: center;
|
|
2195
|
+
width: 28px;
|
|
2196
|
+
height: 28px;
|
|
2197
|
+
padding: 0;
|
|
2198
|
+
border: 0;
|
|
2199
|
+
border-radius: 999px;
|
|
2200
|
+
background: transparent;
|
|
2201
|
+
color: inherit;
|
|
2202
|
+
font: 20px/1 ui-sans-serif, system-ui, sans-serif;
|
|
2203
|
+
cursor: pointer;
|
|
2204
|
+
}
|
|
2205
|
+
button:hover { background: #244a3c; }
|
|
2206
|
+
button:focus-visible { outline: 2px solid #86efac; outline-offset: 2px; }
|
|
2207
|
+
</style>
|
|
2208
|
+
<div class="success">
|
|
2209
|
+
<p role="status">Grava\xE7\xE3o enviada com sucesso!</p>
|
|
2210
|
+
<button type="button" aria-label="Fechar confirma\xE7\xE3o">\xD7</button>
|
|
2211
|
+
</div>
|
|
2212
|
+
`;
|
|
2925
2213
|
|
|
2926
2214
|
// src/core/OffscriptSDK.ts
|
|
2927
|
-
var
|
|
2215
|
+
var TOKEN_STORAGE_KEY = "offscript-session-token";
|
|
2216
|
+
var DRAFT_STORAGE_KEY = "offscript-draft-key";
|
|
2217
|
+
var COMPLETED_TOKEN_STORAGE_KEY = "offscript-completed-token";
|
|
2218
|
+
function tokenFingerprint(token) {
|
|
2219
|
+
let first = 2166136261;
|
|
2220
|
+
let second = 2654435769;
|
|
2221
|
+
for (let index = 0; index < token.length; index++) {
|
|
2222
|
+
const character = token.charCodeAt(index);
|
|
2223
|
+
first = Math.imul(first ^ character, 16777619);
|
|
2224
|
+
second = Math.imul(second ^ character, 2246822519);
|
|
2225
|
+
}
|
|
2226
|
+
return `${first >>> 0}.${second >>> 0}`;
|
|
2227
|
+
}
|
|
2928
2228
|
var Offscript = class {
|
|
2929
|
-
constructor(config) {
|
|
2229
|
+
constructor(config = {}) {
|
|
2930
2230
|
this.bus = new Emitter();
|
|
2931
2231
|
this.status = "idle";
|
|
2932
2232
|
this.recorders = [];
|
|
2933
2233
|
this.accepting = false;
|
|
2934
2234
|
this.destroyed = false;
|
|
2235
|
+
this.suspended = false;
|
|
2236
|
+
this.events = [];
|
|
2935
2237
|
this.recent = [];
|
|
2936
2238
|
this.actionLog = [];
|
|
2937
2239
|
this.contextLog = [];
|
|
2938
|
-
this.
|
|
2939
|
-
this.
|
|
2240
|
+
this.sequence = 0;
|
|
2241
|
+
this.pageId = uid("page_");
|
|
2242
|
+
this.submitted = false;
|
|
2243
|
+
this.launchTokenChanged = false;
|
|
2940
2244
|
this.config = resolveConfig(config);
|
|
2941
2245
|
this.sanitizer = new DataSanitizer(this.config.privacy);
|
|
2942
|
-
this.sessions = new SessionManager(this.config);
|
|
2943
2246
|
}
|
|
2944
|
-
// ------------------------------------------------------------------ API pública
|
|
2945
2247
|
getState() {
|
|
2946
|
-
return { status: this.status,
|
|
2248
|
+
return { status: this.status, pending: this.submitted ? 0 : this.events.length, lastError: this.lastError };
|
|
2947
2249
|
}
|
|
2948
2250
|
capturesSensitiveValues() {
|
|
2949
|
-
return this.config.privacy.captureSensitiveValues
|
|
2950
|
-
}
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
if (this.
|
|
2957
|
-
this.
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
async doInit() {
|
|
2961
|
-
if (typeof window === "undefined") throw new OffscriptError("NOT_SUPPORTED", "O SDK requer um ambiente de navegador");
|
|
2962
|
-
this.ensureControls();
|
|
2963
|
-
this.setStatus("initializing");
|
|
2964
|
-
try {
|
|
2965
|
-
const buffer = await openBuffer(`${this.config.storagePrefix}-${this.config.platformId}`, this.config.transport.persist);
|
|
2966
|
-
this.transport = new EventTransport(this.config.transport, this.sessions.api, buffer);
|
|
2967
|
-
this.transport.on("flushed", (e) => this.bus.emit("flush", e));
|
|
2968
|
-
this.transport.on("overflow", (e) => this.bus.emit("overflow", e));
|
|
2969
|
-
this.transport.on("fatal", ({ error }) => void this.onFatal(error));
|
|
2970
|
-
this.transport.on("failed", ({ error }) => this.debug("transport failed", error));
|
|
2971
|
-
await this.transport.purgeExpired(this.config.privacy.localRetentionMs);
|
|
2972
|
-
this.setStatus("idle");
|
|
2973
|
-
const persisted = this.config.autoResume ? this.sessions.loadPersisted() : null;
|
|
2974
|
-
if (persisted) await this.resumePersisted(persisted);
|
|
2975
|
-
} catch (e) {
|
|
2976
|
-
this.fail(e);
|
|
2977
|
-
throw e;
|
|
2251
|
+
return this.config.privacy.captureSensitiveValues;
|
|
2252
|
+
}
|
|
2253
|
+
async init() {
|
|
2254
|
+
this.assertAlive();
|
|
2255
|
+
if (typeof window === "undefined") throw new OffscriptError("NOT_SUPPORTED", "O SDK requer um navegador");
|
|
2256
|
+
this.initPromise ?? (this.initPromise = this.initialize());
|
|
2257
|
+
await this.initPromise;
|
|
2258
|
+
if (this.restorePromise) await this.restorePromise;
|
|
2259
|
+
if (this.config.controls && this.launchToken && !this.controls) {
|
|
2260
|
+
this.controls = new RecordingControls(this);
|
|
2261
|
+
this.controls.mount();
|
|
2978
2262
|
}
|
|
2979
2263
|
}
|
|
2980
|
-
async
|
|
2981
|
-
|
|
2982
|
-
if (this.
|
|
2983
|
-
|
|
2984
|
-
this.startPromise = p;
|
|
2985
|
-
return p;
|
|
2986
|
-
}
|
|
2987
|
-
async doStart(opts) {
|
|
2988
|
-
await this.init();
|
|
2989
|
-
const cur = this.sessions.session;
|
|
2990
|
-
if ((this.status === "recording" || this.status === "paused") && cur) {
|
|
2991
|
-
if (cur.executionId === opts.executionId) return cur;
|
|
2992
|
-
throw new InvalidStateError(`J\xE1 existe grava\xE7\xE3o ativa para a execu\xE7\xE3o ${cur.executionId}. Encerre-a antes.`);
|
|
2264
|
+
async initialize() {
|
|
2265
|
+
this.resolveLaunchToken();
|
|
2266
|
+
if (this.launchToken && !this.config.persistenceKey) {
|
|
2267
|
+
this.config.persistenceKey = this.getOrCreateDraftKey();
|
|
2993
2268
|
}
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2269
|
+
await this.initializeDraft();
|
|
2270
|
+
}
|
|
2271
|
+
resolveLaunchToken() {
|
|
2272
|
+
const incomingToken = new URLSearchParams(window.location.search).get("offscript_token")?.trim();
|
|
2273
|
+
const previousToken = this.readSessionValue(TOKEN_STORAGE_KEY);
|
|
2274
|
+
const completedToken = this.readSessionValue(COMPLETED_TOKEN_STORAGE_KEY);
|
|
2275
|
+
const legacyCompletion = completedToken === "1" && Boolean(previousToken);
|
|
2276
|
+
const completedFingerprint = legacyCompletion && previousToken ? tokenFingerprint(previousToken) : completedToken;
|
|
2277
|
+
const candidateToken = incomingToken || previousToken || void 0;
|
|
2278
|
+
this.launchTokenChanged = Boolean(incomingToken && previousToken && incomingToken !== previousToken) || Boolean(incomingToken && completedFingerprint && completedFingerprint !== "1" && tokenFingerprint(incomingToken) !== completedFingerprint);
|
|
2279
|
+
if (this.launchTokenChanged) {
|
|
2280
|
+
this.removeSessionValue(COMPLETED_TOKEN_STORAGE_KEY);
|
|
2281
|
+
}
|
|
2282
|
+
if (legacyCompletion && previousToken && !this.launchTokenChanged) {
|
|
2283
|
+
this.writeSessionValue(COMPLETED_TOKEN_STORAGE_KEY, tokenFingerprint(previousToken));
|
|
2284
|
+
this.removeSessionValue(TOKEN_STORAGE_KEY);
|
|
2285
|
+
}
|
|
2286
|
+
const isCompleted = Boolean(candidateToken && completedFingerprint === tokenFingerprint(candidateToken));
|
|
2287
|
+
this.launchToken = isCompleted ? void 0 : candidateToken;
|
|
2288
|
+
if (incomingToken && !isCompleted) this.writeSessionValue(TOKEN_STORAGE_KEY, incomingToken);
|
|
2289
|
+
}
|
|
2290
|
+
getOrCreateDraftKey() {
|
|
2291
|
+
const saved = this.readSessionValue(DRAFT_STORAGE_KEY);
|
|
2292
|
+
if (saved) return saved;
|
|
2293
|
+
const key = uid("draft_");
|
|
2294
|
+
this.writeSessionValue(DRAFT_STORAGE_KEY, key);
|
|
2295
|
+
return key;
|
|
2296
|
+
}
|
|
2297
|
+
readSessionValue(key) {
|
|
2997
2298
|
try {
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
await this.transport.start({ sessionId: s.sessionId, executionId: s.executionId, tabId: s.tabId });
|
|
3002
|
-
this.attach();
|
|
3003
|
-
this.accepting = true;
|
|
3004
|
-
this.setStatus("recording");
|
|
3005
|
-
this.emitLifecycle("session-start", { pageCount: 1 }, "critical");
|
|
3006
|
-
await this.startRecorders();
|
|
3007
|
-
this.sessions.startHeartbeat((r) => this.onHeartbeat(r.status), (e) => void this.onFatal(e), () => this.transport?.pending ?? 0, () => this.status);
|
|
3008
|
-
this.bus.emit("sessionStart", s);
|
|
3009
|
-
return s;
|
|
3010
|
-
} catch (e) {
|
|
3011
|
-
await this.teardown();
|
|
3012
|
-
this.sessions.clearPersisted();
|
|
3013
|
-
this.sessions.reset();
|
|
3014
|
-
this.fail(e);
|
|
3015
|
-
throw e;
|
|
2299
|
+
return window.sessionStorage.getItem(key);
|
|
2300
|
+
} catch {
|
|
2301
|
+
return null;
|
|
3016
2302
|
}
|
|
3017
2303
|
}
|
|
3018
|
-
|
|
3019
|
-
this.setStatus("initializing");
|
|
2304
|
+
writeSessionValue(key, value) {
|
|
3020
2305
|
try {
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
await this.transport.start({ sessionId: s.sessionId, executionId: s.executionId, tabId: s.tabId });
|
|
3024
|
-
this.attach();
|
|
3025
|
-
this.accepting = state.status === "recording";
|
|
3026
|
-
this.setStatus(state.status);
|
|
3027
|
-
this.emitLifecycle("session-resume", { pageCount: this.sessions.pageCount }, "critical");
|
|
3028
|
-
this.emitLifecycle("page-start", { navigationType: safe(() => performance.getEntriesByType("navigation")[0]?.type) }, "critical");
|
|
3029
|
-
await this.startRecorders();
|
|
3030
|
-
if (state.status === "paused") this.recorders.forEach((r) => r.pause?.());
|
|
3031
|
-
this.actions?.recordPageLoad(safe(() => performance.getEntriesByType("navigation")[0]?.type), document.referrer);
|
|
3032
|
-
this.sessions.startHeartbeat((r) => this.onHeartbeat(r.status), (e) => void this.onFatal(e), () => this.transport?.pending ?? 0, () => this.status);
|
|
3033
|
-
const token = this.sessions.currentToken;
|
|
3034
|
-
if (token) {
|
|
3035
|
-
this.lastStart = { testCaseId: s.testCaseId, executionId: s.executionId, sessionToken: token };
|
|
3036
|
-
this.armed ?? (this.armed = { testCaseId: s.testCaseId, sessionToken: token });
|
|
3037
|
-
}
|
|
3038
|
-
this.bus.emit("sessionStart", s);
|
|
3039
|
-
} catch (e) {
|
|
3040
|
-
await this.teardown();
|
|
3041
|
-
this.sessions.clearPersisted();
|
|
3042
|
-
this.sessions.reset();
|
|
3043
|
-
this.fail(e);
|
|
3044
|
-
if (!(e instanceof SessionExpiredError || e instanceof SessionDisabledError || e instanceof AuthError)) throw e;
|
|
2306
|
+
window.sessionStorage.setItem(key, value);
|
|
2307
|
+
} catch {
|
|
3045
2308
|
}
|
|
3046
2309
|
}
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
startArmedRecording() {
|
|
3053
|
-
if ((this.status === "recording" || this.status === "paused") && this.sessions.session) return Promise.resolve(this.sessions.session);
|
|
3054
|
-
return this.startRecording(this.freshStart());
|
|
2310
|
+
removeSessionValue(key) {
|
|
2311
|
+
try {
|
|
2312
|
+
window.sessionStorage.removeItem(key);
|
|
2313
|
+
} catch {
|
|
2314
|
+
}
|
|
3055
2315
|
}
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
await this.
|
|
2316
|
+
async initializeDraft() {
|
|
2317
|
+
if (!this.config.persistenceKey) return;
|
|
2318
|
+
this.store = await RecordingStore.open(this.config.persistenceKey);
|
|
2319
|
+
if (this.launchTokenChanged) await this.store.clear();
|
|
2320
|
+
await this.restoreDraft();
|
|
2321
|
+
}
|
|
2322
|
+
async restoreDraft() {
|
|
2323
|
+
const saved = await this.store.load();
|
|
2324
|
+
if (this.suspended) return;
|
|
2325
|
+
this.clearRecording();
|
|
2326
|
+
if (!saved) {
|
|
2327
|
+
this.setStatus("idle");
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
this.events = saved.events;
|
|
2331
|
+
this.sequence = saved.events[saved.events.length - 1]?.sequence ?? 0;
|
|
2332
|
+
this.actionLog = saved.events.filter((event) => event.kind === "action").map((event) => event.data);
|
|
2333
|
+
this.contextLog = saved.events.filter((event) => event.kind === "context").map((event) => event.data);
|
|
2334
|
+
this.setStatus(saved.status);
|
|
2335
|
+
if (saved.status === "stopped") {
|
|
2336
|
+
this.payload = this.buildPayload();
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
this.accepting = saved.status === "recording";
|
|
2340
|
+
this.attach();
|
|
2341
|
+
await this.startRecorders();
|
|
2342
|
+
if (saved.status === "paused") this.recorders.forEach((recorder) => recorder.pause?.());
|
|
2343
|
+
}
|
|
2344
|
+
save(operation) {
|
|
2345
|
+
void operation.catch((error) => {
|
|
2346
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2347
|
+
this.lastError = { code: "LOCAL_STORAGE_FAILED", message: err.message };
|
|
2348
|
+
this.bus.emit("error", err);
|
|
2349
|
+
});
|
|
3060
2350
|
}
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
2351
|
+
startRecording() {
|
|
2352
|
+
if (this.startPromise) return this.startPromise;
|
|
2353
|
+
this.startPromise = this.doStart().finally(() => {
|
|
2354
|
+
this.startPromise = void 0;
|
|
2355
|
+
});
|
|
2356
|
+
return this.startPromise;
|
|
3065
2357
|
}
|
|
3066
|
-
async
|
|
3067
|
-
|
|
3068
|
-
this.
|
|
3069
|
-
this.
|
|
3070
|
-
this.
|
|
3071
|
-
this.
|
|
3072
|
-
|
|
3073
|
-
this.
|
|
3074
|
-
this.
|
|
3075
|
-
this.
|
|
3076
|
-
this.
|
|
3077
|
-
|
|
2358
|
+
async doStart() {
|
|
2359
|
+
await this.init();
|
|
2360
|
+
if (this.status === "recording" || this.status === "paused") return;
|
|
2361
|
+
if (this.completePromise || this.stopPromise) throw new InvalidStateError("Finaliza\xE7\xE3o em andamento");
|
|
2362
|
+
await this.store?.clear();
|
|
2363
|
+
this.clearRecording();
|
|
2364
|
+
this.setStatus("initializing");
|
|
2365
|
+
await this.store?.setStatus("recording");
|
|
2366
|
+
this.accepting = true;
|
|
2367
|
+
this.attach();
|
|
2368
|
+
this.emitLifecycle("recording-start");
|
|
2369
|
+
await this.startRecorders();
|
|
2370
|
+
this.setStatus("recording");
|
|
2371
|
+
await this.store?.flush();
|
|
2372
|
+
this.bus.emit("recordingStart", void 0);
|
|
3078
2373
|
}
|
|
3079
2374
|
async pauseRecording() {
|
|
2375
|
+
this.assertAlive();
|
|
2376
|
+
if (this.restorePromise) await this.restorePromise;
|
|
3080
2377
|
if (this.status === "paused") return;
|
|
3081
|
-
if (this.status !== "recording") throw new InvalidStateError(
|
|
2378
|
+
if (this.status !== "recording") throw new InvalidStateError("Sem grava\xE7\xE3o ativa");
|
|
3082
2379
|
this.actions?.flushAll();
|
|
3083
|
-
this.emitLifecycle("pause"
|
|
2380
|
+
this.emitLifecycle("pause");
|
|
3084
2381
|
this.accepting = false;
|
|
3085
2382
|
this.recorders.forEach((r) => safe(() => r.pause?.()));
|
|
3086
|
-
this.sessions.setStatus("paused");
|
|
3087
2383
|
this.setStatus("paused");
|
|
3088
|
-
|
|
2384
|
+
await this.store?.flush();
|
|
3089
2385
|
}
|
|
3090
2386
|
async resumeRecording() {
|
|
2387
|
+
this.assertAlive();
|
|
2388
|
+
if (this.restorePromise) await this.restorePromise;
|
|
3091
2389
|
if (this.status === "recording") return;
|
|
3092
|
-
if (this.status !== "paused") throw new InvalidStateError(
|
|
2390
|
+
if (this.status !== "paused") throw new InvalidStateError("Grava\xE7\xE3o n\xE3o est\xE1 pausada");
|
|
3093
2391
|
this.accepting = true;
|
|
3094
|
-
this.
|
|
2392
|
+
this.emitLifecycle("resume");
|
|
2393
|
+
for (const r of this.recorders) await r.resume?.();
|
|
3095
2394
|
this.setStatus("recording");
|
|
3096
|
-
this.
|
|
3097
|
-
for (const r of this.recorders) await Promise.resolve(safe(() => r.resume?.()));
|
|
2395
|
+
await this.store?.flush();
|
|
3098
2396
|
}
|
|
3099
2397
|
stopRecording() {
|
|
3100
2398
|
if (this.stopPromise) return this.stopPromise;
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
return Promise.resolve({ sessionId: s?.sessionId ?? "", flushed: true, pendingEvents: 0, completed: true });
|
|
3105
|
-
return Promise.reject(new InvalidStateError(`N\xE3o \xE9 poss\xEDvel encerrar no estado ${this.status}`));
|
|
3106
|
-
}
|
|
3107
|
-
this.stopPromise = this.doStop().finally(() => this.stopPromise = void 0);
|
|
2399
|
+
this.stopPromise = this.doStop().finally(() => {
|
|
2400
|
+
this.stopPromise = void 0;
|
|
2401
|
+
});
|
|
3108
2402
|
return this.stopPromise;
|
|
3109
2403
|
}
|
|
3110
2404
|
async doStop() {
|
|
3111
|
-
|
|
2405
|
+
this.assertAlive();
|
|
2406
|
+
if (this.restorePromise) await this.restorePromise;
|
|
2407
|
+
if (this.startPromise) await this.startPromise;
|
|
2408
|
+
if (this.payload) return this.payload;
|
|
2409
|
+
if (this.status !== "recording" && this.status !== "paused") throw new InvalidStateError("Sem grava\xE7\xE3o ativa");
|
|
3112
2410
|
this.setStatus("stopping");
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
2411
|
+
this.actions?.flushAll();
|
|
2412
|
+
this.emitLifecycle("stop");
|
|
2413
|
+
const recorders = this.recorders.splice(0).reverse();
|
|
2414
|
+
this.lifecycle?.uninstall();
|
|
2415
|
+
for (const recorder of recorders) {
|
|
2416
|
+
if (!(recorder instanceof HTTPRecorder)) safe(() => recorder.stop());
|
|
2417
|
+
}
|
|
2418
|
+
for (const recorder of recorders) {
|
|
2419
|
+
if (recorder instanceof HTTPRecorder) await recorder.drain();
|
|
3116
2420
|
}
|
|
3117
2421
|
this.accepting = false;
|
|
3118
|
-
this.
|
|
3119
|
-
this.
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
2422
|
+
this.rrweb = this.actions = this.context = void 0;
|
|
2423
|
+
this.payload = this.buildPayload();
|
|
2424
|
+
this.setStatus("stopped");
|
|
2425
|
+
await this.store?.flush();
|
|
2426
|
+
this.bus.emit("recordingEnd", this.payload);
|
|
2427
|
+
return this.payload;
|
|
2428
|
+
}
|
|
2429
|
+
complete(options = {}) {
|
|
2430
|
+
this.assertAlive();
|
|
2431
|
+
if (this.completePromise) return this.completePromise;
|
|
2432
|
+
if (this.submitted) return Promise.resolve(this.response);
|
|
2433
|
+
this.completePromise = this.doComplete(options).finally(() => {
|
|
2434
|
+
this.completePromise = void 0;
|
|
2435
|
+
});
|
|
2436
|
+
return this.completePromise;
|
|
2437
|
+
}
|
|
2438
|
+
async doComplete(options) {
|
|
2439
|
+
const endpoint = options.endpoint ?? this.config.endpoint;
|
|
2440
|
+
const token = options.token?.trim() || this.launchToken;
|
|
2441
|
+
if (!endpoint) throw new ConfigError("Endpoint HTTP obrigat\xF3rio para envio");
|
|
2442
|
+
if (!token) throw new ConfigError("Token obrigat\xF3rio para envio");
|
|
2443
|
+
const url = new URL(endpoint, location.href);
|
|
2444
|
+
if (!["https:", "http:"].includes(url.protocol)) throw new ConfigError("Endpoint HTTP inv\xE1lido");
|
|
2445
|
+
const payload = await this.stopRecording();
|
|
2446
|
+
try {
|
|
2447
|
+
const response = await fetch(url.href, {
|
|
2448
|
+
method: "POST",
|
|
2449
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${token}` },
|
|
2450
|
+
body: JSON.stringify(payload)
|
|
2451
|
+
});
|
|
2452
|
+
if (!response.ok) throw new OffscriptError("TRANSPORT_FAILED", `Falha ao enviar grava\xE7\xE3o: HTTP ${response.status}`, { status: response.status });
|
|
2453
|
+
this.submitted = true;
|
|
2454
|
+
const completedToken = this.launchToken ?? token;
|
|
2455
|
+
this.writeSessionValue(COMPLETED_TOKEN_STORAGE_KEY, tokenFingerprint(completedToken));
|
|
2456
|
+
this.removeSessionValue(TOKEN_STORAGE_KEY);
|
|
2457
|
+
this.removeSessionValue(DRAFT_STORAGE_KEY);
|
|
2458
|
+
this.launchToken = void 0;
|
|
2459
|
+
await this.store?.clear();
|
|
2460
|
+
const text = await response.text();
|
|
3123
2461
|
try {
|
|
3124
|
-
|
|
3125
|
-
} catch
|
|
3126
|
-
this.
|
|
2462
|
+
this.response = text ? JSON.parse(text) : null;
|
|
2463
|
+
} catch {
|
|
2464
|
+
this.response = text;
|
|
3127
2465
|
}
|
|
2466
|
+
this.lastError = void 0;
|
|
2467
|
+
this.controls?.showSuccess();
|
|
2468
|
+
this.controls?.unmount();
|
|
2469
|
+
this.controls = void 0;
|
|
2470
|
+
return this.response;
|
|
2471
|
+
} catch (error) {
|
|
2472
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2473
|
+
this.lastError = { code: err instanceof OffscriptError ? err.code : "INTERNAL", message: err.message };
|
|
2474
|
+
this.bus.emit("error", err);
|
|
2475
|
+
throw err;
|
|
3128
2476
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
2477
|
+
}
|
|
2478
|
+
async finishRecording() {
|
|
2479
|
+
if (this.config.onComplete) return this.config.onComplete();
|
|
2480
|
+
if (this.launchToken) return this.complete();
|
|
2481
|
+
return this.stopRecording();
|
|
2482
|
+
}
|
|
2483
|
+
async discardRecording() {
|
|
2484
|
+
this.assertAlive();
|
|
2485
|
+
if (this.restorePromise) await this.restorePromise;
|
|
2486
|
+
if (this.startPromise) await this.startPromise;
|
|
2487
|
+
if (this.stopPromise) await this.stopPromise;
|
|
2488
|
+
if (this.completePromise) throw new InvalidStateError("Envio em andamento");
|
|
2489
|
+
this.accepting = false;
|
|
2490
|
+
this.stopRecorders();
|
|
3131
2491
|
this.lifecycle?.uninstall();
|
|
3132
|
-
this.
|
|
3133
|
-
this.
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
return summary;
|
|
3138
|
-
}
|
|
3139
|
-
/**
|
|
3140
|
-
* Libera todos os recursos. Por padrão encerra a gravação ativa; com `{ complete: false }` apenas
|
|
3141
|
-
* desanexa a instrumentação e mantém a sessão persistida para ser retomada por outra instância.
|
|
3142
|
-
*/
|
|
3143
|
-
async destroy(opts = {}) {
|
|
2492
|
+
await this.store?.clear();
|
|
2493
|
+
this.clearRecording();
|
|
2494
|
+
this.setStatus("idle");
|
|
2495
|
+
}
|
|
2496
|
+
async destroy() {
|
|
3144
2497
|
if (this.destroyed) return;
|
|
3145
|
-
if (
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
}
|
|
3153
|
-
this.destroyed = true;
|
|
3154
|
-
await this.teardown();
|
|
2498
|
+
if (this.restorePromise) await this.restorePromise;
|
|
2499
|
+
if (this.startPromise) await this.startPromise;
|
|
2500
|
+
if (this.completePromise) await this.completePromise.catch(() => void 0);
|
|
2501
|
+
if (this.stopPromise) await this.stopPromise;
|
|
2502
|
+
this.accepting = false;
|
|
2503
|
+
this.stopRecorders();
|
|
2504
|
+
this.lifecycle?.uninstall();
|
|
3155
2505
|
this.controls?.unmount();
|
|
3156
|
-
this.
|
|
3157
|
-
this.sessions.reset();
|
|
3158
|
-
this.transport?.close();
|
|
2506
|
+
await this.store?.close();
|
|
3159
2507
|
this.bus.clear();
|
|
2508
|
+
this.clearRecording();
|
|
2509
|
+
this.destroyed = true;
|
|
3160
2510
|
}
|
|
3161
|
-
/** Dados estruturados para o motor Playwright, construídos a partir do que foi gravado nesta página. */
|
|
3162
2511
|
exportPlaywrightData() {
|
|
3163
|
-
|
|
3164
|
-
if (!s) throw new InvalidStateError("Sem sess\xE3o ativa");
|
|
3165
|
-
return chunkKQVK5EJR_cjs.buildPlaywrightContract({
|
|
3166
|
-
session: { sessionId: s.sessionId, executionId: s.executionId, testCaseId: s.testCaseId, platformId: s.platformId },
|
|
3167
|
-
actions: this.actionLog,
|
|
3168
|
-
contexts: this.contextLog.map((e) => e.data)
|
|
3169
|
-
});
|
|
2512
|
+
return chunkMZXF2LF2_cjs.buildPlaywrightContract({ actions: this.actionLog, contexts: this.contextLog });
|
|
3170
2513
|
}
|
|
3171
2514
|
getRecordedActions() {
|
|
3172
2515
|
return this.actionLog;
|
|
3173
2516
|
}
|
|
3174
|
-
// ------------------------------------------------------------------ EventSink
|
|
3175
2517
|
on(type, fn) {
|
|
3176
2518
|
return this.bus.on(type, fn);
|
|
3177
2519
|
}
|
|
3178
2520
|
off(type, fn) {
|
|
3179
2521
|
this.bus.off(type, fn);
|
|
3180
2522
|
}
|
|
3181
|
-
/** Usado pelos recorders. Não confundir com os eventos públicos (`on`). */
|
|
3182
2523
|
emit(kind, data, opts = {}) {
|
|
3183
2524
|
return this.record(kind, data, opts);
|
|
3184
2525
|
}
|
|
3185
2526
|
record(kind, data, opts) {
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
}
|
|
3197
|
-
this.
|
|
3198
|
-
const sequence = this.sessions.nextSequence();
|
|
3199
|
-
const timestamp = opts.at ?? Date.now();
|
|
3200
|
-
const id = `${session.tabId}:${sequence}`;
|
|
3201
|
-
let payload = data;
|
|
3202
|
-
let relatedActionId;
|
|
3203
|
-
let relatedActionDeltaMs;
|
|
3204
|
-
if (kind === "action") {
|
|
3205
|
-
const action = { ...data, id, sessionId: session.sessionId, executionId: session.executionId, sequence, timestamp };
|
|
3206
|
-
payload = action;
|
|
3207
|
-
this.recent.push({ id, t: timestamp });
|
|
3208
|
-
if (this.recent.length > 50) this.recent.shift();
|
|
3209
|
-
this.actionLog.push(action);
|
|
3210
|
-
if (this.actionLog.length > MAX_LOG) this.actionLog.shift();
|
|
3211
|
-
} else if (kind === "http" || kind === "console" || kind === "error" || kind === "iframe") {
|
|
3212
|
-
const rel = this.correlate(timestamp);
|
|
3213
|
-
relatedActionId = rel?.id;
|
|
3214
|
-
relatedActionDeltaMs = rel ? timestamp - rel.t : void 0;
|
|
3215
|
-
}
|
|
3216
|
-
const event = {
|
|
3217
|
-
id,
|
|
3218
|
-
kind,
|
|
3219
|
-
sessionId: session.sessionId,
|
|
3220
|
-
executionId: session.executionId,
|
|
3221
|
-
tabId: session.tabId,
|
|
3222
|
-
pageId: session.pageId,
|
|
3223
|
-
sequence,
|
|
3224
|
-
timestamp,
|
|
3225
|
-
relatedActionId,
|
|
3226
|
-
relatedActionDeltaMs,
|
|
3227
|
-
data: payload
|
|
3228
|
-
};
|
|
3229
|
-
if (kind === "context") {
|
|
3230
|
-
this.contextLog.push(event);
|
|
3231
|
-
if (this.contextLog.length > 50) this.contextLog.shift();
|
|
3232
|
-
}
|
|
3233
|
-
this.eventCount++;
|
|
3234
|
-
transport.enqueue(event);
|
|
3235
|
-
this.metrics.emitted++;
|
|
3236
|
-
if (this.config.debug) this.debug("event", kind, sequence);
|
|
3237
|
-
return event;
|
|
3238
|
-
} catch (e) {
|
|
3239
|
-
this.debug("emit failed", e);
|
|
3240
|
-
return null;
|
|
3241
|
-
} finally {
|
|
3242
|
-
if (t0) this.metrics.emitMs += performance.now() - t0;
|
|
2527
|
+
if (!this.accepting) return null;
|
|
2528
|
+
this.rrweb?.recoverIfNeeded();
|
|
2529
|
+
const sequence = ++this.sequence;
|
|
2530
|
+
const timestamp = opts.at ?? Date.now();
|
|
2531
|
+
const id = `${this.pageId}:${sequence}`;
|
|
2532
|
+
let payload = data;
|
|
2533
|
+
if (kind === "action") {
|
|
2534
|
+
const action = { ...data, id, sequence, timestamp };
|
|
2535
|
+
payload = action;
|
|
2536
|
+
this.actionLog.push(action);
|
|
2537
|
+
this.recent.push({ id, t: timestamp });
|
|
2538
|
+
if (this.recent.length > 50) this.recent.shift();
|
|
3243
2539
|
}
|
|
2540
|
+
const related = ["http", "console", "error", "iframe"].includes(kind) ? this.correlate(timestamp) : void 0;
|
|
2541
|
+
const event = {
|
|
2542
|
+
id,
|
|
2543
|
+
kind,
|
|
2544
|
+
tabId: this.pageId,
|
|
2545
|
+
pageId: this.pageId,
|
|
2546
|
+
sequence,
|
|
2547
|
+
timestamp,
|
|
2548
|
+
relatedActionId: related?.id,
|
|
2549
|
+
relatedActionDeltaMs: related ? timestamp - related.t : void 0,
|
|
2550
|
+
data: payload
|
|
2551
|
+
};
|
|
2552
|
+
if (kind === "context") this.contextLog.push(data);
|
|
2553
|
+
this.events.push(event);
|
|
2554
|
+
if (this.store) this.save(this.store.append(event));
|
|
2555
|
+
return event;
|
|
3244
2556
|
}
|
|
3245
2557
|
correlate(at) {
|
|
3246
2558
|
for (let i = this.recent.length - 1; i >= 0; i--) {
|
|
@@ -3256,18 +2568,27 @@ var Offscript = class {
|
|
|
3256
2568
|
attach() {
|
|
3257
2569
|
this.lifecycle ?? (this.lifecycle = new LifecycleManager({
|
|
3258
2570
|
onPageHide: () => {
|
|
3259
|
-
if (!this.accepting) return;
|
|
3260
2571
|
this.actions?.flushAll();
|
|
3261
|
-
|
|
3262
|
-
this.
|
|
2572
|
+
if (!this.store) return;
|
|
2573
|
+
this.accepting = false;
|
|
2574
|
+
this.stopRecorders();
|
|
2575
|
+
this.suspended = true;
|
|
2576
|
+
if (!this.restorePromise) this.store.checkpoint();
|
|
2577
|
+
},
|
|
2578
|
+
onPageShow: () => {
|
|
2579
|
+
if (!this.suspended) return;
|
|
2580
|
+
this.suspended = false;
|
|
2581
|
+
this.setStatus("initializing");
|
|
2582
|
+
this.restorePromise = this.restoreDraft().finally(() => {
|
|
2583
|
+
this.restorePromise = void 0;
|
|
2584
|
+
});
|
|
2585
|
+
this.save(this.restorePromise);
|
|
3263
2586
|
},
|
|
3264
2587
|
onVisibility: (hidden) => {
|
|
3265
|
-
if (hidden
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
}
|
|
2588
|
+
if (hidden) this.actions?.flushAll();
|
|
2589
|
+
},
|
|
2590
|
+
onConnectivity: () => {
|
|
3269
2591
|
},
|
|
3270
|
-
onConnectivity: (online) => this.transport?.setOnline(online),
|
|
3271
2592
|
onNavigation: ({ from, to, kind }) => {
|
|
3272
2593
|
if (!this.accepting) return;
|
|
3273
2594
|
this.actions?.recordNavigation(from, to, kind);
|
|
@@ -3276,16 +2597,14 @@ var Offscript = class {
|
|
|
3276
2597
|
}
|
|
3277
2598
|
}));
|
|
3278
2599
|
this.lifecycle.install();
|
|
3279
|
-
this.transport?.setOnline(typeof navigator === "undefined" ? true : navigator.onLine !== false);
|
|
3280
2600
|
}
|
|
3281
2601
|
async startRecorders() {
|
|
3282
|
-
this.ensureControls();
|
|
3283
2602
|
const r = this.config.recording;
|
|
3284
2603
|
const list = [];
|
|
3285
2604
|
if (r.rrweb) list.push(this.rrweb = new RRWebRecorder(this));
|
|
3286
|
-
if (r.context) list.push(this.context = new ContextRecorder(this
|
|
2605
|
+
if (r.context) list.push(this.context = new ContextRecorder(this));
|
|
3287
2606
|
if (r.actions) list.push(this.actions = new ActionRecorder(this));
|
|
3288
|
-
if (r.http) list.push(new HTTPRecorder(this
|
|
2607
|
+
if (r.http) list.push(new HTTPRecorder(this));
|
|
3289
2608
|
if (r.console || r.errors) list.push(new ErrorRecorder(this));
|
|
3290
2609
|
if (r.iframes) list.push(new IframeRecorder(this));
|
|
3291
2610
|
this.recorders = list;
|
|
@@ -3298,81 +2617,48 @@ var Offscript = class {
|
|
|
3298
2617
|
}
|
|
3299
2618
|
}
|
|
3300
2619
|
}
|
|
2620
|
+
buildPayload() {
|
|
2621
|
+
return {
|
|
2622
|
+
recording: this.events.filter((event) => event.kind !== "http"),
|
|
2623
|
+
request: this.events.filter((event) => event.kind === "http"),
|
|
2624
|
+
regression: this.exportPlaywrightData()
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
3301
2627
|
stopRecorders() {
|
|
3302
2628
|
for (const r of this.recorders.splice(0).reverse()) safe(() => r.stop());
|
|
3303
2629
|
this.rrweb = this.actions = this.context = void 0;
|
|
3304
2630
|
}
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
this.
|
|
3314
|
-
this.
|
|
3315
|
-
this.
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
this.
|
|
3319
|
-
}
|
|
3320
|
-
onHeartbeat(status) {
|
|
3321
|
-
if (status === "disabled") void this.shutdownRemote(new SessionDisabledError());
|
|
3322
|
-
else if (status === "expired") void this.onFatal(new SessionExpiredError());
|
|
3323
|
-
}
|
|
3324
|
-
/** Backend desativou a gravação: encerra com flush de melhor esforço. */
|
|
3325
|
-
async shutdownRemote(err) {
|
|
3326
|
-
if (this.status !== "recording" && this.status !== "paused") return;
|
|
3327
|
-
this.accepting = false;
|
|
3328
|
-
this.stopRecorders();
|
|
3329
|
-
this.sessions.stopHeartbeat();
|
|
3330
|
-
const session = this.sessions.session;
|
|
3331
|
-
await this.transport?.flushFinal(3e3).catch(() => void 0);
|
|
3332
|
-
this.transport?.stop();
|
|
3333
|
-
this.lifecycle?.uninstall();
|
|
3334
|
-
this.sessions.clearPersisted();
|
|
3335
|
-
this.sessions.reset();
|
|
3336
|
-
this.setStatus("stopped");
|
|
3337
|
-
this.bus.emit("disabled", { reason: err.message });
|
|
3338
|
-
if (session) this.bus.emit("sessionEnd", { sessionId: session.sessionId, flushed: false, pendingEvents: this.transport?.pending ?? 0, completed: false });
|
|
3339
|
-
}
|
|
3340
|
-
async onFatal(err) {
|
|
3341
|
-
if (this.status === "error" || this.status === "stopped") return;
|
|
3342
|
-
await this.teardown();
|
|
3343
|
-
this.sessions.clearPersisted();
|
|
3344
|
-
this.sessions.reset();
|
|
3345
|
-
this.fail(err);
|
|
3346
|
-
}
|
|
3347
|
-
fail(e) {
|
|
3348
|
-
const err = e instanceof Error ? e : new Error(String(e));
|
|
3349
|
-
this.lastError = { code: err.code ?? "INTERNAL", message: err.message };
|
|
3350
|
-
this.setStatus("error");
|
|
3351
|
-
this.bus.emit("error", err);
|
|
2631
|
+
clearRecording() {
|
|
2632
|
+
this.events = [];
|
|
2633
|
+
this.actionLog = [];
|
|
2634
|
+
this.contextLog = [];
|
|
2635
|
+
this.recent = [];
|
|
2636
|
+
this.sequence = 0;
|
|
2637
|
+
this.pageId = uid("page_");
|
|
2638
|
+
this.payload = void 0;
|
|
2639
|
+
this.submitted = false;
|
|
2640
|
+
this.response = void 0;
|
|
2641
|
+
this.lastError = void 0;
|
|
2642
|
+
}
|
|
2643
|
+
assertAlive() {
|
|
2644
|
+
if (this.destroyed) throw new InvalidStateError("SDK destru\xEDdo");
|
|
3352
2645
|
}
|
|
3353
2646
|
setStatus(to) {
|
|
3354
2647
|
const from = this.status;
|
|
3355
2648
|
if (from === to) return;
|
|
3356
2649
|
this.status = to;
|
|
2650
|
+
if (this.store && (to === "recording" || to === "paused" || to === "stopped")) this.save(this.store.setStatus(to));
|
|
3357
2651
|
this.bus.emit("statusChange", { from, to });
|
|
3358
2652
|
}
|
|
3359
|
-
debug(...args) {
|
|
3360
|
-
if (this.config.debug) console.debug("[offscript]", ...args);
|
|
3361
|
-
}
|
|
3362
2653
|
};
|
|
3363
|
-
Offscript.version =
|
|
2654
|
+
Offscript.version = chunkMZXF2LF2_cjs.SDK_VERSION;
|
|
3364
2655
|
|
|
3365
|
-
exports.AuthError = AuthError;
|
|
3366
2656
|
exports.ConfigError = ConfigError;
|
|
3367
2657
|
exports.DEFAULT_PRIVACY = DEFAULT_PRIVACY;
|
|
3368
2658
|
exports.DEFAULT_RECORDING = DEFAULT_RECORDING;
|
|
3369
|
-
exports.DEFAULT_TRANSPORT = DEFAULT_TRANSPORT;
|
|
3370
2659
|
exports.DataSanitizer = DataSanitizer;
|
|
3371
2660
|
exports.InvalidStateError = InvalidStateError;
|
|
3372
2661
|
exports.Offscript = Offscript;
|
|
3373
2662
|
exports.OffscriptError = OffscriptError;
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
exports.TransportError = TransportError;
|
|
3377
|
-
//# sourceMappingURL=chunk-I6JPZAQA.cjs.map
|
|
3378
|
-
//# sourceMappingURL=chunk-I6JPZAQA.cjs.map
|
|
2663
|
+
//# sourceMappingURL=chunk-PCJRHW66.cjs.map
|
|
2664
|
+
//# sourceMappingURL=chunk-PCJRHW66.cjs.map
|