@guidelytech/react-native 0.11.0 → 0.13.0

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/dist/index.js CHANGED
@@ -4,7 +4,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4
4
 
5
5
  // src/index.tsx
6
6
  import { useContext as useContext3, useEffect as useEffect9, useMemo as useMemo6, useRef as useRef7, useState as useState8 } from "react";
7
- import { AppState as AppState2, Dimensions, Linking, Platform as Platform6, StyleSheet as StyleSheet10, View as View12 } from "react-native";
7
+ import { AppState as AppState2, Dimensions, Linking, NativeModules, Platform as Platform6, StyleSheet as StyleSheet10, View as View12 } from "react-native";
8
8
  import { createStaticFlags as createStaticFlags2, stableStringify as stableStringify3 } from "@guidelytech/flags";
9
9
 
10
10
  // src/flags.tsx
@@ -96,7 +96,7 @@ function FlagsProvider({ children, client: external, payload, ...options }) {
96
96
  return /* @__PURE__ */ jsx(FlagsContext.Provider, { value, children });
97
97
  }
98
98
 
99
- // ../shared/dist/chunk-TGIUEYXM.js
99
+ // ../shared/dist/chunk-XJXXVV3C.js
100
100
  function fnv1a(input) {
101
101
  let h = 2166136261;
102
102
  for (let i = 0; i < input.length; i++) {
@@ -406,7 +406,7 @@ function sanitizeProperties(props) {
406
406
  }
407
407
  return out;
408
408
  }
409
- var SOURCEMAP_MAX_FILE_BYTES = 25 * 1024 * 1024;
409
+ var SOURCEMAP_MAX_FILE_BYTES = 100 * 1024 * 1024;
410
410
  var MAX_COMPLAINT_ATTACHMENT_BYTES = 2 * 1024 * 1024;
411
411
  var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
412
412
  var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
@@ -701,7 +701,7 @@ function localizeResourceCenter(rc, requested, defaultLocale = DEFAULT_LOCALE) {
701
701
  };
702
702
  }
703
703
 
704
- // ../sdk-core/dist/chunk-JUSM7J5N.js
704
+ // ../sdk-core/dist/chunk-XML74K2P.js
705
705
  var __defProp2 = Object.defineProperty;
706
706
  var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
707
707
  var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -1010,10 +1010,12 @@ var KEYS = {
1010
1010
  /** Messenger conversations this browser can read: [{ id, token }]. */
1011
1011
  conversations: (key) => `guidely:conversations:${key}`,
1012
1012
  /** Announcement ids opened in the messenger's News tab. */
1013
- newsSeen: (key) => `guidely:news:${key}`
1013
+ newsSeen: (key) => `guidely:news:${key}`,
1014
+ /** Where the visitor dragged the messenger launcher ({ side, y }). */
1015
+ launcher: (key) => `guidely:launcher:${key}`
1014
1016
  };
1015
1017
 
1016
- // ../sdk-core/dist/chunk-ZAF2KRJA.js
1018
+ // ../sdk-core/dist/chunk-2E2YUQAG.js
1017
1019
  var EventQueue = class {
1018
1020
  constructor(opts) {
1019
1021
  __publicField2(this, "opts", opts);
@@ -1323,6 +1325,49 @@ function dismissedAfterMerge(local, server) {
1323
1325
  // src/core/client.ts
1324
1326
  import { createFlagsClient as createFlagsClient2 } from "@guidelytech/flags";
1325
1327
 
1328
+ // src/core/launcherPlacement.ts
1329
+ var LAUNCHER_PLACEMENT_KEY = "guidely:messenger.launcher";
1330
+ var LAUNCHER_MARGIN = 16;
1331
+ var LAUNCHER_DRAG_THRESHOLD = 8;
1332
+ var clamp2 = (v, min, max) => Math.min(max, Math.max(min, v));
1333
+ function sideX(f, side) {
1334
+ return side === "left" ? f.insets.left + LAUNCHER_MARGIN : f.width - f.insets.right - LAUNCHER_MARGIN - f.size;
1335
+ }
1336
+ function verticalRange(f) {
1337
+ const max = f.height - f.defaultBottom - f.size;
1338
+ return { min: Math.min(max, f.insets.top + LAUNCHER_MARGIN), max };
1339
+ }
1340
+ function clampToSafeArea(f, p) {
1341
+ const left = f.insets.left + LAUNCHER_MARGIN;
1342
+ const top = f.insets.top + LAUNCHER_MARGIN;
1343
+ return {
1344
+ x: clamp2(p.x, left, Math.max(left, f.width - f.insets.right - LAUNCHER_MARGIN - f.size)),
1345
+ y: clamp2(p.y, top, Math.max(top, f.height - f.insets.bottom - LAUNCHER_MARGIN - f.size))
1346
+ };
1347
+ }
1348
+ function snapPlacement(f, p) {
1349
+ const { min, max } = verticalRange(f);
1350
+ const side = p.x + f.size / 2 < f.width / 2 ? "left" : "right";
1351
+ return { side, fraction: max > min ? (max - clamp2(p.y, min, max)) / (max - min) : 0 };
1352
+ }
1353
+ function placementPoint(f, placement) {
1354
+ const { min, max } = verticalRange(f);
1355
+ return { x: sideX(f, placement.side), y: max - clamp2(placement.fraction, 0, 1) * (max - min) };
1356
+ }
1357
+ function parsePlacement(raw) {
1358
+ if (!raw) return null;
1359
+ try {
1360
+ const v = JSON.parse(raw);
1361
+ if (!v || typeof v !== "object" || v.side !== "left" && v.side !== "right" || typeof v.fraction !== "number" || !Number.isFinite(v.fraction)) return null;
1362
+ return { side: v.side, fraction: clamp2(v.fraction, 0, 1) };
1363
+ } catch {
1364
+ return null;
1365
+ }
1366
+ }
1367
+ function serializePlacement(p) {
1368
+ return JSON.stringify({ side: p.side, fraction: Math.round(clamp2(p.fraction, 0, 1) * 1e4) / 1e4 });
1369
+ }
1370
+
1326
1371
  // src/core/autocapture.ts
1327
1372
  var TAP_SLOP = 10;
1328
1373
  var TAP_MAX_MS = 1e3;
@@ -2383,6 +2428,425 @@ var Messenger = class {
2383
2428
  }
2384
2429
  };
2385
2430
 
2431
+ // src/core/replay.ts
2432
+ var RrwebEvents = {
2433
+ IMG_ID: 8,
2434
+ TOUCH_START: 7,
2435
+ TOUCH_END: 9,
2436
+ CLICK: 2,
2437
+ CSS: "html,body{margin:0;padding:0;width:100%;height:100%;overflow:hidden;background:#000}img{display:block;width:100%;height:100%;object-fit:contain}",
2438
+ dataUrl(base64) {
2439
+ return `data:image/jpeg;base64,${base64}`;
2440
+ },
2441
+ meta(timestamp, href, width, height) {
2442
+ return JSON.stringify({ type: 4, data: { href, width, height }, timestamp });
2443
+ },
2444
+ fullSnapshot(timestamp, dataUrl, width, height) {
2445
+ const style = { type: 2, id: 5, tagName: "style", attributes: {}, childNodes: [{ type: 3, id: 6, textContent: RrwebEvents.CSS, isStyle: true }] };
2446
+ const img = { type: 2, id: 8, tagName: "img", attributes: { src: dataUrl, width, height, alt: "" }, childNodes: [] };
2447
+ const html = { type: 2, id: 3, tagName: "html", attributes: {}, childNodes: [{ type: 2, id: 4, tagName: "head", attributes: {}, childNodes: [style] }, { type: 2, id: 7, tagName: "body", attributes: {}, childNodes: [img] }] };
2448
+ const node = { type: 0, id: 1, childNodes: [{ type: 1, id: 2, name: "html", publicId: "", systemId: "" }, html] };
2449
+ return JSON.stringify({ type: 2, data: { node, initialOffset: { left: 0, top: 0 } }, timestamp });
2450
+ },
2451
+ frame(timestamp, dataUrl) {
2452
+ return JSON.stringify({ type: 3, data: { source: 0, texts: [], attributes: [{ id: 8, attributes: { src: dataUrl } }], removes: [], adds: [] }, timestamp });
2453
+ },
2454
+ /** MouseInteraction (source 2): 7 TouchStart, 9 TouchEnd, 2 Click. */
2455
+ interaction(timestamp, type, x, y) {
2456
+ return JSON.stringify({ type: 3, data: { source: 2, type, id: 8, x: round1(x), y: round1(y) }, timestamp });
2457
+ },
2458
+ touchMove(timestamp, x, y) {
2459
+ return JSON.stringify({ type: 3, data: { source: 6, positions: [{ x: round1(x), y: round1(y), id: 8, timeOffset: 0 }] }, timestamp });
2460
+ },
2461
+ custom(timestamp, tag, payload) {
2462
+ return JSON.stringify({ type: 5, data: { tag, payload }, timestamp });
2463
+ }
2464
+ };
2465
+ var round1 = (v) => Math.round(v * 10) / 10;
2466
+ var UPLOAD_TIMEOUT_MS = 3e4;
2467
+ var ReplayUploader = class {
2468
+ constructor(opts) {
2469
+ __publicField(this, "opts", opts);
2470
+ __publicField(this, "windowId", randomId(16));
2471
+ __publicField(this, "buffer", null);
2472
+ __publicField(this, "queue", []);
2473
+ __publicField(this, "queuedBytes", 0);
2474
+ __publicField(this, "seqs", /* @__PURE__ */ new Map());
2475
+ __publicField(this, "inflight", null);
2476
+ __publicField(this, "flushTimer", null);
2477
+ __publicField(this, "retryTimer", null);
2478
+ __publicField(this, "attempt", 0);
2479
+ __publicField(this, "destroyed", false);
2480
+ __publicField(this, "offRecover");
2481
+ this.offRecover = opts.health?.onRecover?.(() => {
2482
+ this.clearRetry();
2483
+ this.attempt = 0;
2484
+ this.flush();
2485
+ }) ?? null;
2486
+ }
2487
+ get pendingChunks() {
2488
+ return this.queue.length;
2489
+ }
2490
+ get bufferedEvents() {
2491
+ return this.buffer?.events.length ?? 0;
2492
+ }
2493
+ push(sessionId, json) {
2494
+ if (this.destroyed) return;
2495
+ if (this.buffer && this.buffer.sessionId !== sessionId) this.seal();
2496
+ const chunk = this.buffer ?? (this.buffer = { sessionId, meta: this.opts.meta(), events: [], bytes: 0 });
2497
+ chunk.events.push(json);
2498
+ chunk.bytes += json.length;
2499
+ if (chunk.bytes > (this.opts.maxBufferBytes ?? 1e6)) this.flush();
2500
+ else if (!this.flushTimer) {
2501
+ this.flushTimer = setTimeout(() => {
2502
+ this.flushTimer = null;
2503
+ this.flush();
2504
+ }, this.opts.flushIntervalMs ?? 1e4);
2505
+ }
2506
+ }
2507
+ /** Seals the buffer and uploads the backlog (one chunk at a time). */
2508
+ flush() {
2509
+ if (this.destroyed) return;
2510
+ this.seal();
2511
+ if (this.retryTimer || this.inflight || !this.queue.length) return;
2512
+ void this.sendNext();
2513
+ }
2514
+ /** Drops everything buffered or queued (consent withdrawn, server disabled replay). */
2515
+ discard() {
2516
+ this.buffer = null;
2517
+ this.queue = [];
2518
+ this.queuedBytes = 0;
2519
+ this.clearFlushTimer();
2520
+ this.clearRetry();
2521
+ this.attempt = 0;
2522
+ }
2523
+ /** Uploads what is left once; no retries afterwards. */
2524
+ destroy() {
2525
+ this.flush();
2526
+ this.destroyed = true;
2527
+ this.offRecover?.();
2528
+ this.clearFlushTimer();
2529
+ this.clearRetry();
2530
+ }
2531
+ clearFlushTimer() {
2532
+ if (this.flushTimer) clearTimeout(this.flushTimer);
2533
+ this.flushTimer = null;
2534
+ }
2535
+ clearRetry() {
2536
+ if (this.retryTimer) clearTimeout(this.retryTimer);
2537
+ this.retryTimer = null;
2538
+ }
2539
+ scheduleRetry(ms) {
2540
+ this.retryTimer = setTimeout(() => {
2541
+ this.retryTimer = null;
2542
+ this.flush();
2543
+ }, ms);
2544
+ }
2545
+ seal() {
2546
+ this.clearFlushTimer();
2547
+ const chunk = this.buffer;
2548
+ this.buffer = null;
2549
+ if (!chunk || !chunk.events.length) return;
2550
+ this.queue.push(chunk);
2551
+ this.queuedBytes += chunk.bytes;
2552
+ const max = this.opts.maxQueuedBytes ?? 5e6;
2553
+ while (this.queuedBytes > max && this.queue.length > 1) {
2554
+ const i = this.inflight && this.queue[0] === this.inflight ? 1 : 0;
2555
+ const [dropped] = this.queue.splice(i, 1);
2556
+ if (!dropped) break;
2557
+ this.queuedBytes -= dropped.bytes;
2558
+ this.opts.log?.(`replay backlog full: dropped a chunk of ${dropped.events.length} events`);
2559
+ }
2560
+ }
2561
+ nextSeq(chunk) {
2562
+ if (chunk.seq === void 0) {
2563
+ const seq = this.seqs.get(chunk.sessionId) ?? 0;
2564
+ this.seqs.set(chunk.sessionId, seq + 1);
2565
+ chunk.seq = seq;
2566
+ }
2567
+ return chunk.seq;
2568
+ }
2569
+ body(chunk) {
2570
+ const head = JSON.stringify({ sessionId: chunk.sessionId, windowId: this.windowId, seq: this.nextSeq(chunk), ...chunk.meta });
2571
+ return `${head.slice(0, -1)},"events":[${chunk.events.join(",")}]}`;
2572
+ }
2573
+ async sendNext() {
2574
+ const chunk = this.queue[0];
2575
+ if (!chunk) return;
2576
+ const wait = this.destroyed ? 0 : this.opts.health?.waitMs() ?? 0;
2577
+ if (wait > 0) return this.scheduleRetry(wait);
2578
+ this.inflight = chunk;
2579
+ const result = await this.upload(this.body(chunk));
2580
+ this.onResult(chunk, result);
2581
+ }
2582
+ async upload(json) {
2583
+ const f = this.opts.fetch();
2584
+ if (!f) return "drop";
2585
+ const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
2586
+ const timer = ac ? setTimeout(() => ac.abort(), UPLOAD_TIMEOUT_MS) : null;
2587
+ try {
2588
+ let res;
2589
+ try {
2590
+ res = await f(this.opts.url(), { method: "POST", body: json, headers: { "Content-Type": "application/json" }, signal: ac?.signal });
2591
+ } catch {
2592
+ this.opts.health?.failure();
2593
+ return "retry";
2594
+ }
2595
+ if (res.status === 429 || res.status >= 500) this.opts.health?.failure();
2596
+ else this.opts.health?.success();
2597
+ if (res.ok) {
2598
+ const data = await res.json().catch(() => null);
2599
+ return data?.disabled ? "disabled" : "ok";
2600
+ }
2601
+ return res.status >= 400 && res.status < 500 && res.status !== 429 ? "drop" : "retry";
2602
+ } finally {
2603
+ if (timer) clearTimeout(timer);
2604
+ }
2605
+ }
2606
+ onResult(chunk, result) {
2607
+ if (this.inflight === chunk) this.inflight = null;
2608
+ if (result === "retry" && this.queue.includes(chunk)) {
2609
+ if (!this.destroyed) {
2610
+ const max = this.opts.maxAttempts ?? 5;
2611
+ this.attempt = Math.min(this.attempt + 1, max);
2612
+ const delay = this.attempt >= max ? this.opts.slowRetryMs ?? 6e4 : backoffMs(this.attempt - 1, 1e3, 8e3);
2613
+ return this.scheduleRetry(Math.max(delay, this.opts.health?.waitMs() ?? 0));
2614
+ }
2615
+ this.opts.log?.("replay upload failed after the recorder stopped; dropping the chunk");
2616
+ }
2617
+ this.attempt = 0;
2618
+ this.remove(chunk);
2619
+ if (result === "disabled") {
2620
+ for (const c of this.queue.slice()) if (c.sessionId === chunk.sessionId) this.remove(c);
2621
+ if (this.buffer?.sessionId === chunk.sessionId) this.buffer = null;
2622
+ this.opts.onDisabled(chunk.sessionId);
2623
+ }
2624
+ if (this.queue.length && !this.retryTimer && !this.inflight) void this.sendNext();
2625
+ }
2626
+ remove(chunk) {
2627
+ const i = this.queue.indexOf(chunk);
2628
+ if (i >= 0) {
2629
+ this.queue.splice(i, 1);
2630
+ this.queuedBytes -= chunk.bytes;
2631
+ }
2632
+ }
2633
+ };
2634
+ var TOUCH_SETTLE_MS = 200;
2635
+ var IDLE_FRAMES = 3;
2636
+ var MAX_IDLE_INTERVAL_MS = 5e3;
2637
+ var SessionRecorder = class {
2638
+ constructor(opts) {
2639
+ __publicField(this, "opts", opts);
2640
+ __publicField(this, "intervalMs");
2641
+ __publicField(this, "now");
2642
+ __publicField(this, "sid", null);
2643
+ __publicField(this, "foreground", true);
2644
+ __publicField(this, "generation", 0);
2645
+ __publicField(this, "timer", null);
2646
+ __publicField(this, "timerDue", 0);
2647
+ __publicField(this, "capturing", false);
2648
+ __publicField(this, "needKeyframe", true);
2649
+ __publicField(this, "hasKeyframe", false);
2650
+ __publicField(this, "lastFrame", null);
2651
+ __publicField(this, "unchanged", 0);
2652
+ __publicField(this, "captureSoon", false);
2653
+ __publicField(this, "pendingCustom", []);
2654
+ __publicField(this, "touchDown", false);
2655
+ __publicField(this, "downX", 0);
2656
+ __publicField(this, "downY", 0);
2657
+ __publicField(this, "downAt", 0);
2658
+ __publicField(this, "maxDistance", 0);
2659
+ __publicField(this, "lastMoveAt", 0);
2660
+ __publicField(this, "originX", 0);
2661
+ __publicField(this, "originY", 0);
2662
+ /** Viewport of the last frame (points), for chunk metadata. */
2663
+ __publicField(this, "viewportWidth", 0);
2664
+ __publicField(this, "viewportHeight", 0);
2665
+ this.intervalMs = Math.max(500, opts.intervalMs);
2666
+ this.now = opts.now ?? Date.now;
2667
+ }
2668
+ get sessionId() {
2669
+ return this.sid;
2670
+ }
2671
+ get isRecording() {
2672
+ return this.sid !== null;
2673
+ }
2674
+ get currentIntervalMs() {
2675
+ return this.unchanged >= IDLE_FRAMES ? Math.max(this.intervalMs, Math.min(4 * this.intervalMs, MAX_IDLE_INTERVAL_MS)) : this.intervalMs;
2676
+ }
2677
+ /** Starts (or keeps) recording `sessionId`; another session stops the current one first (uploading it). */
2678
+ start(sessionId) {
2679
+ if (this.sid === sessionId) return;
2680
+ if (this.sid !== null) this.stop(false);
2681
+ this.sid = sessionId;
2682
+ this.generation++;
2683
+ this.needKeyframe = true;
2684
+ this.hasKeyframe = false;
2685
+ this.lastFrame = null;
2686
+ this.unchanged = 0;
2687
+ this.captureSoon = false;
2688
+ this.pendingCustom = [];
2689
+ this.touchDown = false;
2690
+ if (this.foreground) this.schedule(0);
2691
+ }
2692
+ /** Stops recording; buffered events are uploaded unless `discard`. */
2693
+ stop(discard) {
2694
+ if (this.sid === null) return;
2695
+ this.sid = null;
2696
+ this.generation++;
2697
+ this.clearTimer();
2698
+ this.capturing = false;
2699
+ this.pendingCustom = [];
2700
+ this.touchDown = false;
2701
+ if (discard) this.opts.uploader.discard();
2702
+ else this.opts.uploader.flush();
2703
+ }
2704
+ /** App backgrounded: no captures; uploads what is buffered. */
2705
+ pause() {
2706
+ this.foreground = false;
2707
+ this.clearTimer();
2708
+ this.generation++;
2709
+ this.capturing = false;
2710
+ this.touchDown = false;
2711
+ this.opts.uploader.flush();
2712
+ }
2713
+ /** App foregrounded: resume with a keyframe. */
2714
+ resume() {
2715
+ if (this.foreground) return;
2716
+ this.foreground = true;
2717
+ this.needKeyframe = true;
2718
+ this.unchanged = 0;
2719
+ if (this.sid !== null) this.schedule(0);
2720
+ }
2721
+ /** A new screen: the next frame is a keyframe with the new href. */
2722
+ onScreen() {
2723
+ if (this.sid === null) return;
2724
+ this.needKeyframe = true;
2725
+ this.activity();
2726
+ }
2727
+ activity() {
2728
+ const wasIdle = this.unchanged >= IDLE_FRAMES;
2729
+ this.unchanged = 0;
2730
+ if (wasIdle && this.sid !== null && this.foreground && !this.capturing && !this.touchDown) this.schedule(this.intervalMs, true);
2731
+ }
2732
+ /** Adds the `guidely` marker for an analytics event. */
2733
+ addGuidelyEvent(name, customName, guideId) {
2734
+ const id = this.sid;
2735
+ if (id === null) return;
2736
+ const payload = { name };
2737
+ if (customName) payload.customName = customName;
2738
+ if (guideId) payload.guideId = guideId;
2739
+ this.activity();
2740
+ const now = this.now();
2741
+ if (!this.hasKeyframe) {
2742
+ if (this.pendingCustom.length < 50) this.pendingCustom.push([now, payload]);
2743
+ return;
2744
+ }
2745
+ this.opts.uploader.push(id, RrwebEvents.custom(now, "guidely", payload));
2746
+ }
2747
+ /** A primary-pointer touch at (x, y) points from the React root view. */
2748
+ touch(phase, rootX, rootY) {
2749
+ const id = this.sid;
2750
+ if (id === null || !this.foreground) return;
2751
+ const x = rootX + this.originX;
2752
+ const y = rootY + this.originY;
2753
+ const now = this.now();
2754
+ const emit = this.hasKeyframe;
2755
+ const push = (json) => emit && this.opts.uploader.push(id, json);
2756
+ switch (phase) {
2757
+ case "down":
2758
+ this.touchDown = true;
2759
+ this.unchanged = 0;
2760
+ this.downX = x;
2761
+ this.downY = y;
2762
+ this.downAt = now;
2763
+ this.maxDistance = 0;
2764
+ this.lastMoveAt = now;
2765
+ push(RrwebEvents.interaction(now, RrwebEvents.TOUCH_START, x, y));
2766
+ return;
2767
+ case "move":
2768
+ if (!this.touchDown) return;
2769
+ this.maxDistance = Math.max(this.maxDistance, Math.hypot(x - this.downX, y - this.downY));
2770
+ if (now - this.lastMoveAt < 50) return;
2771
+ this.lastMoveAt = now;
2772
+ push(RrwebEvents.touchMove(now, x, y));
2773
+ return;
2774
+ default:
2775
+ if (!this.touchDown) return;
2776
+ this.touchDown = false;
2777
+ this.unchanged = 0;
2778
+ this.maxDistance = Math.max(this.maxDistance, Math.hypot(x - this.downX, y - this.downY));
2779
+ push(RrwebEvents.interaction(now, RrwebEvents.TOUCH_END, x, y));
2780
+ if (phase === "up" && this.maxDistance <= 10 && now - this.downAt < 1e3) push(RrwebEvents.interaction(now, RrwebEvents.CLICK, x, y));
2781
+ if (this.capturing) this.captureSoon = true;
2782
+ else this.schedule(TOUCH_SETTLE_MS, true);
2783
+ }
2784
+ }
2785
+ clearTimer() {
2786
+ if (this.timer) clearTimeout(this.timer);
2787
+ this.timer = null;
2788
+ }
2789
+ /** Schedules the next capture `delay` ms from now, or keeps an earlier one when `ifSooner`. */
2790
+ schedule(delay, ifSooner = false) {
2791
+ const due = this.now() + delay;
2792
+ if (ifSooner && this.timer && this.timerDue <= due) return;
2793
+ this.clearTimer();
2794
+ this.timerDue = due;
2795
+ const gen = this.generation;
2796
+ this.timer = setTimeout(() => {
2797
+ this.timer = null;
2798
+ if (gen === this.generation) void this.tick();
2799
+ }, delay);
2800
+ }
2801
+ async tick() {
2802
+ if (this.sid === null || !this.foreground || this.capturing) return;
2803
+ if (this.touchDown) {
2804
+ this.schedule(this.intervalMs);
2805
+ return;
2806
+ }
2807
+ this.capturing = true;
2808
+ this.captureSoon = false;
2809
+ const gen = this.generation;
2810
+ let frame = null;
2811
+ try {
2812
+ frame = await this.opts.capturer(await this.opts.captureOptions());
2813
+ } catch (err) {
2814
+ this.opts.onError?.(err);
2815
+ }
2816
+ if (gen !== this.generation) return;
2817
+ this.capturing = false;
2818
+ if (frame) this.onFrame(frame, this.now());
2819
+ if (this.sid !== null && this.foreground) this.schedule(this.captureSoon ? TOUCH_SETTLE_MS : this.currentIntervalMs);
2820
+ }
2821
+ onFrame(frame, at) {
2822
+ const id = this.sid;
2823
+ if (id === null || !(frame.width > 0) || !(frame.height > 0) || !frame.base64) return;
2824
+ const sizeChanged = frame.width !== this.viewportWidth || frame.height !== this.viewportHeight;
2825
+ const keyframe = this.needKeyframe || !this.hasKeyframe || sizeChanged;
2826
+ if (!keyframe && this.lastFrame === frame.base64) {
2827
+ this.unchanged++;
2828
+ return;
2829
+ }
2830
+ this.unchanged = 0;
2831
+ this.lastFrame = frame.base64;
2832
+ this.originX = frame.originX ?? 0;
2833
+ this.originY = frame.originY ?? 0;
2834
+ this.viewportWidth = frame.width;
2835
+ this.viewportHeight = frame.height;
2836
+ const url = RrwebEvents.dataUrl(frame.base64);
2837
+ const up = this.opts.uploader;
2838
+ if (!keyframe) return up.push(id, RrwebEvents.frame(at, url));
2839
+ this.needKeyframe = false;
2840
+ up.push(id, RrwebEvents.meta(at, this.opts.href(), frame.width, frame.height));
2841
+ up.push(id, RrwebEvents.fullSnapshot(at, url, frame.width, frame.height));
2842
+ if (!this.hasKeyframe) {
2843
+ this.hasKeyframe = true;
2844
+ for (const [ts, payload] of this.pendingCustom) up.push(id, RrwebEvents.custom(Math.max(ts, at), "guidely", payload));
2845
+ this.pendingCustom = [];
2846
+ }
2847
+ }
2848
+ };
2849
+
2386
2850
  // src/core/storage.ts
2387
2851
  var PersistentStore = class {
2388
2852
  constructor(backing, onError = () => {
@@ -2436,7 +2900,7 @@ var PersistentStore = class {
2436
2900
 
2437
2901
  // src/core/client.ts
2438
2902
  var SDK_NAME = "@guidelytech/react-native";
2439
- var SDK_VERSION = "0.11.0";
2903
+ var SDK_VERSION = "0.13.0";
2440
2904
  var DEFAULT_API_URL = "https://guidelytech.com";
2441
2905
  var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
2442
2906
  var CACHED_START_MS = 1500;
@@ -2497,6 +2961,10 @@ var GuidelyClient = class {
2497
2961
  __publicField(this, "lastAudiences", /* @__PURE__ */ new Set());
2498
2962
  __publicField(this, "backgroundedAt", null);
2499
2963
  __publicField(this, "messenger", null);
2964
+ __publicField(this, "replayUploader", null);
2965
+ __publicField(this, "recorder", null);
2966
+ /** Replay decision for the current session (sampled once; forced by start/stopSessionRecording). */
2967
+ __publicField(this, "replayDecision", null);
2500
2968
  __publicField(this, "ui", { steps: [], checklists: [], survey: null, resourceCenterOpen: false, theme: {}, messenger: false });
2501
2969
  __publicField(this, "uiListeners", /* @__PURE__ */ new Set());
2502
2970
  /** Feature flags for this environment: `guidely.flags.isEnabled("new-checkout")`. */
@@ -2602,7 +3070,7 @@ var GuidelyClient = class {
2602
3070
  async bootstrap() {
2603
3071
  const key = this.options.projectId;
2604
3072
  try {
2605
- await this.store.hydrate([KEYS.anon, KEYS.seen, KEYS.session, KEYS.config(key), KEYS.etag(key), KEYS.state(key), KEYS.user(key), KEYS.queue(key), KEYS.outbox(key), KEYS.conversations(key), appVersionKey(key)]);
3073
+ await this.store.hydrate([KEYS.anon, KEYS.seen, KEYS.session, KEYS.config(key), KEYS.etag(key), KEYS.state(key), KEYS.user(key), KEYS.queue(key), KEYS.outbox(key), KEYS.conversations(key), appVersionKey(key), LAUNCHER_PLACEMENT_KEY]);
2606
3074
  if (this.destroyed) return;
2607
3075
  this.anonymousId = this.store.get(KEYS.anon) || "";
2608
3076
  this.hadIdentity = !!this.anonymousId;
@@ -2648,6 +3116,7 @@ var GuidelyClient = class {
2648
3116
  if (this.destroyed) return;
2649
3117
  this.isReady = true;
2650
3118
  if (this.config) this.emitter.emit("ready", this.config);
3119
+ this.syncReplay();
2651
3120
  this.notify();
2652
3121
  this.evaluate("ready");
2653
3122
  } catch (err) {
@@ -2674,6 +3143,7 @@ var GuidelyClient = class {
2674
3143
  this.syncMessenger();
2675
3144
  if (this.hydrated) this.checkLaunch();
2676
3145
  this.emitter.emit("config", this.config);
3146
+ this.syncReplay();
2677
3147
  this.log("config loaded", this.config.configVersion, `${this.config.guides.length} guides`);
2678
3148
  return true;
2679
3149
  }
@@ -2726,6 +3196,7 @@ var GuidelyClient = class {
2726
3196
  void this.outbox?.flush();
2727
3197
  void this.store.flush();
2728
3198
  void this.flags.flush();
3199
+ this.recorder?.pause();
2729
3200
  this.messenger?.setForeground(false);
2730
3201
  } else if (state === "active" && this.backgroundedAt !== null) {
2731
3202
  this.messenger?.setForeground(true);
@@ -2735,7 +3206,10 @@ var GuidelyClient = class {
2735
3206
  this.session = newSession();
2736
3207
  this.persistSession();
2737
3208
  this.firedDelays.clear();
3209
+ this.recorder?.stop(false);
2738
3210
  }
3211
+ this.recorder?.resume();
3212
+ this.syncReplay();
2739
3213
  this.cameToForeground();
2740
3214
  void this.flags.refresh();
2741
3215
  if (Date.now() - this.configFetchedAt > this.options.configRefreshMs && this.api.health.canSend()) void this.refresh();
@@ -2758,6 +3232,9 @@ var GuidelyClient = class {
2758
3232
  this.queued = [];
2759
3233
  this.messenger?.destroy();
2760
3234
  this.messenger = null;
3235
+ this.recorder?.stop(!this.analyticsEnabled());
3236
+ this.recorder = null;
3237
+ this.replayUploader?.destroy();
2761
3238
  this.visibilityWatchers.forEach((stop) => stop());
2762
3239
  this.visibilityWatchers.clear();
2763
3240
  this.timers.forEach((t) => clearTimeout(t));
@@ -2872,6 +3349,7 @@ var GuidelyClient = class {
2872
3349
  this.store.set(KEYS.anon, this.anonymousId);
2873
3350
  this.state = emptyState();
2874
3351
  this.persistState();
3352
+ this.recorder?.stop(false);
2875
3353
  this.session = newSession();
2876
3354
  this.persistSession();
2877
3355
  this.lastAudiences = /* @__PURE__ */ new Set();
@@ -2879,6 +3357,7 @@ var GuidelyClient = class {
2879
3357
  this.firstVisit = true;
2880
3358
  this.exposures.clear();
2881
3359
  this.syncFlagAttributes();
3360
+ this.syncReplay();
2882
3361
  this.emitter.emit("identify", { userId: null, anonymousId: this.anonymousId });
2883
3362
  this.notify();
2884
3363
  if (this.isReady) this.evaluate("identify");
@@ -2893,6 +3372,7 @@ var GuidelyClient = class {
2893
3372
  this.whenHydrated(() => {
2894
3373
  if (consent === "denied") this.queue?.clear();
2895
3374
  else if (consent === "granted") void this.queue?.flush();
3375
+ this.syncReplay();
2896
3376
  });
2897
3377
  }
2898
3378
  analyticsEnabled() {
@@ -2921,6 +3401,7 @@ var GuidelyClient = class {
2921
3401
  const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
2922
3402
  if (locale) event.properties = { ...event.properties ?? {}, locale };
2923
3403
  this.queue.enqueue(event);
3404
+ this.recorder?.addGuidelyEvent(name, event.customName, event.guideId);
2924
3405
  this.emitter.emit("track", event);
2925
3406
  return event;
2926
3407
  }
@@ -2958,12 +3439,13 @@ var GuidelyClient = class {
2958
3439
  }
2959
3440
  this.screenName = name;
2960
3441
  this.targets.setScreen(name);
3442
+ if (changed) this.recorder?.onScreen();
2961
3443
  if (changed) this.syncFlagAttributes();
2962
3444
  const url = this.currentUrl();
2963
3445
  if (changed) {
2964
3446
  this.screenStartedAt = Date.now();
2965
3447
  this.screenLeft = this.backgroundedAt !== null;
2966
- if (this.pageviewsOn()) this.enqueue("page_view", { properties: { ...properties, path: url, screen: name, platform: this.bridge.device.platform } });
3448
+ if (this.pageviewsOn()) this.enqueue("page_view", { properties: { ...properties, path: url, screen: name, ...this.deviceProperties() } });
2967
3449
  }
2968
3450
  this.completeChecklistTasksBy("url_visited", url);
2969
3451
  this.active?.controller.resume();
@@ -2992,6 +3474,112 @@ var GuidelyClient = class {
2992
3474
  if (this.isReady) this.evaluate("click", { targetId });
2993
3475
  }
2994
3476
  // ---------------------------------------------------------------------------
3477
+ // Session replay
3478
+ // ---------------------------------------------------------------------------
3479
+ replaySettings() {
3480
+ const r = this.config?.settings?.replay;
3481
+ if (!r?.enabled || !this.options.sessionReplay?.enabled || !this.bridge.capturer) return null;
3482
+ return r;
3483
+ }
3484
+ /** Reconciles recording with config, consent, readiness, the session and its sample decision. Idempotent. */
3485
+ syncReplay() {
3486
+ if (this.destroyed || !this.hydrated) return;
3487
+ const r = this.isReady && this.analyticsEnabled() ? this.replaySettings() : null;
3488
+ const id = this.session.id;
3489
+ if (r && this.replayDecision?.sessionId !== id) {
3490
+ this.replayDecision = { sessionId: id, record: rolloutBucket(id, "replay") < (r.sampleRate ?? 100) };
3491
+ this.log(`replay ${this.replayDecision.record ? "sampled" : "not sampled"} for session ${id}`);
3492
+ }
3493
+ if (!r || !this.replayDecision?.record) {
3494
+ if (this.recorder?.isRecording) this.recorder.stop(!this.analyticsEnabled());
3495
+ return;
3496
+ }
3497
+ const rec = this.recorder ?? this.createRecorder();
3498
+ if (rec && rec.sessionId !== id) {
3499
+ this.log(`recording session ${id}`);
3500
+ rec.start(id);
3501
+ }
3502
+ }
3503
+ createRecorder() {
3504
+ const capturer = this.bridge.capturer;
3505
+ if (!capturer) return null;
3506
+ const uploader = this.replayUploader ?? (this.replayUploader = new ReplayUploader({
3507
+ url: () => this.api.url("/replay"),
3508
+ fetch: () => this.options.fetch ?? (typeof fetch === "function" ? fetch : null),
3509
+ health: this.api.health,
3510
+ meta: () => {
3511
+ const rec2 = this.recorder;
3512
+ return {
3513
+ anonymousId: this.anonymousId,
3514
+ userId: this.user?.id || null,
3515
+ ...this.collectUrls() && this.screenName ? { url: this.currentUrl() } : {},
3516
+ device: { type: this.bridge.device.deviceType, os: this.bridge.device.platform },
3517
+ ...rec2 && rec2.viewportWidth > 0 && rec2.viewportHeight > 0 ? { viewport: { width: rec2.viewportWidth, height: rec2.viewportHeight } } : {},
3518
+ sdk: { name: SDK_NAME, version: SDK_VERSION }
3519
+ };
3520
+ },
3521
+ onDisabled: (sid) => {
3522
+ if (sid !== this.session.id) return;
3523
+ this.log(`replay disabled by the server for session ${sid}`);
3524
+ this.replayDecision = { sessionId: sid, record: false };
3525
+ this.recorder?.stop(true);
3526
+ },
3527
+ log: (m) => this.log(m)
3528
+ }));
3529
+ const opts = this.options.sessionReplay ?? {};
3530
+ const rec = new SessionRecorder({
3531
+ capturer,
3532
+ uploader,
3533
+ intervalMs: opts.captureIntervalMs ?? 1e3,
3534
+ captureOptions: () => this.replayCaptureOptions(),
3535
+ href: () => this.currentUrl(),
3536
+ onError: (err) => this.reportError(err)
3537
+ });
3538
+ if (this.backgroundedAt !== null) rec.pause();
3539
+ this.recorder = rec;
3540
+ return rec;
3541
+ }
3542
+ async replayCaptureOptions() {
3543
+ const r = this.config?.settings?.replay;
3544
+ const opts = this.options.sessionReplay ?? {};
3545
+ const ids = new Set((r?.blockSelectors ?? []).map((sel) => guidelyIdFromSelector(sel)).filter((id) => !!id));
3546
+ const rects = await Promise.all(Array.from(ids, (id) => this.targets.measure(id)));
3547
+ return { maskAllText: !!opts.maskAllText || !!r?.maskAllText, maskAllImages: !!opts.maskAllImages, blockRects: rects.filter((rect) => !!rect) };
3548
+ }
3549
+ /**
3550
+ * Records this session even when it was not sampled (still needs `sessionReplay.enabled`, replay
3551
+ * enabled for the project, analytics consent and the native replay module). Sticks until the session ends.
3552
+ */
3553
+ startSessionRecording() {
3554
+ this.whenHydrated(() => {
3555
+ this.replayDecision = { sessionId: this.session.id, record: true };
3556
+ this.syncReplay();
3557
+ });
3558
+ }
3559
+ /** Stops recording for the rest of this session; what was recorded is uploaded. */
3560
+ stopSessionRecording() {
3561
+ this.whenHydrated(() => {
3562
+ this.replayDecision = { sessionId: this.session.id, record: false };
3563
+ this.recorder?.stop(false);
3564
+ });
3565
+ }
3566
+ /** True while this session is being recorded. */
3567
+ isRecording() {
3568
+ return !!this.recorder?.isRecording;
3569
+ }
3570
+ /** The current analytics session id (also the replay id). */
3571
+ getSessionId() {
3572
+ return this.session.id;
3573
+ }
3574
+ /** True when touches should be passed to {@link recordTouch}. */
3575
+ isRecordingTouches() {
3576
+ return !!this.recorder?.isRecording;
3577
+ }
3578
+ /** A primary-pointer touch on the app, in window points (the provider's touch observer calls this). */
3579
+ recordTouch(phase, x, y) {
3580
+ this.recorder?.touch(phase, x, y);
3581
+ }
3582
+ // ---------------------------------------------------------------------------
2995
3583
  // Product analytics (autocapture)
2996
3584
  // ---------------------------------------------------------------------------
2997
3585
  captureAllowed() {
@@ -3038,7 +3626,7 @@ var GuidelyClient = class {
3038
3626
  }
3039
3627
  if (this.destroyed) return;
3040
3628
  const platform = this.bridge.device.platform;
3041
- const maskAll = !!this.config?.settings?.replay?.maskAllText;
3629
+ const maskAll = !!this.config?.settings?.replay?.maskAllText || !!this.options.sessionReplay?.maskAllText;
3042
3630
  const eventUrl = this.collectUrls() && url ? url : void 0;
3043
3631
  if (on.clicks && el) this.enqueue("click", { url: eventUrl, properties: { ...tapProperties(el, tap.x, tap.y, platform, maskAll) } });
3044
3632
  const count = this.rage.add(tap.x, tap.y, t);
@@ -3128,7 +3716,7 @@ var GuidelyClient = class {
3128
3716
  if (this.screenName && this.screenLeft) {
3129
3717
  this.screenStartedAt = Date.now();
3130
3718
  this.screenLeft = false;
3131
- if (this.pageviewsOn() && this.autocaptureOn("pageLeave")) this.enqueue("page_view", { properties: { path: this.currentUrl(), screen: this.screenName, platform: this.bridge.device.platform, resumed: true } });
3719
+ if (this.pageviewsOn() && this.autocaptureOn("pageLeave")) this.enqueue("page_view", { properties: { path: this.currentUrl(), screen: this.screenName, ...this.deviceProperties(), resumed: true } });
3132
3720
  }
3133
3721
  } catch (err) {
3134
3722
  this.reportError(err);
@@ -3890,6 +4478,14 @@ var GuidelyClient = class {
3890
4478
  getMessenger() {
3891
4479
  return this.messenger;
3892
4480
  }
4481
+ /** Where the visitor dragged the messenger bubble on this device, or null to use the configured spot. */
4482
+ getMessengerLauncherPlacement() {
4483
+ return parsePlacement(this.store.get(LAUNCHER_PLACEMENT_KEY));
4484
+ }
4485
+ /** Remembers the bubble's spot on this device (in memory only when no `storage` was passed). */
4486
+ setMessengerLauncherPlacement(placement) {
4487
+ this.store.set(LAUNCHER_PLACEMENT_KEY, serializePlacement(placement));
4488
+ }
3893
4489
  /** Opens the Scout messenger, optionally on a tab. No-op when Scout is off for this platform. */
3894
4490
  openMessenger(tab) {
3895
4491
  this.whenHydrated(() => this.messenger?.open(tab));
@@ -3920,6 +4516,11 @@ var GuidelyClient = class {
3920
4516
  onFlagsChange(cb) {
3921
4517
  return this.flags.subscribe(cb);
3922
4518
  }
4519
+ /** Device facts on every screen view, read by the Pages devices / browsers breakdowns. */
4520
+ deviceProperties() {
4521
+ const d = this.bridge.device;
4522
+ return { platform: d.platform, deviceType: d.deviceType, os: d.platform };
4523
+ }
3923
4524
  /** Attributes flags are evaluated with: identity, traits, account, audiences, screen and device. */
3924
4525
  flagContext() {
3925
4526
  const d = this.bridge.device;
@@ -4718,8 +5319,8 @@ var styles3 = StyleSheet3.create({
4718
5319
  });
4719
5320
 
4720
5321
  // src/ui/Messenger.tsx
4721
- import { Fragment as Fragment2, useCallback, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState3, useSyncExternalStore as useSyncExternalStore2 } from "react";
4722
- import { ActivityIndicator, Keyboard, Modal as Modal2, Platform as Platform2, Pressable as Pressable4, ScrollView as ScrollView3, StyleSheet as StyleSheet4, Text as Text4, TextInput, View as View4 } from "react-native";
5322
+ import { Fragment as Fragment2, useCallback, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef2, useState as useState3, useSyncExternalStore as useSyncExternalStore2 } from "react";
5323
+ import { AccessibilityInfo, ActivityIndicator, Animated, Easing, Keyboard, Modal as Modal2, PanResponder, Platform as Platform2, Pressable as Pressable4, ScrollView as ScrollView3, StyleSheet as StyleSheet4, Text as Text4, TextInput, View as View4 } from "react-native";
4723
5324
  import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
4724
5325
  function useMessenger(store) {
4725
5326
  return useSyncExternalStore2(
@@ -4748,14 +5349,89 @@ function ago(iso) {
4748
5349
  if (s < 86400) return `${Math.floor(s / 3600)}h`;
4749
5350
  return `${Math.floor(s / 86400)}d`;
4750
5351
  }
4751
- function MessengerLauncher({ store, palette: p, style }) {
5352
+ var LAUNCHER_SIZE = 54;
5353
+ function useReduceMotion() {
5354
+ const [on, setOn] = useState3(false);
5355
+ useEffect3(() => {
5356
+ let live = true;
5357
+ AccessibilityInfo.isReduceMotionEnabled?.().then((v) => live && setOn(v)).catch(() => {
5358
+ });
5359
+ const sub = AccessibilityInfo.addEventListener?.("reduceMotionChanged", setOn);
5360
+ return () => {
5361
+ live = false;
5362
+ sub?.remove();
5363
+ };
5364
+ }, []);
5365
+ return on;
5366
+ }
5367
+ function MessengerLauncher({ store, client, palette: p, style, frame, defaultSide }) {
4752
5368
  const snap = useMessenger(store);
5369
+ const reduceMotion = useReduceMotion();
5370
+ const [placement, setPlacement] = useState3(() => client.getMessengerLauncherPlacement());
5371
+ const pan = useRef2(new Animated.ValueXY()).current;
5372
+ const offset = useRef2({ x: 0, y: 0 });
5373
+ const dragging = useRef2(false);
5374
+ const live = useRef2({ frame, defaultSide, reduceMotion });
5375
+ live.current = { frame, defaultSide, reduceMotion };
5376
+ const { width, height, size, defaultBottom } = frame;
5377
+ const { top, bottom, left, right } = frame.insets;
5378
+ useLayoutEffect(() => {
5379
+ if (dragging.current) return;
5380
+ const f = live.current.frame;
5381
+ const anchor = placementPoint(f, { side: defaultSide, fraction: 0 });
5382
+ const at = placement ? placementPoint(f, placement) : anchor;
5383
+ offset.current = { x: at.x - anchor.x, y: at.y - anchor.y };
5384
+ pan.setValue(offset.current);
5385
+ }, [pan, placement, defaultSide, width, height, size, defaultBottom, top, bottom, left, right]);
5386
+ const responder = useMemo3(() => {
5387
+ const moved = (g) => Math.hypot(g.dx, g.dy) > LAUNCHER_DRAG_THRESHOLD;
5388
+ const anchor = () => placementPoint(live.current.frame, { side: live.current.defaultSide, fraction: 0 });
5389
+ let start = { x: 0, y: 0 };
5390
+ const follow = (g) => {
5391
+ const a = anchor();
5392
+ const at = clampToSafeArea(live.current.frame, { x: a.x + start.x + g.dx, y: a.y + start.y + g.dy });
5393
+ return { x: at.x - a.x, y: at.y - a.y };
5394
+ };
5395
+ const drop = (g) => {
5396
+ const a = anchor();
5397
+ const d = follow(g);
5398
+ const next = snapPlacement(live.current.frame, { x: a.x + d.x, y: a.y + d.y });
5399
+ const at = placementPoint(live.current.frame, next);
5400
+ const to = { x: at.x - a.x, y: at.y - a.y };
5401
+ offset.current = to;
5402
+ client.setMessengerLauncherPlacement(next);
5403
+ const done = ({ finished }) => {
5404
+ if (finished) dragging.current = false;
5405
+ setPlacement(next);
5406
+ };
5407
+ if (live.current.reduceMotion) {
5408
+ pan.setValue(to);
5409
+ done({ finished: true });
5410
+ } else Animated.timing(pan, { toValue: to, duration: 300, easing: Easing.out(Easing.cubic), useNativeDriver: true }).start(done);
5411
+ };
5412
+ return PanResponder.create({
5413
+ // Taps stay with the Pressable; only a real drag is taken (which cancels its press).
5414
+ onMoveShouldSetPanResponderCapture: (_, g) => moved(g),
5415
+ onMoveShouldSetPanResponder: (_, g) => moved(g),
5416
+ onPanResponderGrant: () => {
5417
+ dragging.current = true;
5418
+ start = offset.current;
5419
+ pan.stopAnimation((v) => {
5420
+ if (Number.isFinite(v?.x) && Number.isFinite(v?.y)) start = v;
5421
+ });
5422
+ },
5423
+ onPanResponderMove: (_, g) => pan.setValue(follow(g)),
5424
+ onPanResponderRelease: (_, g) => drop(g),
5425
+ onPanResponderTerminate: (_, g) => drop(g),
5426
+ onPanResponderTerminationRequest: () => false
5427
+ });
5428
+ }, [client, pan]);
4753
5429
  if (snap.open) return null;
4754
5430
  const label = snap.unread ? `Open ${snap.config.name}, ${snap.unread} unread` : `Open ${snap.config.name}`;
4755
- return /* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => store.open(), style: ({ pressed }) => [styles4.launcher, style, { backgroundColor: p.primary, opacity: pressed ? 0.85 : 1 }], children: [
5431
+ return /* @__PURE__ */ jsx5(Animated.View, { ...responder.panHandlers, style: [styles4.launcherWrap, style, { transform: pan.getTranslateTransform() }], children: /* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => !dragging.current && store.open(), style: ({ pressed }) => [styles4.launcher, { backgroundColor: p.primary, opacity: pressed ? 0.85 : 1 }], children: [
4756
5432
  /* @__PURE__ */ jsx5(Text4, { style: [styles4.launcherIcon, { color: p.onPrimary }], children: "\u{1F4AC}" }),
4757
5433
  snap.unread ? /* @__PURE__ */ jsx5(View4, { style: styles4.badge, children: /* @__PURE__ */ jsx5(Text4, { style: styles4.badgeText, children: snap.unread }) }) : null
4758
- ] });
5434
+ ] }) });
4759
5435
  }
4760
5436
  function MessengerSheet({ store, client, palette: p, topInset, bottomInset }) {
4761
5437
  const snap = useMessenger(store);
@@ -5000,7 +5676,8 @@ function Article({ article, palette: p, client }) {
5000
5676
  }
5001
5677
  var styles4 = StyleSheet4.create({
5002
5678
  flex: { flex: 1 },
5003
- launcher: { position: "absolute", width: 54, height: 54, borderRadius: 27, alignItems: "center", justifyContent: "center", elevation: 8, shadowColor: "#000", shadowOpacity: 0.22, shadowRadius: 12, shadowOffset: { width: 0, height: 4 } },
5679
+ launcherWrap: { position: "absolute", width: LAUNCHER_SIZE, height: LAUNCHER_SIZE, elevation: 8 },
5680
+ launcher: { width: LAUNCHER_SIZE, height: LAUNCHER_SIZE, borderRadius: 27, alignItems: "center", justifyContent: "center", elevation: 8, shadowColor: "#000", shadowOpacity: 0.22, shadowRadius: 12, shadowOffset: { width: 0, height: 4 } },
5004
5681
  launcherIcon: { fontSize: 24 },
5005
5682
  badge: { position: "absolute", top: -2, right: -2, minWidth: 20, height: 20, borderRadius: 10, backgroundColor: "#dc2626", alignItems: "center", justifyContent: "center", paddingHorizontal: 5, borderWidth: 2, borderColor: "#ffffff" },
5006
5683
  badgeText: { color: "#ffffff", fontSize: 11, fontWeight: "800" },
@@ -5210,7 +5887,7 @@ var styles5 = StyleSheet5.create({
5210
5887
 
5211
5888
  // src/ui/StepLayer.tsx
5212
5889
  import { useEffect as useEffect5, useRef as useRef3, useState as useState5 } from "react";
5213
- import { AccessibilityInfo, Animated, Easing, Pressable as Pressable6, StyleSheet as StyleSheet7, View as View7 } from "react-native";
5890
+ import { AccessibilityInfo as AccessibilityInfo2, Animated as Animated2, Easing as Easing2, Pressable as Pressable6, StyleSheet as StyleSheet7, View as View7 } from "react-native";
5214
5891
 
5215
5892
  // src/core/position.ts
5216
5893
  var MARGIN = 12;
@@ -5233,20 +5910,20 @@ function positionCard(target, size, viewport, insets, placement, offset) {
5233
5910
  const order = rawSide === "auto" ? ["bottom", "top"] : [rawSide, opposite[rawSide], "bottom", "top"];
5234
5911
  const space = { top: target.y - insets.top, bottom: viewport.height - insets.bottom - (target.y + target.height) };
5235
5912
  const side = order.find(fits) ?? (space.bottom >= space.top ? "bottom" : "top");
5236
- const clamp2 = (v, min, max) => Math.max(min, Math.min(max, v));
5913
+ const clamp3 = (v, min, max) => Math.max(min, Math.min(max, v));
5237
5914
  if (side === "top" || side === "bottom") {
5238
5915
  const cx = target.x + target.width / 2;
5239
5916
  let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width - size.width : cx - size.width / 2;
5240
- x2 = clamp2(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN - size.width));
5917
+ x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN - size.width));
5241
5918
  let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset - size.height;
5242
- y2 = clamp2(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size.height));
5243
- return { x: x2, y: y2, side, arrow: clamp2(cx - x2, ARROW_INSET, size.width - ARROW_INSET) };
5919
+ y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size.height));
5920
+ return { x: x2, y: y2, side, arrow: clamp3(cx - x2, ARROW_INSET, size.width - ARROW_INSET) };
5244
5921
  }
5245
5922
  const cy = target.y + target.height / 2;
5246
5923
  let y = align === "start" ? target.y : align === "end" ? target.y + target.height - size.height : cy - size.height / 2;
5247
- y = clamp2(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size.height));
5924
+ y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size.height));
5248
5925
  const x = side === "right" ? target.x + target.width + offset : target.x - offset - size.width;
5249
- return { x, y, side, arrow: clamp2(cy - y, ARROW_INSET, size.height - ARROW_INSET) };
5926
+ return { x, y, side, arrow: clamp3(cy - y, ARROW_INSET, size.height - ARROW_INSET) };
5250
5927
  }
5251
5928
 
5252
5929
  // src/ui/StepCard.tsx
@@ -5342,10 +6019,10 @@ function useTargetRect(client, targetId, origin) {
5342
6019
  return rect;
5343
6020
  }
5344
6021
  function useFadeIn(key) {
5345
- const opacity = useRef3(new Animated.Value(0)).current;
6022
+ const opacity = useRef3(new Animated2.Value(0)).current;
5346
6023
  useEffect5(() => {
5347
6024
  opacity.setValue(0);
5348
- Animated.timing(opacity, { toValue: 1, duration: 160, easing: Easing.out(Easing.quad), useNativeDriver: true }).start();
6025
+ Animated2.timing(opacity, { toValue: 1, duration: 160, easing: Easing2.out(Easing2.quad), useNativeDriver: true }).start();
5349
6026
  }, [key, opacity]);
5350
6027
  return opacity;
5351
6028
  }
@@ -5357,22 +6034,22 @@ function StepLayer({ view, client, palette: p, geometry }) {
5357
6034
  const cardWidth = Math.min(viewport.width - 32, step.width ?? 340);
5358
6035
  useEffect5(() => {
5359
6036
  if (step.type === "hotspot" && !view.expanded) return;
5360
- AccessibilityInfo.announceForAccessibility?.(announcement(view));
6037
+ AccessibilityInfo2.announceForAccessibility?.(announcement(view));
5361
6038
  }, [view.key, view.expanded]);
5362
6039
  const backdrop = (children) => /* @__PURE__ */ jsx8(Pressable6, { accessible: false, style: [StyleSheet7.absoluteFill, { backgroundColor: p.overlay }], onPress: () => client.backdropPressed(view.guide.id), children });
5363
6040
  const anchored = !view.asModal && ["tooltip", "popover", "spotlight", "hotspot"].includes(step.type);
5364
6041
  if (!anchored && step.type === "banner") {
5365
6042
  const top = step.position.startsWith("top");
5366
- return /* @__PURE__ */ jsx8(Animated.View, { pointerEvents: "box-none", style: [styles7.banner, top ? { top: insets.top + 8 } : { bottom: insets.bottom + 8 }, { left: insets.left + 8, right: insets.right + 8, opacity }], children: /* @__PURE__ */ jsx8(StepCard, { view, client, palette: p, width: viewport.width - insets.left - insets.right - 16, compact: true }) });
6043
+ return /* @__PURE__ */ jsx8(Animated2.View, { pointerEvents: "box-none", style: [styles7.banner, top ? { top: insets.top + 8 } : { bottom: insets.bottom + 8 }, { left: insets.left + 8, right: insets.right + 8, opacity }], children: /* @__PURE__ */ jsx8(StepCard, { view, client, palette: p, width: viewport.width - insets.left - insets.right - 16, compact: true }) });
5367
6044
  }
5368
6045
  if (!anchored && step.type === "slideout") {
5369
6046
  const wide = viewport.width >= 600;
5370
6047
  const right = !step.position.endsWith("left");
5371
6048
  const width = wide ? Math.min(380, cardWidth) : viewport.width - insets.left - insets.right - 24;
5372
- return /* @__PURE__ */ jsx8(Animated.View, { pointerEvents: "box-none", style: [styles7.abs, { bottom: insets.bottom + 12, opacity }, wide ? right ? { right: insets.right + 16 } : { left: insets.left + 16 } : { left: insets.left + 12 }], children: /* @__PURE__ */ jsx8(StepCard, { view, client, palette: p, width }) });
6049
+ return /* @__PURE__ */ jsx8(Animated2.View, { pointerEvents: "box-none", style: [styles7.abs, { bottom: insets.bottom + 12, opacity }, wide ? right ? { right: insets.right + 16 } : { left: insets.left + 16 } : { left: insets.left + 12 }], children: /* @__PURE__ */ jsx8(StepCard, { view, client, palette: p, width }) });
5373
6050
  }
5374
6051
  if (!anchored) {
5375
- return /* @__PURE__ */ jsxs6(Animated.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, styles7.center, { opacity }], children: [
6052
+ return /* @__PURE__ */ jsxs6(Animated2.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, styles7.center, { opacity }], children: [
5376
6053
  step.showOverlay ? backdrop() : null,
5377
6054
  /* @__PURE__ */ jsx8(StepCard, { view, client, palette: p, width: Math.min(viewport.width - 40, step.width ?? 360) })
5378
6055
  ] });
@@ -5397,7 +6074,7 @@ function StepLayer({ view, client, palette: p, geometry }) {
5397
6074
  [hole.x, hole.y + hole.height - r, -r, -2 * r],
5398
6075
  [hole.x + hole.width - r, hole.y + hole.height - r, -2 * r, -2 * r]
5399
6076
  ];
5400
- return /* @__PURE__ */ jsxs6(Animated.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, { opacity }], children: [
6077
+ return /* @__PURE__ */ jsxs6(Animated2.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, { opacity }], children: [
5401
6078
  /* @__PURE__ */ jsx8(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: 0, height: Math.max(0, hole.y) }] }),
5402
6079
  /* @__PURE__ */ jsx8(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: hole.y + hole.height, bottom: 0 }] }),
5403
6080
  /* @__PURE__ */ jsx8(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, width: Math.max(0, hole.x), top: hole.y, height: hole.height }] }),
@@ -5407,7 +6084,7 @@ function StepLayer({ view, client, palette: p, geometry }) {
5407
6084
  /* @__PURE__ */ jsx8(Anchored, { view, client, palette: p, geometry, rect: hole, width: cardWidth })
5408
6085
  ] });
5409
6086
  }
5410
- return /* @__PURE__ */ jsxs6(Animated.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, { opacity }], children: [
6087
+ return /* @__PURE__ */ jsxs6(Animated2.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, { opacity }], children: [
5411
6088
  step.showOverlay ? backdrop() : null,
5412
6089
  /* @__PURE__ */ jsx8(Anchored, { view, client, palette: p, geometry, rect, width: cardWidth })
5413
6090
  ] });
@@ -5429,11 +6106,11 @@ function Arrow({ side, at, color, border }) {
5429
6106
  return /* @__PURE__ */ jsx8(View7, { pointerEvents: "none", style: [styles7.abs, styles7.arrow, place, { backgroundColor: color, borderColor: border }] });
5430
6107
  }
5431
6108
  function Hotspot({ x, y, size, pulse, color, onPress, label }) {
5432
- const scale = useRef3(new Animated.Value(0)).current;
6109
+ const scale = useRef3(new Animated2.Value(0)).current;
5433
6110
  const [reduceMotion, setReduceMotion] = useState5(false);
5434
6111
  useEffect5(() => {
5435
6112
  let alive = true;
5436
- AccessibilityInfo.isReduceMotionEnabled?.().then((v) => alive && setReduceMotion(v), () => {
6113
+ AccessibilityInfo2.isReduceMotionEnabled?.().then((v) => alive && setReduceMotion(v), () => {
5437
6114
  });
5438
6115
  return () => {
5439
6116
  alive = false;
@@ -5441,13 +6118,13 @@ function Hotspot({ x, y, size, pulse, color, onPress, label }) {
5441
6118
  }, []);
5442
6119
  useEffect5(() => {
5443
6120
  if (!pulse || reduceMotion) return;
5444
- const loop = Animated.loop(Animated.timing(scale, { toValue: 1, duration: 1400, easing: Easing.out(Easing.ease), useNativeDriver: true }));
6121
+ const loop = Animated2.loop(Animated2.timing(scale, { toValue: 1, duration: 1400, easing: Easing2.out(Easing2.ease), useNativeDriver: true }));
5445
6122
  loop.start();
5446
6123
  return () => loop.stop();
5447
6124
  }, [pulse, reduceMotion, scale]);
5448
6125
  return /* @__PURE__ */ jsxs6(Pressable6, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width: size, height: size }], children: [
5449
6126
  pulse && !reduceMotion ? /* @__PURE__ */ jsx8(
5450
- Animated.View,
6127
+ Animated2.View,
5451
6128
  {
5452
6129
  pointerEvents: "none",
5453
6130
  style: [styles7.abs, { width: size, height: size, borderRadius: size / 2, backgroundColor: color, opacity: scale.interpolate({ inputRange: [0, 1], outputRange: [0.55, 0] }), transform: [{ scale: scale.interpolate({ inputRange: [0, 1], outputRange: [1, 2.6] }) }] }]
@@ -5466,7 +6143,7 @@ var styles7 = StyleSheet7.create({
5466
6143
  // src/ui/Survey.tsx
5467
6144
  import { useEffect as useEffect6, useState as useState6 } from "react";
5468
6145
  import {
5469
- AccessibilityInfo as AccessibilityInfo2,
6146
+ AccessibilityInfo as AccessibilityInfo3,
5470
6147
  KeyboardAvoidingView,
5471
6148
  Platform as Platform4,
5472
6149
  Pressable as Pressable7,
@@ -5489,7 +6166,7 @@ function Survey({
5489
6166
  useEffect6(() => {
5490
6167
  setAnswers({});
5491
6168
  setError("");
5492
- AccessibilityInfo2.announceForAccessibility?.(
6169
+ AccessibilityInfo3.announceForAccessibility?.(
5493
6170
  `Survey: ${config?.questions[0]?.title ?? view.guide.name}`
5494
6171
  );
5495
6172
  }, [view.key]);
@@ -5872,6 +6549,7 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
5872
6549
  const unread = showRcLauncher ? client.getUnreadAnnouncements() : 0;
5873
6550
  const messengerLeft = messenger?.getSnapshot().config.launcher.position === "bottom-left";
5874
6551
  const checklistOnSameSide = ui.checklists.some((c) => c.guide.config.checklist?.position === "bottom-left" === messengerLeft) && client.options.showChecklistLauncher !== false;
6552
+ const messengerBottom = insets.bottom + launcherOffset + (checklistOnSameSide ? 56 : 0);
5875
6553
  return /* @__PURE__ */ jsxs8(View9, { ref, collapsable: false, pointerEvents: "box-none", style: StyleSheet9.absoluteFill, onLayout: measureOrigin, children: [
5876
6554
  ui.checklists.map((c) => /* @__PURE__ */ jsx10(Checklist, { view: c, client, palette: palette(client.themeFor(c.guide), scheme), geometry, showLauncher: client.options.showChecklistLauncher !== false, launcherBottom: launcherOffset }, c.guide.id)),
5877
6555
  showRcLauncher ? /* @__PURE__ */ jsxs8(
@@ -5887,7 +6565,17 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
5887
6565
  ]
5888
6566
  }
5889
6567
  ) : null,
5890
- messenger && client.options.showMessengerLauncher !== false ? /* @__PURE__ */ jsx10(MessengerLauncher, { store: messenger, palette: basePalette, style: [messengerLeft ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: insets.bottom + launcherOffset + (checklistOnSameSide ? 56 : 0) }] }) : null,
6568
+ messenger && client.options.showMessengerLauncher !== false ? /* @__PURE__ */ jsx10(
6569
+ MessengerLauncher,
6570
+ {
6571
+ store: messenger,
6572
+ client,
6573
+ palette: basePalette,
6574
+ style: [messengerLeft ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: messengerBottom }],
6575
+ frame: { width, height, insets, size: LAUNCHER_SIZE, defaultBottom: messengerBottom },
6576
+ defaultSide: messengerLeft ? "left" : "right"
6577
+ }
6578
+ ) : null,
5891
6579
  ui.survey ? /* @__PURE__ */ jsx10(Survey, { view: ui.survey, client, palette: palette(client.themeFor(ui.survey.guide), scheme), geometry }) : null,
5892
6580
  ui.steps.map((s) => /* @__PURE__ */ jsx10(StepLayer, { view: s, client, palette: palette(client.themeFor(s.guide), scheme, s.guide.config.appearance), geometry }, s.key)),
5893
6581
  messenger ? /* @__PURE__ */ jsx10(MessengerSheet, { store: messenger, client, palette: basePalette, topInset: insets.top, bottomInset: insets.bottom }) : /* @__PURE__ */ jsx10(ResourceCenter, { client, palette: basePalette, open: ui.resourceCenterOpen, topInset: insets.top, bottomInset: insets.bottom })
@@ -5907,8 +6595,9 @@ function rcLauncherLabel(client) {
5907
6595
 
5908
6596
  // src/ui/Autocapture.tsx
5909
6597
  import { jsx as jsx11 } from "react/jsx-runtime";
6598
+ var GUIDELY_MASK_NATIVE_ID = "guidely-mask";
5910
6599
  function GuidelyMask({ children, ...rest }) {
5911
- return /* @__PURE__ */ jsx11(View10, { ...rest, children });
6600
+ return /* @__PURE__ */ jsx11(View10, { collapsable: false, ...rest, nativeID: GUIDELY_MASK_NATIVE_ID, children });
5912
6601
  }
5913
6602
  var RESOLVE = {
5914
6603
  isIgnored: (type) => type === GuidelyOverlay,
@@ -5919,6 +6608,18 @@ function useTapObserver(client) {
5919
6608
  clientRef.current = client;
5920
6609
  return useMemo5(() => {
5921
6610
  const tracker = new TapTracker();
6611
+ let replayPointer = null;
6612
+ const replay = (phase, e) => {
6613
+ const c = clientRef.current;
6614
+ const n = e.nativeEvent;
6615
+ if (!c?.isRecordingTouches()) return void (replayPointer = null);
6616
+ if (phase === "down") {
6617
+ if (replayPointer !== null) return;
6618
+ replayPointer = n.identifier;
6619
+ } else if (n.identifier !== replayPointer) return;
6620
+ if (phase === "up" || phase === "cancel") replayPointer = null;
6621
+ c.recordTouch(phase, n.pageX, n.pageY);
6622
+ };
5922
6623
  const guard = (fn) => (e) => {
5923
6624
  try {
5924
6625
  fn(e);
@@ -5928,12 +6629,17 @@ function useTapObserver(client) {
5928
6629
  };
5929
6630
  return {
5930
6631
  onTouchStartCapture: guard((e) => {
6632
+ replay("down", e);
5931
6633
  const n = e.nativeEvent;
5932
6634
  if (!clientRef.current?.isTapCaptureOn()) return tracker.cancel();
5933
6635
  tracker.begin(n.pageX, n.pageY, Date.now(), Array.isArray(n.touches) ? n.touches.length : 1, n.identifier);
5934
6636
  }),
5935
- onTouchMoveCapture: guard((e) => tracker.move(e.nativeEvent.pageX, e.nativeEvent.pageY, e.nativeEvent.identifier)),
6637
+ onTouchMoveCapture: guard((e) => {
6638
+ replay("move", e);
6639
+ tracker.move(e.nativeEvent.pageX, e.nativeEvent.pageY, e.nativeEvent.identifier);
6640
+ }),
5936
6641
  onTouchEndCapture: guard((e) => {
6642
+ replay("up", e);
5937
6643
  const n = e.nativeEvent;
5938
6644
  const tap = tracker.end(n.pageX, n.pageY, Date.now(), n.identifier);
5939
6645
  const c = clientRef.current;
@@ -5941,7 +6647,10 @@ function useTapObserver(client) {
5941
6647
  const r = resolveTapTarget(e, RESOLVE);
5942
6648
  if (!r.ignored) c.recordTap({ x: tap.x, y: tap.y, t: Date.now(), element: r.element, hit: r.hit });
5943
6649
  }),
5944
- onTouchCancelCapture: guard(() => tracker.cancel())
6650
+ onTouchCancelCapture: guard((e) => {
6651
+ replay("cancel", e);
6652
+ tracker.cancel();
6653
+ })
5945
6654
  };
5946
6655
  }, []);
5947
6656
  }
@@ -6035,9 +6744,15 @@ function defaultBridge(appVersion) {
6035
6744
  },
6036
6745
  openExternal: (url) => void Linking.openURL(url).catch(() => {
6037
6746
  }),
6038
- windowSize: () => Dimensions.get("window")
6747
+ windowSize: () => Dimensions.get("window"),
6748
+ capturer: nativeCapturer()
6039
6749
  };
6040
6750
  }
6751
+ function nativeCapturer() {
6752
+ const mod = NativeModules.GuidelyReplay;
6753
+ if (!mod || typeof mod.capture !== "function") return void 0;
6754
+ return (options) => mod.capture(options);
6755
+ }
6041
6756
  function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
6042
6757
  const [client, setClient] = useState8(null);
6043
6758
  const optionsRef = useRef7(options);
@@ -6107,7 +6822,7 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
6107
6822
  const flags = useMemo6(() => ({ client: client?.flags ?? null, bootstrap }), [client, bootstrap]);
6108
6823
  const tapObserver = useTapObserver(client);
6109
6824
  return /* @__PURE__ */ jsx13(GuidelyContext.Provider, { value: client, children: /* @__PURE__ */ jsxs9(FlagsContext.Provider, { value: flags, children: [
6110
- options.autocapture === false ? children : /* @__PURE__ */ jsx13(View12, { style: styles10.root, ...tapObserver, children }),
6825
+ options.autocapture === false && !options.sessionReplay?.enabled ? children : /* @__PURE__ */ jsx13(View12, { style: styles10.root, ...tapObserver, children }),
6111
6826
  client ? /* @__PURE__ */ jsx13(GuidelyOverlay, { client, insets, launcherOffset }) : null
6112
6827
  ] }) });
6113
6828
  }
@@ -6127,6 +6842,9 @@ var NOOP_RESULTS = {
6127
6842
  getMessenger: null,
6128
6843
  isGuideCompleted: false,
6129
6844
  isClientReady: false,
6845
+ isRecording: false,
6846
+ isRecordingTouches: false,
6847
+ getSessionId: "",
6130
6848
  getScreen: "",
6131
6849
  on: () => {
6132
6850
  },
@@ -6260,8 +6978,11 @@ export {
6260
6978
  InMemoryStickyBucketStore,
6261
6979
  Messenger,
6262
6980
  RageClickDetector,
6981
+ ReplayUploader,
6982
+ RrwebEvents,
6263
6983
  SDK_NAME,
6264
6984
  SDK_VERSION,
6985
+ SessionRecorder,
6265
6986
  TapTracker,
6266
6987
  TargetRegistry,
6267
6988
  createFlagsClient3 as createFlagsClient,