@offscript/sdk 0.1.7 → 0.1.9

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.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkM7CHVDW6_cjs = require('./chunk-M7CHVDW6.cjs');
3
+ var chunkM2E6CWGK_cjs = require('./chunk-M2E6CWGK.cjs');
4
4
 
5
5
  // src/types/config.ts
6
6
  var DEFAULT_RECORDING = {
@@ -347,7 +347,7 @@ var ActionRecorder = class {
347
347
  this.scrollState = /* @__PURE__ */ new Map();
348
348
  this.move = [];
349
349
  const r = sink.config.recording;
350
- this.gen = new chunkM7CHVDW6_cjs.SelectorGenerator({ testIdAttributes: r.testIdAttributes, stableAttributes: r.stableAttributes });
350
+ this.gen = new chunkM2E6CWGK_cjs.SelectorGenerator({ testIdAttributes: r.testIdAttributes, stableAttributes: r.stableAttributes });
351
351
  }
352
352
  start() {
353
353
  const d = this.d;
@@ -411,7 +411,7 @@ var ActionRecorder = class {
411
411
  emit(draft, el, extra) {
412
412
  const s = this.sink.sanitizer;
413
413
  const data = {
414
- schemaVersion: chunkM7CHVDW6_cjs.ACTION_SCHEMA_VERSION,
414
+ schemaVersion: chunkM2E6CWGK_cjs.ACTION_SCHEMA_VERSION,
415
415
  type: draft.type,
416
416
  url: s.sanitizeUrl(location.href),
417
417
  viewport: { width: window.innerWidth, height: window.innerHeight },
@@ -672,7 +672,7 @@ function collectContext(reason, captureSensitiveValues = false) {
672
672
  timezone: safe(() => Intl.DateTimeFormat().resolvedOptions().timeZone),
673
673
  online: nav.onLine,
674
674
  visibility: document.visibilityState,
675
- sdkVersion: chunkM7CHVDW6_cjs.SDK_VERSION,
675
+ sdkVersion: chunkM2E6CWGK_cjs.SDK_VERSION,
676
676
  captureSensitiveValues
677
677
  };
678
678
  }
@@ -1255,6 +1255,8 @@ var IframeRecorder = class {
1255
1255
  var PRIORITY = { 2: "critical", 4: "critical", 5: "critical", 0: "normal", 1: "normal", 3: "normal", 6: "normal" };
1256
1256
  var URL_ATTRS = ["src", "href", "action", "poster", "data-src", "data", "formaction", "srcset", "cite", "rr_src"];
1257
1257
  var LOW_SOURCES = /* @__PURE__ */ new Set([1, 6, 12, 3]);
1258
+ var SNAPSHOT_DEBOUNCE_MS = 200;
1259
+ var SNAPSHOT_SETTLE_TIMEOUT_MS = 5e3;
1258
1260
  var RRWebRecorder = class {
1259
1261
  constructor(sink) {
1260
1262
  this.sink = sink;
@@ -1263,6 +1265,7 @@ var RRWebRecorder = class {
1263
1265
  this.stopped = true;
1264
1266
  this.needsSnapshot = false;
1265
1267
  this.snapshotsTaken = 0;
1268
+ this.settling = false;
1266
1269
  }
1267
1270
  async start() {
1268
1271
  this.stopped = false;
@@ -1295,14 +1298,76 @@ var RRWebRecorder = class {
1295
1298
  };
1296
1299
  this.stopFn = mod.record(options) ?? void 0;
1297
1300
  }
1301
+ /** Captura o DOM depois do ciclo de renderização atual (Vue, React, etc.). */
1302
+ scheduleSnapshot() {
1303
+ this.cancelScheduledSnapshot();
1304
+ if (typeof window === "undefined") return;
1305
+ this.settling = true;
1306
+ if (typeof MutationObserver !== "undefined" && document.documentElement) {
1307
+ this.settleObserver = new MutationObserver(() => this.notifySnapshotActivity());
1308
+ this.settleObserver.observe(document.documentElement, {
1309
+ attributes: true,
1310
+ characterData: true,
1311
+ childList: true,
1312
+ subtree: true
1313
+ });
1314
+ }
1315
+ this.settleTimeout = window.setTimeout(() => this.captureSettledSnapshot(), SNAPSHOT_SETTLE_TIMEOUT_MS);
1316
+ }
1317
+ notifySnapshotActivity() {
1318
+ if (!this.settling || typeof window === "undefined") return;
1319
+ if (this.scheduledSnapshotTimer !== void 0) window.clearTimeout(this.scheduledSnapshotTimer);
1320
+ this.scheduledSnapshotTimer = window.setTimeout(() => this.captureSettledSnapshot(), SNAPSHOT_DEBOUNCE_MS);
1321
+ }
1322
+ captureSettledSnapshot() {
1323
+ if (!this.settling || typeof window === "undefined") return;
1324
+ this.settling = false;
1325
+ this.cancelScheduledSnapshot();
1326
+ const capture = () => {
1327
+ if (!this.stopped && !this.paused) this.takeFullSnapshot();
1328
+ };
1329
+ if (typeof window.requestAnimationFrame !== "function") {
1330
+ capture();
1331
+ return;
1332
+ }
1333
+ this.scheduledSnapshotFrame = window.requestAnimationFrame(() => {
1334
+ this.scheduledSnapshotFrame = window.requestAnimationFrame(() => {
1335
+ this.scheduledSnapshotFrame = void 0;
1336
+ capture();
1337
+ });
1338
+ });
1339
+ }
1340
+ cancelScheduledSnapshot() {
1341
+ if (typeof window === "undefined") return;
1342
+ this.settling = false;
1343
+ if (this.scheduledSnapshotFrame !== void 0) {
1344
+ window.cancelAnimationFrame(this.scheduledSnapshotFrame);
1345
+ this.scheduledSnapshotFrame = void 0;
1346
+ }
1347
+ if (this.scheduledSnapshotTimer !== void 0) {
1348
+ window.clearTimeout(this.scheduledSnapshotTimer);
1349
+ this.scheduledSnapshotTimer = void 0;
1350
+ }
1351
+ if (this.settleTimeout !== void 0) {
1352
+ window.clearTimeout(this.settleTimeout);
1353
+ this.settleTimeout = void 0;
1354
+ }
1355
+ this.settleObserver?.disconnect();
1356
+ this.settleObserver = void 0;
1357
+ }
1298
1358
  onEvent(event) {
1299
1359
  if (this.paused) return;
1300
1360
  guard(this.sink, this.name, () => {
1301
1361
  this.sanitizeEvent(event);
1302
1362
  let priority = PRIORITY[event.type] ?? "normal";
1303
1363
  if (event.type === 3 && LOW_SOURCES.has(event.data?.source)) priority = "low";
1304
- if (event.type === 2) this.snapshotsTaken++;
1305
1364
  const emitted = this.sink.emit("rrweb", event, { priority, at: event.timestamp });
1365
+ if (event.type === 2) {
1366
+ this.needsSnapshot = !emitted;
1367
+ if (emitted) {
1368
+ this.snapshotsTaken++;
1369
+ }
1370
+ }
1306
1371
  if (!emitted && event.type === 3) this.needsSnapshot = true;
1307
1372
  });
1308
1373
  }
@@ -1335,13 +1400,15 @@ var RRWebRecorder = class {
1335
1400
  /** Chamado pelo núcleo quando há folga novamente após descartes. */
1336
1401
  recoverIfNeeded() {
1337
1402
  if (this.needsSnapshot && !this.paused) {
1338
- this.needsSnapshot = false;
1339
1403
  this.takeFullSnapshot();
1340
1404
  }
1341
1405
  }
1342
1406
  takeFullSnapshot() {
1343
1407
  if (this.stopped) return;
1344
- guard(this.sink, this.name, () => this.mod?.record.takeFullSnapshot(true));
1408
+ guard(this.sink, this.name, () => {
1409
+ if (!this.mod?.record) return;
1410
+ this.mod.record.takeFullSnapshot(true);
1411
+ });
1345
1412
  }
1346
1413
  addCustomEvent(tag, payload) {
1347
1414
  if (this.stopped) return;
@@ -1352,6 +1419,7 @@ var RRWebRecorder = class {
1352
1419
  }
1353
1420
  pause() {
1354
1421
  this.paused = true;
1422
+ this.cancelScheduledSnapshot();
1355
1423
  }
1356
1424
  /** Ao retomar, as mutações perdidas durante a pausa exigem um snapshot completo. */
1357
1425
  resume() {
@@ -1359,7 +1427,10 @@ var RRWebRecorder = class {
1359
1427
  this.takeFullSnapshot();
1360
1428
  }
1361
1429
  stop() {
1430
+ const snapshotPending = this.settling || this.scheduledSnapshotFrame !== void 0;
1431
+ if (!this.stopped && !this.paused && snapshotPending) this.takeFullSnapshot();
1362
1432
  this.stopped = true;
1433
+ this.cancelScheduledSnapshot();
1363
1434
  safe(() => this.stopFn?.());
1364
1435
  this.stopFn = void 0;
1365
1436
  }
@@ -2215,7 +2286,7 @@ var SUCCESS_TEMPLATE = `
2215
2286
  var TOKEN_STORAGE_KEY = "offscript-session-token";
2216
2287
  var DRAFT_STORAGE_KEY = "offscript-draft-key";
2217
2288
  var COMPLETED_TOKEN_STORAGE_KEY = "offscript-completed-token";
2218
- var RECORDING_SEGMENT_START_QUERY = "offscript_recording_start";
2289
+ var NAVIGATION_SETTLE_WINDOW_MS = 5e3;
2219
2290
  function tokenFingerprint(token) {
2220
2291
  let first = 2166136261;
2221
2292
  let second = 2654435769;
@@ -2226,6 +2297,23 @@ function tokenFingerprint(token) {
2226
2297
  }
2227
2298
  return `${first >>> 0}.${second >>> 0}`;
2228
2299
  }
2300
+ async function encodeRecordingPayload(payload) {
2301
+ const json = JSON.stringify(payload);
2302
+ const CompressionStreamCtor = globalThis.CompressionStream;
2303
+ if (!CompressionStreamCtor) return { body: json };
2304
+ try {
2305
+ const stream = new CompressionStreamCtor("gzip");
2306
+ const writer = stream.writable.getWriter();
2307
+ await writer.write(new TextEncoder().encode(json));
2308
+ await writer.close();
2309
+ return {
2310
+ body: await new Response(stream.readable).blob(),
2311
+ contentEncoding: "gzip"
2312
+ };
2313
+ } catch {
2314
+ return { body: json };
2315
+ }
2316
+ }
2229
2317
  var Offscript = class {
2230
2318
  constructor(config = {}) {
2231
2319
  this.bus = new Emitter();
@@ -2242,6 +2330,7 @@ var Offscript = class {
2242
2330
  this.pageId = uid("page_");
2243
2331
  this.submitted = false;
2244
2332
  this.launchTokenChanged = false;
2333
+ this.snapshotAfterNavigationUntil = 0;
2245
2334
  this.config = resolveConfig(config);
2246
2335
  this.sanitizer = new DataSanitizer(this.config.privacy);
2247
2336
  }
@@ -2257,11 +2346,19 @@ var Offscript = class {
2257
2346
  this.initPromise ?? (this.initPromise = this.initialize());
2258
2347
  await this.initPromise;
2259
2348
  if (this.restorePromise) await this.restorePromise;
2260
- if (this.config.controls && this.launchToken && !this.controls) {
2349
+ this.exposeRunnerBridge();
2350
+ if (this.config.controls && this.launchToken && !this.controls && !window.__offscriptRunner?.enabled) {
2261
2351
  this.controls = new RecordingControls(this);
2262
2352
  this.controls.mount();
2263
2353
  }
2264
2354
  }
2355
+ exposeRunnerBridge() {
2356
+ const bridge = window.__offscriptRunner;
2357
+ if (!bridge?.enabled || !bridge.endpoint) return;
2358
+ bridge.startRecording = () => this.startRecording();
2359
+ bridge.complete = () => this.complete({ endpoint: bridge.endpoint });
2360
+ bridge.ready = true;
2361
+ }
2265
2362
  async initialize() {
2266
2363
  this.resolveLaunchToken();
2267
2364
  if (this.launchToken && !this.config.persistenceKey) {
@@ -2317,27 +2414,8 @@ var Offscript = class {
2317
2414
  async initializeDraft() {
2318
2415
  if (!this.config.persistenceKey) return;
2319
2416
  this.store = await RecordingStore.open(this.config.persistenceKey);
2320
- const startsNewSegment = await this.consumeRecordingSegmentMarker();
2321
- if (this.launchTokenChanged || startsNewSegment) await this.store.clear();
2417
+ if (this.launchTokenChanged) await this.store.clear();
2322
2418
  await this.restoreDraft();
2323
- if (startsNewSegment) await this.startFreshRecording();
2324
- }
2325
- async consumeRecordingSegmentMarker() {
2326
- const url = new URL(window.location.href);
2327
- const marker = url.searchParams.get(RECORDING_SEGMENT_START_QUERY);
2328
- if (!marker) return false;
2329
- url.searchParams.delete(RECORDING_SEGMENT_START_QUERY);
2330
- window.history.replaceState(window.history.state, "", url.href);
2331
- if (!this.launchToken || !globalThis.crypto?.subtle) return false;
2332
- let fingerprint;
2333
- try {
2334
- const tokenBytes = new TextEncoder().encode(this.launchToken);
2335
- const digest = await globalThis.crypto.subtle.digest("SHA-256", tokenBytes);
2336
- fingerprint = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
2337
- } catch {
2338
- return false;
2339
- }
2340
- return marker === fingerprint;
2341
2419
  }
2342
2420
  async restoreDraft() {
2343
2421
  const saved = await this.store.load();
@@ -2467,10 +2545,17 @@ var Offscript = class {
2467
2545
  if (!["https:", "http:"].includes(url.protocol)) throw new ConfigError("Endpoint HTTP inv\xE1lido");
2468
2546
  const payload = await this.stopRecording();
2469
2547
  try {
2548
+ const encodedPayload = await encodeRecordingPayload(payload);
2549
+ const headers = {
2550
+ "Content-Type": "application/json",
2551
+ Accept: "application/json",
2552
+ Authorization: `Bearer ${token}`
2553
+ };
2554
+ if (encodedPayload.contentEncoding) headers["Content-Encoding"] = encodedPayload.contentEncoding;
2470
2555
  const response = await fetch(url.href, {
2471
2556
  method: "POST",
2472
- headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${token}` },
2473
- body: JSON.stringify(payload)
2557
+ headers,
2558
+ body: encodedPayload.body
2474
2559
  });
2475
2560
  if (!response.ok) throw new OffscriptError("TRANSPORT_FAILED", `Falha ao enviar grava\xE7\xE3o: HTTP ${response.status}`, { status: response.status });
2476
2561
  this.submitted = true;
@@ -2532,7 +2617,7 @@ var Offscript = class {
2532
2617
  this.destroyed = true;
2533
2618
  }
2534
2619
  exportPlaywrightData() {
2535
- return chunkM7CHVDW6_cjs.buildPlaywrightContract({ actions: this.actionLog, contexts: this.contextLog });
2620
+ return chunkM2E6CWGK_cjs.buildPlaywrightContract({ actions: this.actionLog, contexts: this.contextLog });
2536
2621
  }
2537
2622
  getRecordedActions() {
2538
2623
  return this.actionLog;
@@ -2551,6 +2636,7 @@ var Offscript = class {
2551
2636
  this.rrweb?.recoverIfNeeded();
2552
2637
  const sequence = ++this.sequence;
2553
2638
  const timestamp = opts.at ?? Date.now();
2639
+ if (kind === "http" && timestamp <= this.snapshotAfterNavigationUntil) this.rrweb?.notifySnapshotActivity();
2554
2640
  const id = `${this.pageId}:${sequence}`;
2555
2641
  let payload = data;
2556
2642
  if (kind === "action") {
@@ -2617,6 +2703,8 @@ var Offscript = class {
2617
2703
  this.actions?.recordNavigation(from, to, kind);
2618
2704
  this.emitLifecycle("navigation", { from: this.sanitizer.sanitizeUrl(from), to: this.sanitizer.sanitizeUrl(to), kind }, "critical");
2619
2705
  this.context?.record("navigation");
2706
+ this.snapshotAfterNavigationUntil = Date.now() + NAVIGATION_SETTLE_WINDOW_MS;
2707
+ this.rrweb?.scheduleSnapshot();
2620
2708
  }
2621
2709
  }));
2622
2710
  this.lifecycle.install();
@@ -2674,7 +2762,7 @@ var Offscript = class {
2674
2762
  this.bus.emit("statusChange", { from, to });
2675
2763
  }
2676
2764
  };
2677
- Offscript.version = chunkM7CHVDW6_cjs.SDK_VERSION;
2765
+ Offscript.version = chunkM2E6CWGK_cjs.SDK_VERSION;
2678
2766
 
2679
2767
  exports.ConfigError = ConfigError;
2680
2768
  exports.DEFAULT_PRIVACY = DEFAULT_PRIVACY;
@@ -2683,5 +2771,5 @@ exports.DataSanitizer = DataSanitizer;
2683
2771
  exports.InvalidStateError = InvalidStateError;
2684
2772
  exports.Offscript = Offscript;
2685
2773
  exports.OffscriptError = OffscriptError;
2686
- //# sourceMappingURL=chunk-7FCA5EJI.cjs.map
2687
- //# sourceMappingURL=chunk-7FCA5EJI.cjs.map
2774
+ //# sourceMappingURL=chunk-YPGRVKY4.cjs.map
2775
+ //# sourceMappingURL=chunk-YPGRVKY4.cjs.map