@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,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var chunk7FCA5EJI_cjs = require('../chunk-7FCA5EJI.cjs');
4
- require('../chunk-M7CHVDW6.cjs');
3
+ var chunkYPGRVKY4_cjs = require('../chunk-YPGRVKY4.cjs');
4
+ require('../chunk-M2E6CWGK.cjs');
5
5
 
6
6
  // src/adapters/nuxt.ts
7
7
  function createNuxtOffscript(config) {
8
8
  if (typeof window === "undefined") return null;
9
- const sdk = new chunk7FCA5EJI_cjs.Offscript(config);
9
+ const sdk = new chunkYPGRVKY4_cjs.Offscript(config);
10
10
  void sdk.init().catch(() => void 0);
11
11
  return sdk;
12
12
  }
@@ -1,5 +1,5 @@
1
- import { Offscript } from '../chunk-LUTOFXSV.js';
2
- import '../chunk-4EPIPNDL.js';
1
+ import { Offscript } from '../chunk-5F637PDG.js';
2
+ import '../chunk-KKEYBVTX.js';
3
3
 
4
4
  // src/adapters/nuxt.ts
5
5
  function createNuxtOffscript(config) {
@@ -1,18 +1,18 @@
1
1
  'use strict';
2
2
 
3
- var chunkOXGIMYKL_cjs = require('../chunk-OXGIMYKL.cjs');
4
- require('../chunk-7FCA5EJI.cjs');
5
- require('../chunk-M7CHVDW6.cjs');
3
+ var chunkWK242AJ2_cjs = require('../chunk-WK242AJ2.cjs');
4
+ require('../chunk-YPGRVKY4.cjs');
5
+ require('../chunk-M2E6CWGK.cjs');
6
6
 
7
7
 
8
8
 
9
9
  Object.defineProperty(exports, "createOffscript", {
10
10
  enumerable: true,
11
- get: function () { return chunkOXGIMYKL_cjs.createOffscript; }
11
+ get: function () { return chunkWK242AJ2_cjs.createOffscript; }
12
12
  });
13
13
  Object.defineProperty(exports, "startOffscript", {
14
14
  enumerable: true,
15
- get: function () { return chunkOXGIMYKL_cjs.startOffscript; }
15
+ get: function () { return chunkWK242AJ2_cjs.startOffscript; }
16
16
  });
17
17
  //# sourceMappingURL=vanilla.cjs.map
18
18
  //# sourceMappingURL=vanilla.cjs.map
@@ -1,5 +1,5 @@
1
- export { createOffscript, startOffscript } from '../chunk-ZB5N4KJE.js';
2
- import '../chunk-LUTOFXSV.js';
3
- import '../chunk-4EPIPNDL.js';
1
+ export { createOffscript, startOffscript } from '../chunk-U232L3JD.js';
2
+ import '../chunk-5F637PDG.js';
3
+ import '../chunk-KKEYBVTX.js';
4
4
  //# sourceMappingURL=vanilla.js.map
5
5
  //# sourceMappingURL=vanilla.js.map
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunk7FCA5EJI_cjs = require('../chunk-7FCA5EJI.cjs');
4
- require('../chunk-M7CHVDW6.cjs');
3
+ var chunkYPGRVKY4_cjs = require('../chunk-YPGRVKY4.cjs');
4
+ require('../chunk-M2E6CWGK.cjs');
5
5
 
6
6
  // src/adapters/vue.ts
7
7
  var OFFSCRIPT_KEY = "$offscript";
8
8
  var OffscriptPlugin = {
9
9
  install(app, config) {
10
- const sdk = new chunk7FCA5EJI_cjs.Offscript(config);
10
+ const sdk = new chunkYPGRVKY4_cjs.Offscript(config);
11
11
  void sdk.init().catch(() => void 0);
12
12
  if (app.config?.globalProperties) {
13
13
  app.config.globalProperties[OFFSCRIPT_KEY] = sdk;
@@ -1,5 +1,5 @@
1
- import { Offscript } from '../chunk-LUTOFXSV.js';
2
- import '../chunk-4EPIPNDL.js';
1
+ import { Offscript } from '../chunk-5F637PDG.js';
2
+ import '../chunk-KKEYBVTX.js';
3
3
 
4
4
  // src/adapters/vue.ts
5
5
  var OFFSCRIPT_KEY = "$offscript";
@@ -1,4 +1,4 @@
1
- import { buildPlaywrightContract, SDK_VERSION, SelectorGenerator, ACTION_SCHEMA_VERSION } from './chunk-4EPIPNDL.js';
1
+ import { buildPlaywrightContract, SDK_VERSION, SelectorGenerator, ACTION_SCHEMA_VERSION } from './chunk-KKEYBVTX.js';
2
2
 
3
3
  // src/types/config.ts
4
4
  var DEFAULT_RECORDING = {
@@ -1253,6 +1253,8 @@ var IframeRecorder = class {
1253
1253
  var PRIORITY = { 2: "critical", 4: "critical", 5: "critical", 0: "normal", 1: "normal", 3: "normal", 6: "normal" };
1254
1254
  var URL_ATTRS = ["src", "href", "action", "poster", "data-src", "data", "formaction", "srcset", "cite", "rr_src"];
1255
1255
  var LOW_SOURCES = /* @__PURE__ */ new Set([1, 6, 12, 3]);
1256
+ var SNAPSHOT_DEBOUNCE_MS = 200;
1257
+ var SNAPSHOT_SETTLE_TIMEOUT_MS = 5e3;
1256
1258
  var RRWebRecorder = class {
1257
1259
  constructor(sink) {
1258
1260
  this.sink = sink;
@@ -1261,6 +1263,7 @@ var RRWebRecorder = class {
1261
1263
  this.stopped = true;
1262
1264
  this.needsSnapshot = false;
1263
1265
  this.snapshotsTaken = 0;
1266
+ this.settling = false;
1264
1267
  }
1265
1268
  async start() {
1266
1269
  this.stopped = false;
@@ -1293,14 +1296,76 @@ var RRWebRecorder = class {
1293
1296
  };
1294
1297
  this.stopFn = mod.record(options) ?? void 0;
1295
1298
  }
1299
+ /** Captura o DOM depois do ciclo de renderização atual (Vue, React, etc.). */
1300
+ scheduleSnapshot() {
1301
+ this.cancelScheduledSnapshot();
1302
+ if (typeof window === "undefined") return;
1303
+ this.settling = true;
1304
+ if (typeof MutationObserver !== "undefined" && document.documentElement) {
1305
+ this.settleObserver = new MutationObserver(() => this.notifySnapshotActivity());
1306
+ this.settleObserver.observe(document.documentElement, {
1307
+ attributes: true,
1308
+ characterData: true,
1309
+ childList: true,
1310
+ subtree: true
1311
+ });
1312
+ }
1313
+ this.settleTimeout = window.setTimeout(() => this.captureSettledSnapshot(), SNAPSHOT_SETTLE_TIMEOUT_MS);
1314
+ }
1315
+ notifySnapshotActivity() {
1316
+ if (!this.settling || typeof window === "undefined") return;
1317
+ if (this.scheduledSnapshotTimer !== void 0) window.clearTimeout(this.scheduledSnapshotTimer);
1318
+ this.scheduledSnapshotTimer = window.setTimeout(() => this.captureSettledSnapshot(), SNAPSHOT_DEBOUNCE_MS);
1319
+ }
1320
+ captureSettledSnapshot() {
1321
+ if (!this.settling || typeof window === "undefined") return;
1322
+ this.settling = false;
1323
+ this.cancelScheduledSnapshot();
1324
+ const capture = () => {
1325
+ if (!this.stopped && !this.paused) this.takeFullSnapshot();
1326
+ };
1327
+ if (typeof window.requestAnimationFrame !== "function") {
1328
+ capture();
1329
+ return;
1330
+ }
1331
+ this.scheduledSnapshotFrame = window.requestAnimationFrame(() => {
1332
+ this.scheduledSnapshotFrame = window.requestAnimationFrame(() => {
1333
+ this.scheduledSnapshotFrame = void 0;
1334
+ capture();
1335
+ });
1336
+ });
1337
+ }
1338
+ cancelScheduledSnapshot() {
1339
+ if (typeof window === "undefined") return;
1340
+ this.settling = false;
1341
+ if (this.scheduledSnapshotFrame !== void 0) {
1342
+ window.cancelAnimationFrame(this.scheduledSnapshotFrame);
1343
+ this.scheduledSnapshotFrame = void 0;
1344
+ }
1345
+ if (this.scheduledSnapshotTimer !== void 0) {
1346
+ window.clearTimeout(this.scheduledSnapshotTimer);
1347
+ this.scheduledSnapshotTimer = void 0;
1348
+ }
1349
+ if (this.settleTimeout !== void 0) {
1350
+ window.clearTimeout(this.settleTimeout);
1351
+ this.settleTimeout = void 0;
1352
+ }
1353
+ this.settleObserver?.disconnect();
1354
+ this.settleObserver = void 0;
1355
+ }
1296
1356
  onEvent(event) {
1297
1357
  if (this.paused) return;
1298
1358
  guard(this.sink, this.name, () => {
1299
1359
  this.sanitizeEvent(event);
1300
1360
  let priority = PRIORITY[event.type] ?? "normal";
1301
1361
  if (event.type === 3 && LOW_SOURCES.has(event.data?.source)) priority = "low";
1302
- if (event.type === 2) this.snapshotsTaken++;
1303
1362
  const emitted = this.sink.emit("rrweb", event, { priority, at: event.timestamp });
1363
+ if (event.type === 2) {
1364
+ this.needsSnapshot = !emitted;
1365
+ if (emitted) {
1366
+ this.snapshotsTaken++;
1367
+ }
1368
+ }
1304
1369
  if (!emitted && event.type === 3) this.needsSnapshot = true;
1305
1370
  });
1306
1371
  }
@@ -1333,13 +1398,15 @@ var RRWebRecorder = class {
1333
1398
  /** Chamado pelo núcleo quando há folga novamente após descartes. */
1334
1399
  recoverIfNeeded() {
1335
1400
  if (this.needsSnapshot && !this.paused) {
1336
- this.needsSnapshot = false;
1337
1401
  this.takeFullSnapshot();
1338
1402
  }
1339
1403
  }
1340
1404
  takeFullSnapshot() {
1341
1405
  if (this.stopped) return;
1342
- guard(this.sink, this.name, () => this.mod?.record.takeFullSnapshot(true));
1406
+ guard(this.sink, this.name, () => {
1407
+ if (!this.mod?.record) return;
1408
+ this.mod.record.takeFullSnapshot(true);
1409
+ });
1343
1410
  }
1344
1411
  addCustomEvent(tag, payload) {
1345
1412
  if (this.stopped) return;
@@ -1350,6 +1417,7 @@ var RRWebRecorder = class {
1350
1417
  }
1351
1418
  pause() {
1352
1419
  this.paused = true;
1420
+ this.cancelScheduledSnapshot();
1353
1421
  }
1354
1422
  /** Ao retomar, as mutações perdidas durante a pausa exigem um snapshot completo. */
1355
1423
  resume() {
@@ -1357,7 +1425,10 @@ var RRWebRecorder = class {
1357
1425
  this.takeFullSnapshot();
1358
1426
  }
1359
1427
  stop() {
1428
+ const snapshotPending = this.settling || this.scheduledSnapshotFrame !== void 0;
1429
+ if (!this.stopped && !this.paused && snapshotPending) this.takeFullSnapshot();
1360
1430
  this.stopped = true;
1431
+ this.cancelScheduledSnapshot();
1361
1432
  safe(() => this.stopFn?.());
1362
1433
  this.stopFn = void 0;
1363
1434
  }
@@ -2213,7 +2284,7 @@ var SUCCESS_TEMPLATE = `
2213
2284
  var TOKEN_STORAGE_KEY = "offscript-session-token";
2214
2285
  var DRAFT_STORAGE_KEY = "offscript-draft-key";
2215
2286
  var COMPLETED_TOKEN_STORAGE_KEY = "offscript-completed-token";
2216
- var RECORDING_SEGMENT_START_QUERY = "offscript_recording_start";
2287
+ var NAVIGATION_SETTLE_WINDOW_MS = 5e3;
2217
2288
  function tokenFingerprint(token) {
2218
2289
  let first = 2166136261;
2219
2290
  let second = 2654435769;
@@ -2224,6 +2295,23 @@ function tokenFingerprint(token) {
2224
2295
  }
2225
2296
  return `${first >>> 0}.${second >>> 0}`;
2226
2297
  }
2298
+ async function encodeRecordingPayload(payload) {
2299
+ const json = JSON.stringify(payload);
2300
+ const CompressionStreamCtor = globalThis.CompressionStream;
2301
+ if (!CompressionStreamCtor) return { body: json };
2302
+ try {
2303
+ const stream = new CompressionStreamCtor("gzip");
2304
+ const writer = stream.writable.getWriter();
2305
+ await writer.write(new TextEncoder().encode(json));
2306
+ await writer.close();
2307
+ return {
2308
+ body: await new Response(stream.readable).blob(),
2309
+ contentEncoding: "gzip"
2310
+ };
2311
+ } catch {
2312
+ return { body: json };
2313
+ }
2314
+ }
2227
2315
  var Offscript = class {
2228
2316
  constructor(config = {}) {
2229
2317
  this.bus = new Emitter();
@@ -2240,6 +2328,7 @@ var Offscript = class {
2240
2328
  this.pageId = uid("page_");
2241
2329
  this.submitted = false;
2242
2330
  this.launchTokenChanged = false;
2331
+ this.snapshotAfterNavigationUntil = 0;
2243
2332
  this.config = resolveConfig(config);
2244
2333
  this.sanitizer = new DataSanitizer(this.config.privacy);
2245
2334
  }
@@ -2255,11 +2344,19 @@ var Offscript = class {
2255
2344
  this.initPromise ?? (this.initPromise = this.initialize());
2256
2345
  await this.initPromise;
2257
2346
  if (this.restorePromise) await this.restorePromise;
2258
- if (this.config.controls && this.launchToken && !this.controls) {
2347
+ this.exposeRunnerBridge();
2348
+ if (this.config.controls && this.launchToken && !this.controls && !window.__offscriptRunner?.enabled) {
2259
2349
  this.controls = new RecordingControls(this);
2260
2350
  this.controls.mount();
2261
2351
  }
2262
2352
  }
2353
+ exposeRunnerBridge() {
2354
+ const bridge = window.__offscriptRunner;
2355
+ if (!bridge?.enabled || !bridge.endpoint) return;
2356
+ bridge.startRecording = () => this.startRecording();
2357
+ bridge.complete = () => this.complete({ endpoint: bridge.endpoint });
2358
+ bridge.ready = true;
2359
+ }
2263
2360
  async initialize() {
2264
2361
  this.resolveLaunchToken();
2265
2362
  if (this.launchToken && !this.config.persistenceKey) {
@@ -2315,27 +2412,8 @@ var Offscript = class {
2315
2412
  async initializeDraft() {
2316
2413
  if (!this.config.persistenceKey) return;
2317
2414
  this.store = await RecordingStore.open(this.config.persistenceKey);
2318
- const startsNewSegment = await this.consumeRecordingSegmentMarker();
2319
- if (this.launchTokenChanged || startsNewSegment) await this.store.clear();
2415
+ if (this.launchTokenChanged) await this.store.clear();
2320
2416
  await this.restoreDraft();
2321
- if (startsNewSegment) await this.startFreshRecording();
2322
- }
2323
- async consumeRecordingSegmentMarker() {
2324
- const url = new URL(window.location.href);
2325
- const marker = url.searchParams.get(RECORDING_SEGMENT_START_QUERY);
2326
- if (!marker) return false;
2327
- url.searchParams.delete(RECORDING_SEGMENT_START_QUERY);
2328
- window.history.replaceState(window.history.state, "", url.href);
2329
- if (!this.launchToken || !globalThis.crypto?.subtle) return false;
2330
- let fingerprint;
2331
- try {
2332
- const tokenBytes = new TextEncoder().encode(this.launchToken);
2333
- const digest = await globalThis.crypto.subtle.digest("SHA-256", tokenBytes);
2334
- fingerprint = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
2335
- } catch {
2336
- return false;
2337
- }
2338
- return marker === fingerprint;
2339
2417
  }
2340
2418
  async restoreDraft() {
2341
2419
  const saved = await this.store.load();
@@ -2465,10 +2543,17 @@ var Offscript = class {
2465
2543
  if (!["https:", "http:"].includes(url.protocol)) throw new ConfigError("Endpoint HTTP inv\xE1lido");
2466
2544
  const payload = await this.stopRecording();
2467
2545
  try {
2546
+ const encodedPayload = await encodeRecordingPayload(payload);
2547
+ const headers = {
2548
+ "Content-Type": "application/json",
2549
+ Accept: "application/json",
2550
+ Authorization: `Bearer ${token}`
2551
+ };
2552
+ if (encodedPayload.contentEncoding) headers["Content-Encoding"] = encodedPayload.contentEncoding;
2468
2553
  const response = await fetch(url.href, {
2469
2554
  method: "POST",
2470
- headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${token}` },
2471
- body: JSON.stringify(payload)
2555
+ headers,
2556
+ body: encodedPayload.body
2472
2557
  });
2473
2558
  if (!response.ok) throw new OffscriptError("TRANSPORT_FAILED", `Falha ao enviar grava\xE7\xE3o: HTTP ${response.status}`, { status: response.status });
2474
2559
  this.submitted = true;
@@ -2549,6 +2634,7 @@ var Offscript = class {
2549
2634
  this.rrweb?.recoverIfNeeded();
2550
2635
  const sequence = ++this.sequence;
2551
2636
  const timestamp = opts.at ?? Date.now();
2637
+ if (kind === "http" && timestamp <= this.snapshotAfterNavigationUntil) this.rrweb?.notifySnapshotActivity();
2552
2638
  const id = `${this.pageId}:${sequence}`;
2553
2639
  let payload = data;
2554
2640
  if (kind === "action") {
@@ -2615,6 +2701,8 @@ var Offscript = class {
2615
2701
  this.actions?.recordNavigation(from, to, kind);
2616
2702
  this.emitLifecycle("navigation", { from: this.sanitizer.sanitizeUrl(from), to: this.sanitizer.sanitizeUrl(to), kind }, "critical");
2617
2703
  this.context?.record("navigation");
2704
+ this.snapshotAfterNavigationUntil = Date.now() + NAVIGATION_SETTLE_WINDOW_MS;
2705
+ this.rrweb?.scheduleSnapshot();
2618
2706
  }
2619
2707
  }));
2620
2708
  this.lifecycle.install();
@@ -2675,5 +2763,5 @@ var Offscript = class {
2675
2763
  Offscript.version = SDK_VERSION;
2676
2764
 
2677
2765
  export { ConfigError, DEFAULT_PRIVACY, DEFAULT_RECORDING, DataSanitizer, InvalidStateError, Offscript, OffscriptError };
2678
- //# sourceMappingURL=chunk-LUTOFXSV.js.map
2679
- //# sourceMappingURL=chunk-LUTOFXSV.js.map
2766
+ //# sourceMappingURL=chunk-5F637PDG.js.map
2767
+ //# sourceMappingURL=chunk-5F637PDG.js.map