@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/README.md +46 -4
- package/dist/index.cjs +750 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +277 -31
- package/dist/index.d.ts +277 -31
- package/dist/index.js +765 -44
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -37,8 +37,11 @@ __export(index_exports, {
|
|
|
37
37
|
InMemoryStickyBucketStore: () => import_flags6.InMemoryStickyBucketStore,
|
|
38
38
|
Messenger: () => Messenger,
|
|
39
39
|
RageClickDetector: () => RageClickDetector,
|
|
40
|
+
ReplayUploader: () => ReplayUploader,
|
|
41
|
+
RrwebEvents: () => RrwebEvents,
|
|
40
42
|
SDK_NAME: () => SDK_NAME,
|
|
41
43
|
SDK_VERSION: () => SDK_VERSION,
|
|
44
|
+
SessionRecorder: () => SessionRecorder,
|
|
42
45
|
TapTracker: () => TapTracker,
|
|
43
46
|
TargetRegistry: () => TargetRegistry,
|
|
44
47
|
createFlagsClient: () => import_flags6.createFlagsClient,
|
|
@@ -160,7 +163,7 @@ function FlagsProvider({ children, client: external, payload, ...options }) {
|
|
|
160
163
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FlagsContext.Provider, { value, children });
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
// ../shared/dist/chunk-
|
|
166
|
+
// ../shared/dist/chunk-XJXXVV3C.js
|
|
164
167
|
function fnv1a(input) {
|
|
165
168
|
let h = 2166136261;
|
|
166
169
|
for (let i = 0; i < input.length; i++) {
|
|
@@ -470,7 +473,7 @@ function sanitizeProperties(props) {
|
|
|
470
473
|
}
|
|
471
474
|
return out;
|
|
472
475
|
}
|
|
473
|
-
var SOURCEMAP_MAX_FILE_BYTES =
|
|
476
|
+
var SOURCEMAP_MAX_FILE_BYTES = 100 * 1024 * 1024;
|
|
474
477
|
var MAX_COMPLAINT_ATTACHMENT_BYTES = 2 * 1024 * 1024;
|
|
475
478
|
var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
476
479
|
var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
|
|
@@ -765,7 +768,7 @@ function localizeResourceCenter(rc, requested, defaultLocale = DEFAULT_LOCALE) {
|
|
|
765
768
|
};
|
|
766
769
|
}
|
|
767
770
|
|
|
768
|
-
// ../sdk-core/dist/chunk-
|
|
771
|
+
// ../sdk-core/dist/chunk-XML74K2P.js
|
|
769
772
|
var __defProp2 = Object.defineProperty;
|
|
770
773
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
771
774
|
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -1074,10 +1077,12 @@ var KEYS = {
|
|
|
1074
1077
|
/** Messenger conversations this browser can read: [{ id, token }]. */
|
|
1075
1078
|
conversations: (key) => `guidely:conversations:${key}`,
|
|
1076
1079
|
/** Announcement ids opened in the messenger's News tab. */
|
|
1077
|
-
newsSeen: (key) => `guidely:news:${key}
|
|
1080
|
+
newsSeen: (key) => `guidely:news:${key}`,
|
|
1081
|
+
/** Where the visitor dragged the messenger launcher ({ side, y }). */
|
|
1082
|
+
launcher: (key) => `guidely:launcher:${key}`
|
|
1078
1083
|
};
|
|
1079
1084
|
|
|
1080
|
-
// ../sdk-core/dist/chunk-
|
|
1085
|
+
// ../sdk-core/dist/chunk-2E2YUQAG.js
|
|
1081
1086
|
var EventQueue = class {
|
|
1082
1087
|
constructor(opts) {
|
|
1083
1088
|
__publicField2(this, "opts", opts);
|
|
@@ -1387,6 +1392,49 @@ function dismissedAfterMerge(local, server) {
|
|
|
1387
1392
|
// src/core/client.ts
|
|
1388
1393
|
var import_flags2 = require("@guidelytech/flags");
|
|
1389
1394
|
|
|
1395
|
+
// src/core/launcherPlacement.ts
|
|
1396
|
+
var LAUNCHER_PLACEMENT_KEY = "guidely:messenger.launcher";
|
|
1397
|
+
var LAUNCHER_MARGIN = 16;
|
|
1398
|
+
var LAUNCHER_DRAG_THRESHOLD = 8;
|
|
1399
|
+
var clamp2 = (v, min, max) => Math.min(max, Math.max(min, v));
|
|
1400
|
+
function sideX(f, side) {
|
|
1401
|
+
return side === "left" ? f.insets.left + LAUNCHER_MARGIN : f.width - f.insets.right - LAUNCHER_MARGIN - f.size;
|
|
1402
|
+
}
|
|
1403
|
+
function verticalRange(f) {
|
|
1404
|
+
const max = f.height - f.defaultBottom - f.size;
|
|
1405
|
+
return { min: Math.min(max, f.insets.top + LAUNCHER_MARGIN), max };
|
|
1406
|
+
}
|
|
1407
|
+
function clampToSafeArea(f, p) {
|
|
1408
|
+
const left = f.insets.left + LAUNCHER_MARGIN;
|
|
1409
|
+
const top = f.insets.top + LAUNCHER_MARGIN;
|
|
1410
|
+
return {
|
|
1411
|
+
x: clamp2(p.x, left, Math.max(left, f.width - f.insets.right - LAUNCHER_MARGIN - f.size)),
|
|
1412
|
+
y: clamp2(p.y, top, Math.max(top, f.height - f.insets.bottom - LAUNCHER_MARGIN - f.size))
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
function snapPlacement(f, p) {
|
|
1416
|
+
const { min, max } = verticalRange(f);
|
|
1417
|
+
const side = p.x + f.size / 2 < f.width / 2 ? "left" : "right";
|
|
1418
|
+
return { side, fraction: max > min ? (max - clamp2(p.y, min, max)) / (max - min) : 0 };
|
|
1419
|
+
}
|
|
1420
|
+
function placementPoint(f, placement) {
|
|
1421
|
+
const { min, max } = verticalRange(f);
|
|
1422
|
+
return { x: sideX(f, placement.side), y: max - clamp2(placement.fraction, 0, 1) * (max - min) };
|
|
1423
|
+
}
|
|
1424
|
+
function parsePlacement(raw) {
|
|
1425
|
+
if (!raw) return null;
|
|
1426
|
+
try {
|
|
1427
|
+
const v = JSON.parse(raw);
|
|
1428
|
+
if (!v || typeof v !== "object" || v.side !== "left" && v.side !== "right" || typeof v.fraction !== "number" || !Number.isFinite(v.fraction)) return null;
|
|
1429
|
+
return { side: v.side, fraction: clamp2(v.fraction, 0, 1) };
|
|
1430
|
+
} catch {
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
function serializePlacement(p) {
|
|
1435
|
+
return JSON.stringify({ side: p.side, fraction: Math.round(clamp2(p.fraction, 0, 1) * 1e4) / 1e4 });
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1390
1438
|
// src/core/autocapture.ts
|
|
1391
1439
|
var TAP_SLOP = 10;
|
|
1392
1440
|
var TAP_MAX_MS = 1e3;
|
|
@@ -2447,6 +2495,425 @@ var Messenger = class {
|
|
|
2447
2495
|
}
|
|
2448
2496
|
};
|
|
2449
2497
|
|
|
2498
|
+
// src/core/replay.ts
|
|
2499
|
+
var RrwebEvents = {
|
|
2500
|
+
IMG_ID: 8,
|
|
2501
|
+
TOUCH_START: 7,
|
|
2502
|
+
TOUCH_END: 9,
|
|
2503
|
+
CLICK: 2,
|
|
2504
|
+
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}",
|
|
2505
|
+
dataUrl(base64) {
|
|
2506
|
+
return `data:image/jpeg;base64,${base64}`;
|
|
2507
|
+
},
|
|
2508
|
+
meta(timestamp, href, width, height) {
|
|
2509
|
+
return JSON.stringify({ type: 4, data: { href, width, height }, timestamp });
|
|
2510
|
+
},
|
|
2511
|
+
fullSnapshot(timestamp, dataUrl, width, height) {
|
|
2512
|
+
const style = { type: 2, id: 5, tagName: "style", attributes: {}, childNodes: [{ type: 3, id: 6, textContent: RrwebEvents.CSS, isStyle: true }] };
|
|
2513
|
+
const img = { type: 2, id: 8, tagName: "img", attributes: { src: dataUrl, width, height, alt: "" }, childNodes: [] };
|
|
2514
|
+
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] }] };
|
|
2515
|
+
const node = { type: 0, id: 1, childNodes: [{ type: 1, id: 2, name: "html", publicId: "", systemId: "" }, html] };
|
|
2516
|
+
return JSON.stringify({ type: 2, data: { node, initialOffset: { left: 0, top: 0 } }, timestamp });
|
|
2517
|
+
},
|
|
2518
|
+
frame(timestamp, dataUrl) {
|
|
2519
|
+
return JSON.stringify({ type: 3, data: { source: 0, texts: [], attributes: [{ id: 8, attributes: { src: dataUrl } }], removes: [], adds: [] }, timestamp });
|
|
2520
|
+
},
|
|
2521
|
+
/** MouseInteraction (source 2): 7 TouchStart, 9 TouchEnd, 2 Click. */
|
|
2522
|
+
interaction(timestamp, type, x, y) {
|
|
2523
|
+
return JSON.stringify({ type: 3, data: { source: 2, type, id: 8, x: round1(x), y: round1(y) }, timestamp });
|
|
2524
|
+
},
|
|
2525
|
+
touchMove(timestamp, x, y) {
|
|
2526
|
+
return JSON.stringify({ type: 3, data: { source: 6, positions: [{ x: round1(x), y: round1(y), id: 8, timeOffset: 0 }] }, timestamp });
|
|
2527
|
+
},
|
|
2528
|
+
custom(timestamp, tag, payload) {
|
|
2529
|
+
return JSON.stringify({ type: 5, data: { tag, payload }, timestamp });
|
|
2530
|
+
}
|
|
2531
|
+
};
|
|
2532
|
+
var round1 = (v) => Math.round(v * 10) / 10;
|
|
2533
|
+
var UPLOAD_TIMEOUT_MS = 3e4;
|
|
2534
|
+
var ReplayUploader = class {
|
|
2535
|
+
constructor(opts) {
|
|
2536
|
+
__publicField(this, "opts", opts);
|
|
2537
|
+
__publicField(this, "windowId", randomId(16));
|
|
2538
|
+
__publicField(this, "buffer", null);
|
|
2539
|
+
__publicField(this, "queue", []);
|
|
2540
|
+
__publicField(this, "queuedBytes", 0);
|
|
2541
|
+
__publicField(this, "seqs", /* @__PURE__ */ new Map());
|
|
2542
|
+
__publicField(this, "inflight", null);
|
|
2543
|
+
__publicField(this, "flushTimer", null);
|
|
2544
|
+
__publicField(this, "retryTimer", null);
|
|
2545
|
+
__publicField(this, "attempt", 0);
|
|
2546
|
+
__publicField(this, "destroyed", false);
|
|
2547
|
+
__publicField(this, "offRecover");
|
|
2548
|
+
this.offRecover = opts.health?.onRecover?.(() => {
|
|
2549
|
+
this.clearRetry();
|
|
2550
|
+
this.attempt = 0;
|
|
2551
|
+
this.flush();
|
|
2552
|
+
}) ?? null;
|
|
2553
|
+
}
|
|
2554
|
+
get pendingChunks() {
|
|
2555
|
+
return this.queue.length;
|
|
2556
|
+
}
|
|
2557
|
+
get bufferedEvents() {
|
|
2558
|
+
return this.buffer?.events.length ?? 0;
|
|
2559
|
+
}
|
|
2560
|
+
push(sessionId, json) {
|
|
2561
|
+
if (this.destroyed) return;
|
|
2562
|
+
if (this.buffer && this.buffer.sessionId !== sessionId) this.seal();
|
|
2563
|
+
const chunk = this.buffer ?? (this.buffer = { sessionId, meta: this.opts.meta(), events: [], bytes: 0 });
|
|
2564
|
+
chunk.events.push(json);
|
|
2565
|
+
chunk.bytes += json.length;
|
|
2566
|
+
if (chunk.bytes > (this.opts.maxBufferBytes ?? 1e6)) this.flush();
|
|
2567
|
+
else if (!this.flushTimer) {
|
|
2568
|
+
this.flushTimer = setTimeout(() => {
|
|
2569
|
+
this.flushTimer = null;
|
|
2570
|
+
this.flush();
|
|
2571
|
+
}, this.opts.flushIntervalMs ?? 1e4);
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
/** Seals the buffer and uploads the backlog (one chunk at a time). */
|
|
2575
|
+
flush() {
|
|
2576
|
+
if (this.destroyed) return;
|
|
2577
|
+
this.seal();
|
|
2578
|
+
if (this.retryTimer || this.inflight || !this.queue.length) return;
|
|
2579
|
+
void this.sendNext();
|
|
2580
|
+
}
|
|
2581
|
+
/** Drops everything buffered or queued (consent withdrawn, server disabled replay). */
|
|
2582
|
+
discard() {
|
|
2583
|
+
this.buffer = null;
|
|
2584
|
+
this.queue = [];
|
|
2585
|
+
this.queuedBytes = 0;
|
|
2586
|
+
this.clearFlushTimer();
|
|
2587
|
+
this.clearRetry();
|
|
2588
|
+
this.attempt = 0;
|
|
2589
|
+
}
|
|
2590
|
+
/** Uploads what is left once; no retries afterwards. */
|
|
2591
|
+
destroy() {
|
|
2592
|
+
this.flush();
|
|
2593
|
+
this.destroyed = true;
|
|
2594
|
+
this.offRecover?.();
|
|
2595
|
+
this.clearFlushTimer();
|
|
2596
|
+
this.clearRetry();
|
|
2597
|
+
}
|
|
2598
|
+
clearFlushTimer() {
|
|
2599
|
+
if (this.flushTimer) clearTimeout(this.flushTimer);
|
|
2600
|
+
this.flushTimer = null;
|
|
2601
|
+
}
|
|
2602
|
+
clearRetry() {
|
|
2603
|
+
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
2604
|
+
this.retryTimer = null;
|
|
2605
|
+
}
|
|
2606
|
+
scheduleRetry(ms) {
|
|
2607
|
+
this.retryTimer = setTimeout(() => {
|
|
2608
|
+
this.retryTimer = null;
|
|
2609
|
+
this.flush();
|
|
2610
|
+
}, ms);
|
|
2611
|
+
}
|
|
2612
|
+
seal() {
|
|
2613
|
+
this.clearFlushTimer();
|
|
2614
|
+
const chunk = this.buffer;
|
|
2615
|
+
this.buffer = null;
|
|
2616
|
+
if (!chunk || !chunk.events.length) return;
|
|
2617
|
+
this.queue.push(chunk);
|
|
2618
|
+
this.queuedBytes += chunk.bytes;
|
|
2619
|
+
const max = this.opts.maxQueuedBytes ?? 5e6;
|
|
2620
|
+
while (this.queuedBytes > max && this.queue.length > 1) {
|
|
2621
|
+
const i = this.inflight && this.queue[0] === this.inflight ? 1 : 0;
|
|
2622
|
+
const [dropped] = this.queue.splice(i, 1);
|
|
2623
|
+
if (!dropped) break;
|
|
2624
|
+
this.queuedBytes -= dropped.bytes;
|
|
2625
|
+
this.opts.log?.(`replay backlog full: dropped a chunk of ${dropped.events.length} events`);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
nextSeq(chunk) {
|
|
2629
|
+
if (chunk.seq === void 0) {
|
|
2630
|
+
const seq = this.seqs.get(chunk.sessionId) ?? 0;
|
|
2631
|
+
this.seqs.set(chunk.sessionId, seq + 1);
|
|
2632
|
+
chunk.seq = seq;
|
|
2633
|
+
}
|
|
2634
|
+
return chunk.seq;
|
|
2635
|
+
}
|
|
2636
|
+
body(chunk) {
|
|
2637
|
+
const head = JSON.stringify({ sessionId: chunk.sessionId, windowId: this.windowId, seq: this.nextSeq(chunk), ...chunk.meta });
|
|
2638
|
+
return `${head.slice(0, -1)},"events":[${chunk.events.join(",")}]}`;
|
|
2639
|
+
}
|
|
2640
|
+
async sendNext() {
|
|
2641
|
+
const chunk = this.queue[0];
|
|
2642
|
+
if (!chunk) return;
|
|
2643
|
+
const wait = this.destroyed ? 0 : this.opts.health?.waitMs() ?? 0;
|
|
2644
|
+
if (wait > 0) return this.scheduleRetry(wait);
|
|
2645
|
+
this.inflight = chunk;
|
|
2646
|
+
const result = await this.upload(this.body(chunk));
|
|
2647
|
+
this.onResult(chunk, result);
|
|
2648
|
+
}
|
|
2649
|
+
async upload(json) {
|
|
2650
|
+
const f = this.opts.fetch();
|
|
2651
|
+
if (!f) return "drop";
|
|
2652
|
+
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
2653
|
+
const timer = ac ? setTimeout(() => ac.abort(), UPLOAD_TIMEOUT_MS) : null;
|
|
2654
|
+
try {
|
|
2655
|
+
let res;
|
|
2656
|
+
try {
|
|
2657
|
+
res = await f(this.opts.url(), { method: "POST", body: json, headers: { "Content-Type": "application/json" }, signal: ac?.signal });
|
|
2658
|
+
} catch {
|
|
2659
|
+
this.opts.health?.failure();
|
|
2660
|
+
return "retry";
|
|
2661
|
+
}
|
|
2662
|
+
if (res.status === 429 || res.status >= 500) this.opts.health?.failure();
|
|
2663
|
+
else this.opts.health?.success();
|
|
2664
|
+
if (res.ok) {
|
|
2665
|
+
const data = await res.json().catch(() => null);
|
|
2666
|
+
return data?.disabled ? "disabled" : "ok";
|
|
2667
|
+
}
|
|
2668
|
+
return res.status >= 400 && res.status < 500 && res.status !== 429 ? "drop" : "retry";
|
|
2669
|
+
} finally {
|
|
2670
|
+
if (timer) clearTimeout(timer);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
onResult(chunk, result) {
|
|
2674
|
+
if (this.inflight === chunk) this.inflight = null;
|
|
2675
|
+
if (result === "retry" && this.queue.includes(chunk)) {
|
|
2676
|
+
if (!this.destroyed) {
|
|
2677
|
+
const max = this.opts.maxAttempts ?? 5;
|
|
2678
|
+
this.attempt = Math.min(this.attempt + 1, max);
|
|
2679
|
+
const delay = this.attempt >= max ? this.opts.slowRetryMs ?? 6e4 : backoffMs(this.attempt - 1, 1e3, 8e3);
|
|
2680
|
+
return this.scheduleRetry(Math.max(delay, this.opts.health?.waitMs() ?? 0));
|
|
2681
|
+
}
|
|
2682
|
+
this.opts.log?.("replay upload failed after the recorder stopped; dropping the chunk");
|
|
2683
|
+
}
|
|
2684
|
+
this.attempt = 0;
|
|
2685
|
+
this.remove(chunk);
|
|
2686
|
+
if (result === "disabled") {
|
|
2687
|
+
for (const c of this.queue.slice()) if (c.sessionId === chunk.sessionId) this.remove(c);
|
|
2688
|
+
if (this.buffer?.sessionId === chunk.sessionId) this.buffer = null;
|
|
2689
|
+
this.opts.onDisabled(chunk.sessionId);
|
|
2690
|
+
}
|
|
2691
|
+
if (this.queue.length && !this.retryTimer && !this.inflight) void this.sendNext();
|
|
2692
|
+
}
|
|
2693
|
+
remove(chunk) {
|
|
2694
|
+
const i = this.queue.indexOf(chunk);
|
|
2695
|
+
if (i >= 0) {
|
|
2696
|
+
this.queue.splice(i, 1);
|
|
2697
|
+
this.queuedBytes -= chunk.bytes;
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
};
|
|
2701
|
+
var TOUCH_SETTLE_MS = 200;
|
|
2702
|
+
var IDLE_FRAMES = 3;
|
|
2703
|
+
var MAX_IDLE_INTERVAL_MS = 5e3;
|
|
2704
|
+
var SessionRecorder = class {
|
|
2705
|
+
constructor(opts) {
|
|
2706
|
+
__publicField(this, "opts", opts);
|
|
2707
|
+
__publicField(this, "intervalMs");
|
|
2708
|
+
__publicField(this, "now");
|
|
2709
|
+
__publicField(this, "sid", null);
|
|
2710
|
+
__publicField(this, "foreground", true);
|
|
2711
|
+
__publicField(this, "generation", 0);
|
|
2712
|
+
__publicField(this, "timer", null);
|
|
2713
|
+
__publicField(this, "timerDue", 0);
|
|
2714
|
+
__publicField(this, "capturing", false);
|
|
2715
|
+
__publicField(this, "needKeyframe", true);
|
|
2716
|
+
__publicField(this, "hasKeyframe", false);
|
|
2717
|
+
__publicField(this, "lastFrame", null);
|
|
2718
|
+
__publicField(this, "unchanged", 0);
|
|
2719
|
+
__publicField(this, "captureSoon", false);
|
|
2720
|
+
__publicField(this, "pendingCustom", []);
|
|
2721
|
+
__publicField(this, "touchDown", false);
|
|
2722
|
+
__publicField(this, "downX", 0);
|
|
2723
|
+
__publicField(this, "downY", 0);
|
|
2724
|
+
__publicField(this, "downAt", 0);
|
|
2725
|
+
__publicField(this, "maxDistance", 0);
|
|
2726
|
+
__publicField(this, "lastMoveAt", 0);
|
|
2727
|
+
__publicField(this, "originX", 0);
|
|
2728
|
+
__publicField(this, "originY", 0);
|
|
2729
|
+
/** Viewport of the last frame (points), for chunk metadata. */
|
|
2730
|
+
__publicField(this, "viewportWidth", 0);
|
|
2731
|
+
__publicField(this, "viewportHeight", 0);
|
|
2732
|
+
this.intervalMs = Math.max(500, opts.intervalMs);
|
|
2733
|
+
this.now = opts.now ?? Date.now;
|
|
2734
|
+
}
|
|
2735
|
+
get sessionId() {
|
|
2736
|
+
return this.sid;
|
|
2737
|
+
}
|
|
2738
|
+
get isRecording() {
|
|
2739
|
+
return this.sid !== null;
|
|
2740
|
+
}
|
|
2741
|
+
get currentIntervalMs() {
|
|
2742
|
+
return this.unchanged >= IDLE_FRAMES ? Math.max(this.intervalMs, Math.min(4 * this.intervalMs, MAX_IDLE_INTERVAL_MS)) : this.intervalMs;
|
|
2743
|
+
}
|
|
2744
|
+
/** Starts (or keeps) recording `sessionId`; another session stops the current one first (uploading it). */
|
|
2745
|
+
start(sessionId) {
|
|
2746
|
+
if (this.sid === sessionId) return;
|
|
2747
|
+
if (this.sid !== null) this.stop(false);
|
|
2748
|
+
this.sid = sessionId;
|
|
2749
|
+
this.generation++;
|
|
2750
|
+
this.needKeyframe = true;
|
|
2751
|
+
this.hasKeyframe = false;
|
|
2752
|
+
this.lastFrame = null;
|
|
2753
|
+
this.unchanged = 0;
|
|
2754
|
+
this.captureSoon = false;
|
|
2755
|
+
this.pendingCustom = [];
|
|
2756
|
+
this.touchDown = false;
|
|
2757
|
+
if (this.foreground) this.schedule(0);
|
|
2758
|
+
}
|
|
2759
|
+
/** Stops recording; buffered events are uploaded unless `discard`. */
|
|
2760
|
+
stop(discard) {
|
|
2761
|
+
if (this.sid === null) return;
|
|
2762
|
+
this.sid = null;
|
|
2763
|
+
this.generation++;
|
|
2764
|
+
this.clearTimer();
|
|
2765
|
+
this.capturing = false;
|
|
2766
|
+
this.pendingCustom = [];
|
|
2767
|
+
this.touchDown = false;
|
|
2768
|
+
if (discard) this.opts.uploader.discard();
|
|
2769
|
+
else this.opts.uploader.flush();
|
|
2770
|
+
}
|
|
2771
|
+
/** App backgrounded: no captures; uploads what is buffered. */
|
|
2772
|
+
pause() {
|
|
2773
|
+
this.foreground = false;
|
|
2774
|
+
this.clearTimer();
|
|
2775
|
+
this.generation++;
|
|
2776
|
+
this.capturing = false;
|
|
2777
|
+
this.touchDown = false;
|
|
2778
|
+
this.opts.uploader.flush();
|
|
2779
|
+
}
|
|
2780
|
+
/** App foregrounded: resume with a keyframe. */
|
|
2781
|
+
resume() {
|
|
2782
|
+
if (this.foreground) return;
|
|
2783
|
+
this.foreground = true;
|
|
2784
|
+
this.needKeyframe = true;
|
|
2785
|
+
this.unchanged = 0;
|
|
2786
|
+
if (this.sid !== null) this.schedule(0);
|
|
2787
|
+
}
|
|
2788
|
+
/** A new screen: the next frame is a keyframe with the new href. */
|
|
2789
|
+
onScreen() {
|
|
2790
|
+
if (this.sid === null) return;
|
|
2791
|
+
this.needKeyframe = true;
|
|
2792
|
+
this.activity();
|
|
2793
|
+
}
|
|
2794
|
+
activity() {
|
|
2795
|
+
const wasIdle = this.unchanged >= IDLE_FRAMES;
|
|
2796
|
+
this.unchanged = 0;
|
|
2797
|
+
if (wasIdle && this.sid !== null && this.foreground && !this.capturing && !this.touchDown) this.schedule(this.intervalMs, true);
|
|
2798
|
+
}
|
|
2799
|
+
/** Adds the `guidely` marker for an analytics event. */
|
|
2800
|
+
addGuidelyEvent(name, customName, guideId) {
|
|
2801
|
+
const id = this.sid;
|
|
2802
|
+
if (id === null) return;
|
|
2803
|
+
const payload = { name };
|
|
2804
|
+
if (customName) payload.customName = customName;
|
|
2805
|
+
if (guideId) payload.guideId = guideId;
|
|
2806
|
+
this.activity();
|
|
2807
|
+
const now = this.now();
|
|
2808
|
+
if (!this.hasKeyframe) {
|
|
2809
|
+
if (this.pendingCustom.length < 50) this.pendingCustom.push([now, payload]);
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
this.opts.uploader.push(id, RrwebEvents.custom(now, "guidely", payload));
|
|
2813
|
+
}
|
|
2814
|
+
/** A primary-pointer touch at (x, y) points from the React root view. */
|
|
2815
|
+
touch(phase, rootX, rootY) {
|
|
2816
|
+
const id = this.sid;
|
|
2817
|
+
if (id === null || !this.foreground) return;
|
|
2818
|
+
const x = rootX + this.originX;
|
|
2819
|
+
const y = rootY + this.originY;
|
|
2820
|
+
const now = this.now();
|
|
2821
|
+
const emit = this.hasKeyframe;
|
|
2822
|
+
const push = (json) => emit && this.opts.uploader.push(id, json);
|
|
2823
|
+
switch (phase) {
|
|
2824
|
+
case "down":
|
|
2825
|
+
this.touchDown = true;
|
|
2826
|
+
this.unchanged = 0;
|
|
2827
|
+
this.downX = x;
|
|
2828
|
+
this.downY = y;
|
|
2829
|
+
this.downAt = now;
|
|
2830
|
+
this.maxDistance = 0;
|
|
2831
|
+
this.lastMoveAt = now;
|
|
2832
|
+
push(RrwebEvents.interaction(now, RrwebEvents.TOUCH_START, x, y));
|
|
2833
|
+
return;
|
|
2834
|
+
case "move":
|
|
2835
|
+
if (!this.touchDown) return;
|
|
2836
|
+
this.maxDistance = Math.max(this.maxDistance, Math.hypot(x - this.downX, y - this.downY));
|
|
2837
|
+
if (now - this.lastMoveAt < 50) return;
|
|
2838
|
+
this.lastMoveAt = now;
|
|
2839
|
+
push(RrwebEvents.touchMove(now, x, y));
|
|
2840
|
+
return;
|
|
2841
|
+
default:
|
|
2842
|
+
if (!this.touchDown) return;
|
|
2843
|
+
this.touchDown = false;
|
|
2844
|
+
this.unchanged = 0;
|
|
2845
|
+
this.maxDistance = Math.max(this.maxDistance, Math.hypot(x - this.downX, y - this.downY));
|
|
2846
|
+
push(RrwebEvents.interaction(now, RrwebEvents.TOUCH_END, x, y));
|
|
2847
|
+
if (phase === "up" && this.maxDistance <= 10 && now - this.downAt < 1e3) push(RrwebEvents.interaction(now, RrwebEvents.CLICK, x, y));
|
|
2848
|
+
if (this.capturing) this.captureSoon = true;
|
|
2849
|
+
else this.schedule(TOUCH_SETTLE_MS, true);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
clearTimer() {
|
|
2853
|
+
if (this.timer) clearTimeout(this.timer);
|
|
2854
|
+
this.timer = null;
|
|
2855
|
+
}
|
|
2856
|
+
/** Schedules the next capture `delay` ms from now, or keeps an earlier one when `ifSooner`. */
|
|
2857
|
+
schedule(delay, ifSooner = false) {
|
|
2858
|
+
const due = this.now() + delay;
|
|
2859
|
+
if (ifSooner && this.timer && this.timerDue <= due) return;
|
|
2860
|
+
this.clearTimer();
|
|
2861
|
+
this.timerDue = due;
|
|
2862
|
+
const gen = this.generation;
|
|
2863
|
+
this.timer = setTimeout(() => {
|
|
2864
|
+
this.timer = null;
|
|
2865
|
+
if (gen === this.generation) void this.tick();
|
|
2866
|
+
}, delay);
|
|
2867
|
+
}
|
|
2868
|
+
async tick() {
|
|
2869
|
+
if (this.sid === null || !this.foreground || this.capturing) return;
|
|
2870
|
+
if (this.touchDown) {
|
|
2871
|
+
this.schedule(this.intervalMs);
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
this.capturing = true;
|
|
2875
|
+
this.captureSoon = false;
|
|
2876
|
+
const gen = this.generation;
|
|
2877
|
+
let frame = null;
|
|
2878
|
+
try {
|
|
2879
|
+
frame = await this.opts.capturer(await this.opts.captureOptions());
|
|
2880
|
+
} catch (err) {
|
|
2881
|
+
this.opts.onError?.(err);
|
|
2882
|
+
}
|
|
2883
|
+
if (gen !== this.generation) return;
|
|
2884
|
+
this.capturing = false;
|
|
2885
|
+
if (frame) this.onFrame(frame, this.now());
|
|
2886
|
+
if (this.sid !== null && this.foreground) this.schedule(this.captureSoon ? TOUCH_SETTLE_MS : this.currentIntervalMs);
|
|
2887
|
+
}
|
|
2888
|
+
onFrame(frame, at) {
|
|
2889
|
+
const id = this.sid;
|
|
2890
|
+
if (id === null || !(frame.width > 0) || !(frame.height > 0) || !frame.base64) return;
|
|
2891
|
+
const sizeChanged = frame.width !== this.viewportWidth || frame.height !== this.viewportHeight;
|
|
2892
|
+
const keyframe = this.needKeyframe || !this.hasKeyframe || sizeChanged;
|
|
2893
|
+
if (!keyframe && this.lastFrame === frame.base64) {
|
|
2894
|
+
this.unchanged++;
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
this.unchanged = 0;
|
|
2898
|
+
this.lastFrame = frame.base64;
|
|
2899
|
+
this.originX = frame.originX ?? 0;
|
|
2900
|
+
this.originY = frame.originY ?? 0;
|
|
2901
|
+
this.viewportWidth = frame.width;
|
|
2902
|
+
this.viewportHeight = frame.height;
|
|
2903
|
+
const url = RrwebEvents.dataUrl(frame.base64);
|
|
2904
|
+
const up = this.opts.uploader;
|
|
2905
|
+
if (!keyframe) return up.push(id, RrwebEvents.frame(at, url));
|
|
2906
|
+
this.needKeyframe = false;
|
|
2907
|
+
up.push(id, RrwebEvents.meta(at, this.opts.href(), frame.width, frame.height));
|
|
2908
|
+
up.push(id, RrwebEvents.fullSnapshot(at, url, frame.width, frame.height));
|
|
2909
|
+
if (!this.hasKeyframe) {
|
|
2910
|
+
this.hasKeyframe = true;
|
|
2911
|
+
for (const [ts, payload] of this.pendingCustom) up.push(id, RrwebEvents.custom(Math.max(ts, at), "guidely", payload));
|
|
2912
|
+
this.pendingCustom = [];
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2916
|
+
|
|
2450
2917
|
// src/core/storage.ts
|
|
2451
2918
|
var PersistentStore = class {
|
|
2452
2919
|
constructor(backing, onError = () => {
|
|
@@ -2500,7 +2967,7 @@ var PersistentStore = class {
|
|
|
2500
2967
|
|
|
2501
2968
|
// src/core/client.ts
|
|
2502
2969
|
var SDK_NAME = "@guidelytech/react-native";
|
|
2503
|
-
var SDK_VERSION = "0.
|
|
2970
|
+
var SDK_VERSION = "0.13.0";
|
|
2504
2971
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
2505
2972
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2506
2973
|
var CACHED_START_MS = 1500;
|
|
@@ -2561,6 +3028,10 @@ var GuidelyClient = class {
|
|
|
2561
3028
|
__publicField(this, "lastAudiences", /* @__PURE__ */ new Set());
|
|
2562
3029
|
__publicField(this, "backgroundedAt", null);
|
|
2563
3030
|
__publicField(this, "messenger", null);
|
|
3031
|
+
__publicField(this, "replayUploader", null);
|
|
3032
|
+
__publicField(this, "recorder", null);
|
|
3033
|
+
/** Replay decision for the current session (sampled once; forced by start/stopSessionRecording). */
|
|
3034
|
+
__publicField(this, "replayDecision", null);
|
|
2564
3035
|
__publicField(this, "ui", { steps: [], checklists: [], survey: null, resourceCenterOpen: false, theme: {}, messenger: false });
|
|
2565
3036
|
__publicField(this, "uiListeners", /* @__PURE__ */ new Set());
|
|
2566
3037
|
/** Feature flags for this environment: `guidely.flags.isEnabled("new-checkout")`. */
|
|
@@ -2666,7 +3137,7 @@ var GuidelyClient = class {
|
|
|
2666
3137
|
async bootstrap() {
|
|
2667
3138
|
const key = this.options.projectId;
|
|
2668
3139
|
try {
|
|
2669
|
-
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)]);
|
|
3140
|
+
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]);
|
|
2670
3141
|
if (this.destroyed) return;
|
|
2671
3142
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
2672
3143
|
this.hadIdentity = !!this.anonymousId;
|
|
@@ -2712,6 +3183,7 @@ var GuidelyClient = class {
|
|
|
2712
3183
|
if (this.destroyed) return;
|
|
2713
3184
|
this.isReady = true;
|
|
2714
3185
|
if (this.config) this.emitter.emit("ready", this.config);
|
|
3186
|
+
this.syncReplay();
|
|
2715
3187
|
this.notify();
|
|
2716
3188
|
this.evaluate("ready");
|
|
2717
3189
|
} catch (err) {
|
|
@@ -2738,6 +3210,7 @@ var GuidelyClient = class {
|
|
|
2738
3210
|
this.syncMessenger();
|
|
2739
3211
|
if (this.hydrated) this.checkLaunch();
|
|
2740
3212
|
this.emitter.emit("config", this.config);
|
|
3213
|
+
this.syncReplay();
|
|
2741
3214
|
this.log("config loaded", this.config.configVersion, `${this.config.guides.length} guides`);
|
|
2742
3215
|
return true;
|
|
2743
3216
|
}
|
|
@@ -2790,6 +3263,7 @@ var GuidelyClient = class {
|
|
|
2790
3263
|
void this.outbox?.flush();
|
|
2791
3264
|
void this.store.flush();
|
|
2792
3265
|
void this.flags.flush();
|
|
3266
|
+
this.recorder?.pause();
|
|
2793
3267
|
this.messenger?.setForeground(false);
|
|
2794
3268
|
} else if (state === "active" && this.backgroundedAt !== null) {
|
|
2795
3269
|
this.messenger?.setForeground(true);
|
|
@@ -2799,7 +3273,10 @@ var GuidelyClient = class {
|
|
|
2799
3273
|
this.session = newSession();
|
|
2800
3274
|
this.persistSession();
|
|
2801
3275
|
this.firedDelays.clear();
|
|
3276
|
+
this.recorder?.stop(false);
|
|
2802
3277
|
}
|
|
3278
|
+
this.recorder?.resume();
|
|
3279
|
+
this.syncReplay();
|
|
2803
3280
|
this.cameToForeground();
|
|
2804
3281
|
void this.flags.refresh();
|
|
2805
3282
|
if (Date.now() - this.configFetchedAt > this.options.configRefreshMs && this.api.health.canSend()) void this.refresh();
|
|
@@ -2822,6 +3299,9 @@ var GuidelyClient = class {
|
|
|
2822
3299
|
this.queued = [];
|
|
2823
3300
|
this.messenger?.destroy();
|
|
2824
3301
|
this.messenger = null;
|
|
3302
|
+
this.recorder?.stop(!this.analyticsEnabled());
|
|
3303
|
+
this.recorder = null;
|
|
3304
|
+
this.replayUploader?.destroy();
|
|
2825
3305
|
this.visibilityWatchers.forEach((stop) => stop());
|
|
2826
3306
|
this.visibilityWatchers.clear();
|
|
2827
3307
|
this.timers.forEach((t) => clearTimeout(t));
|
|
@@ -2936,6 +3416,7 @@ var GuidelyClient = class {
|
|
|
2936
3416
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
2937
3417
|
this.state = emptyState();
|
|
2938
3418
|
this.persistState();
|
|
3419
|
+
this.recorder?.stop(false);
|
|
2939
3420
|
this.session = newSession();
|
|
2940
3421
|
this.persistSession();
|
|
2941
3422
|
this.lastAudiences = /* @__PURE__ */ new Set();
|
|
@@ -2943,6 +3424,7 @@ var GuidelyClient = class {
|
|
|
2943
3424
|
this.firstVisit = true;
|
|
2944
3425
|
this.exposures.clear();
|
|
2945
3426
|
this.syncFlagAttributes();
|
|
3427
|
+
this.syncReplay();
|
|
2946
3428
|
this.emitter.emit("identify", { userId: null, anonymousId: this.anonymousId });
|
|
2947
3429
|
this.notify();
|
|
2948
3430
|
if (this.isReady) this.evaluate("identify");
|
|
@@ -2957,6 +3439,7 @@ var GuidelyClient = class {
|
|
|
2957
3439
|
this.whenHydrated(() => {
|
|
2958
3440
|
if (consent === "denied") this.queue?.clear();
|
|
2959
3441
|
else if (consent === "granted") void this.queue?.flush();
|
|
3442
|
+
this.syncReplay();
|
|
2960
3443
|
});
|
|
2961
3444
|
}
|
|
2962
3445
|
analyticsEnabled() {
|
|
@@ -2985,6 +3468,7 @@ var GuidelyClient = class {
|
|
|
2985
3468
|
const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
|
|
2986
3469
|
if (locale) event.properties = { ...event.properties ?? {}, locale };
|
|
2987
3470
|
this.queue.enqueue(event);
|
|
3471
|
+
this.recorder?.addGuidelyEvent(name, event.customName, event.guideId);
|
|
2988
3472
|
this.emitter.emit("track", event);
|
|
2989
3473
|
return event;
|
|
2990
3474
|
}
|
|
@@ -3022,12 +3506,13 @@ var GuidelyClient = class {
|
|
|
3022
3506
|
}
|
|
3023
3507
|
this.screenName = name;
|
|
3024
3508
|
this.targets.setScreen(name);
|
|
3509
|
+
if (changed) this.recorder?.onScreen();
|
|
3025
3510
|
if (changed) this.syncFlagAttributes();
|
|
3026
3511
|
const url = this.currentUrl();
|
|
3027
3512
|
if (changed) {
|
|
3028
3513
|
this.screenStartedAt = Date.now();
|
|
3029
3514
|
this.screenLeft = this.backgroundedAt !== null;
|
|
3030
|
-
if (this.pageviewsOn()) this.enqueue("page_view", { properties: { ...properties, path: url, screen: name,
|
|
3515
|
+
if (this.pageviewsOn()) this.enqueue("page_view", { properties: { ...properties, path: url, screen: name, ...this.deviceProperties() } });
|
|
3031
3516
|
}
|
|
3032
3517
|
this.completeChecklistTasksBy("url_visited", url);
|
|
3033
3518
|
this.active?.controller.resume();
|
|
@@ -3056,6 +3541,112 @@ var GuidelyClient = class {
|
|
|
3056
3541
|
if (this.isReady) this.evaluate("click", { targetId });
|
|
3057
3542
|
}
|
|
3058
3543
|
// ---------------------------------------------------------------------------
|
|
3544
|
+
// Session replay
|
|
3545
|
+
// ---------------------------------------------------------------------------
|
|
3546
|
+
replaySettings() {
|
|
3547
|
+
const r = this.config?.settings?.replay;
|
|
3548
|
+
if (!r?.enabled || !this.options.sessionReplay?.enabled || !this.bridge.capturer) return null;
|
|
3549
|
+
return r;
|
|
3550
|
+
}
|
|
3551
|
+
/** Reconciles recording with config, consent, readiness, the session and its sample decision. Idempotent. */
|
|
3552
|
+
syncReplay() {
|
|
3553
|
+
if (this.destroyed || !this.hydrated) return;
|
|
3554
|
+
const r = this.isReady && this.analyticsEnabled() ? this.replaySettings() : null;
|
|
3555
|
+
const id = this.session.id;
|
|
3556
|
+
if (r && this.replayDecision?.sessionId !== id) {
|
|
3557
|
+
this.replayDecision = { sessionId: id, record: rolloutBucket(id, "replay") < (r.sampleRate ?? 100) };
|
|
3558
|
+
this.log(`replay ${this.replayDecision.record ? "sampled" : "not sampled"} for session ${id}`);
|
|
3559
|
+
}
|
|
3560
|
+
if (!r || !this.replayDecision?.record) {
|
|
3561
|
+
if (this.recorder?.isRecording) this.recorder.stop(!this.analyticsEnabled());
|
|
3562
|
+
return;
|
|
3563
|
+
}
|
|
3564
|
+
const rec = this.recorder ?? this.createRecorder();
|
|
3565
|
+
if (rec && rec.sessionId !== id) {
|
|
3566
|
+
this.log(`recording session ${id}`);
|
|
3567
|
+
rec.start(id);
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
createRecorder() {
|
|
3571
|
+
const capturer = this.bridge.capturer;
|
|
3572
|
+
if (!capturer) return null;
|
|
3573
|
+
const uploader = this.replayUploader ?? (this.replayUploader = new ReplayUploader({
|
|
3574
|
+
url: () => this.api.url("/replay"),
|
|
3575
|
+
fetch: () => this.options.fetch ?? (typeof fetch === "function" ? fetch : null),
|
|
3576
|
+
health: this.api.health,
|
|
3577
|
+
meta: () => {
|
|
3578
|
+
const rec2 = this.recorder;
|
|
3579
|
+
return {
|
|
3580
|
+
anonymousId: this.anonymousId,
|
|
3581
|
+
userId: this.user?.id || null,
|
|
3582
|
+
...this.collectUrls() && this.screenName ? { url: this.currentUrl() } : {},
|
|
3583
|
+
device: { type: this.bridge.device.deviceType, os: this.bridge.device.platform },
|
|
3584
|
+
...rec2 && rec2.viewportWidth > 0 && rec2.viewportHeight > 0 ? { viewport: { width: rec2.viewportWidth, height: rec2.viewportHeight } } : {},
|
|
3585
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION }
|
|
3586
|
+
};
|
|
3587
|
+
},
|
|
3588
|
+
onDisabled: (sid) => {
|
|
3589
|
+
if (sid !== this.session.id) return;
|
|
3590
|
+
this.log(`replay disabled by the server for session ${sid}`);
|
|
3591
|
+
this.replayDecision = { sessionId: sid, record: false };
|
|
3592
|
+
this.recorder?.stop(true);
|
|
3593
|
+
},
|
|
3594
|
+
log: (m) => this.log(m)
|
|
3595
|
+
}));
|
|
3596
|
+
const opts = this.options.sessionReplay ?? {};
|
|
3597
|
+
const rec = new SessionRecorder({
|
|
3598
|
+
capturer,
|
|
3599
|
+
uploader,
|
|
3600
|
+
intervalMs: opts.captureIntervalMs ?? 1e3,
|
|
3601
|
+
captureOptions: () => this.replayCaptureOptions(),
|
|
3602
|
+
href: () => this.currentUrl(),
|
|
3603
|
+
onError: (err) => this.reportError(err)
|
|
3604
|
+
});
|
|
3605
|
+
if (this.backgroundedAt !== null) rec.pause();
|
|
3606
|
+
this.recorder = rec;
|
|
3607
|
+
return rec;
|
|
3608
|
+
}
|
|
3609
|
+
async replayCaptureOptions() {
|
|
3610
|
+
const r = this.config?.settings?.replay;
|
|
3611
|
+
const opts = this.options.sessionReplay ?? {};
|
|
3612
|
+
const ids = new Set((r?.blockSelectors ?? []).map((sel) => guidelyIdFromSelector(sel)).filter((id) => !!id));
|
|
3613
|
+
const rects = await Promise.all(Array.from(ids, (id) => this.targets.measure(id)));
|
|
3614
|
+
return { maskAllText: !!opts.maskAllText || !!r?.maskAllText, maskAllImages: !!opts.maskAllImages, blockRects: rects.filter((rect) => !!rect) };
|
|
3615
|
+
}
|
|
3616
|
+
/**
|
|
3617
|
+
* Records this session even when it was not sampled (still needs `sessionReplay.enabled`, replay
|
|
3618
|
+
* enabled for the project, analytics consent and the native replay module). Sticks until the session ends.
|
|
3619
|
+
*/
|
|
3620
|
+
startSessionRecording() {
|
|
3621
|
+
this.whenHydrated(() => {
|
|
3622
|
+
this.replayDecision = { sessionId: this.session.id, record: true };
|
|
3623
|
+
this.syncReplay();
|
|
3624
|
+
});
|
|
3625
|
+
}
|
|
3626
|
+
/** Stops recording for the rest of this session; what was recorded is uploaded. */
|
|
3627
|
+
stopSessionRecording() {
|
|
3628
|
+
this.whenHydrated(() => {
|
|
3629
|
+
this.replayDecision = { sessionId: this.session.id, record: false };
|
|
3630
|
+
this.recorder?.stop(false);
|
|
3631
|
+
});
|
|
3632
|
+
}
|
|
3633
|
+
/** True while this session is being recorded. */
|
|
3634
|
+
isRecording() {
|
|
3635
|
+
return !!this.recorder?.isRecording;
|
|
3636
|
+
}
|
|
3637
|
+
/** The current analytics session id (also the replay id). */
|
|
3638
|
+
getSessionId() {
|
|
3639
|
+
return this.session.id;
|
|
3640
|
+
}
|
|
3641
|
+
/** True when touches should be passed to {@link recordTouch}. */
|
|
3642
|
+
isRecordingTouches() {
|
|
3643
|
+
return !!this.recorder?.isRecording;
|
|
3644
|
+
}
|
|
3645
|
+
/** A primary-pointer touch on the app, in window points (the provider's touch observer calls this). */
|
|
3646
|
+
recordTouch(phase, x, y) {
|
|
3647
|
+
this.recorder?.touch(phase, x, y);
|
|
3648
|
+
}
|
|
3649
|
+
// ---------------------------------------------------------------------------
|
|
3059
3650
|
// Product analytics (autocapture)
|
|
3060
3651
|
// ---------------------------------------------------------------------------
|
|
3061
3652
|
captureAllowed() {
|
|
@@ -3102,7 +3693,7 @@ var GuidelyClient = class {
|
|
|
3102
3693
|
}
|
|
3103
3694
|
if (this.destroyed) return;
|
|
3104
3695
|
const platform = this.bridge.device.platform;
|
|
3105
|
-
const maskAll = !!this.config?.settings?.replay?.maskAllText;
|
|
3696
|
+
const maskAll = !!this.config?.settings?.replay?.maskAllText || !!this.options.sessionReplay?.maskAllText;
|
|
3106
3697
|
const eventUrl = this.collectUrls() && url ? url : void 0;
|
|
3107
3698
|
if (on.clicks && el) this.enqueue("click", { url: eventUrl, properties: { ...tapProperties(el, tap.x, tap.y, platform, maskAll) } });
|
|
3108
3699
|
const count = this.rage.add(tap.x, tap.y, t);
|
|
@@ -3192,7 +3783,7 @@ var GuidelyClient = class {
|
|
|
3192
3783
|
if (this.screenName && this.screenLeft) {
|
|
3193
3784
|
this.screenStartedAt = Date.now();
|
|
3194
3785
|
this.screenLeft = false;
|
|
3195
|
-
if (this.pageviewsOn() && this.autocaptureOn("pageLeave")) this.enqueue("page_view", { properties: { path: this.currentUrl(), screen: this.screenName,
|
|
3786
|
+
if (this.pageviewsOn() && this.autocaptureOn("pageLeave")) this.enqueue("page_view", { properties: { path: this.currentUrl(), screen: this.screenName, ...this.deviceProperties(), resumed: true } });
|
|
3196
3787
|
}
|
|
3197
3788
|
} catch (err) {
|
|
3198
3789
|
this.reportError(err);
|
|
@@ -3954,6 +4545,14 @@ var GuidelyClient = class {
|
|
|
3954
4545
|
getMessenger() {
|
|
3955
4546
|
return this.messenger;
|
|
3956
4547
|
}
|
|
4548
|
+
/** Where the visitor dragged the messenger bubble on this device, or null to use the configured spot. */
|
|
4549
|
+
getMessengerLauncherPlacement() {
|
|
4550
|
+
return parsePlacement(this.store.get(LAUNCHER_PLACEMENT_KEY));
|
|
4551
|
+
}
|
|
4552
|
+
/** Remembers the bubble's spot on this device (in memory only when no `storage` was passed). */
|
|
4553
|
+
setMessengerLauncherPlacement(placement) {
|
|
4554
|
+
this.store.set(LAUNCHER_PLACEMENT_KEY, serializePlacement(placement));
|
|
4555
|
+
}
|
|
3957
4556
|
/** Opens the Scout messenger, optionally on a tab. No-op when Scout is off for this platform. */
|
|
3958
4557
|
openMessenger(tab) {
|
|
3959
4558
|
this.whenHydrated(() => this.messenger?.open(tab));
|
|
@@ -3984,6 +4583,11 @@ var GuidelyClient = class {
|
|
|
3984
4583
|
onFlagsChange(cb) {
|
|
3985
4584
|
return this.flags.subscribe(cb);
|
|
3986
4585
|
}
|
|
4586
|
+
/** Device facts on every screen view, read by the Pages devices / browsers breakdowns. */
|
|
4587
|
+
deviceProperties() {
|
|
4588
|
+
const d = this.bridge.device;
|
|
4589
|
+
return { platform: d.platform, deviceType: d.deviceType, os: d.platform };
|
|
4590
|
+
}
|
|
3987
4591
|
/** Attributes flags are evaluated with: identity, traits, account, audiences, screen and device. */
|
|
3988
4592
|
flagContext() {
|
|
3989
4593
|
const d = this.bridge.device;
|
|
@@ -4812,14 +5416,89 @@ function ago(iso) {
|
|
|
4812
5416
|
if (s < 86400) return `${Math.floor(s / 3600)}h`;
|
|
4813
5417
|
return `${Math.floor(s / 86400)}d`;
|
|
4814
5418
|
}
|
|
4815
|
-
|
|
5419
|
+
var LAUNCHER_SIZE = 54;
|
|
5420
|
+
function useReduceMotion() {
|
|
5421
|
+
const [on, setOn] = (0, import_react3.useState)(false);
|
|
5422
|
+
(0, import_react3.useEffect)(() => {
|
|
5423
|
+
let live = true;
|
|
5424
|
+
import_react_native5.AccessibilityInfo.isReduceMotionEnabled?.().then((v) => live && setOn(v)).catch(() => {
|
|
5425
|
+
});
|
|
5426
|
+
const sub = import_react_native5.AccessibilityInfo.addEventListener?.("reduceMotionChanged", setOn);
|
|
5427
|
+
return () => {
|
|
5428
|
+
live = false;
|
|
5429
|
+
sub?.remove();
|
|
5430
|
+
};
|
|
5431
|
+
}, []);
|
|
5432
|
+
return on;
|
|
5433
|
+
}
|
|
5434
|
+
function MessengerLauncher({ store, client, palette: p, style, frame, defaultSide }) {
|
|
4816
5435
|
const snap = useMessenger(store);
|
|
5436
|
+
const reduceMotion = useReduceMotion();
|
|
5437
|
+
const [placement, setPlacement] = (0, import_react3.useState)(() => client.getMessengerLauncherPlacement());
|
|
5438
|
+
const pan = (0, import_react3.useRef)(new import_react_native5.Animated.ValueXY()).current;
|
|
5439
|
+
const offset = (0, import_react3.useRef)({ x: 0, y: 0 });
|
|
5440
|
+
const dragging = (0, import_react3.useRef)(false);
|
|
5441
|
+
const live = (0, import_react3.useRef)({ frame, defaultSide, reduceMotion });
|
|
5442
|
+
live.current = { frame, defaultSide, reduceMotion };
|
|
5443
|
+
const { width, height, size, defaultBottom } = frame;
|
|
5444
|
+
const { top, bottom, left, right } = frame.insets;
|
|
5445
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
5446
|
+
if (dragging.current) return;
|
|
5447
|
+
const f = live.current.frame;
|
|
5448
|
+
const anchor = placementPoint(f, { side: defaultSide, fraction: 0 });
|
|
5449
|
+
const at = placement ? placementPoint(f, placement) : anchor;
|
|
5450
|
+
offset.current = { x: at.x - anchor.x, y: at.y - anchor.y };
|
|
5451
|
+
pan.setValue(offset.current);
|
|
5452
|
+
}, [pan, placement, defaultSide, width, height, size, defaultBottom, top, bottom, left, right]);
|
|
5453
|
+
const responder = (0, import_react3.useMemo)(() => {
|
|
5454
|
+
const moved = (g) => Math.hypot(g.dx, g.dy) > LAUNCHER_DRAG_THRESHOLD;
|
|
5455
|
+
const anchor = () => placementPoint(live.current.frame, { side: live.current.defaultSide, fraction: 0 });
|
|
5456
|
+
let start = { x: 0, y: 0 };
|
|
5457
|
+
const follow = (g) => {
|
|
5458
|
+
const a = anchor();
|
|
5459
|
+
const at = clampToSafeArea(live.current.frame, { x: a.x + start.x + g.dx, y: a.y + start.y + g.dy });
|
|
5460
|
+
return { x: at.x - a.x, y: at.y - a.y };
|
|
5461
|
+
};
|
|
5462
|
+
const drop = (g) => {
|
|
5463
|
+
const a = anchor();
|
|
5464
|
+
const d = follow(g);
|
|
5465
|
+
const next = snapPlacement(live.current.frame, { x: a.x + d.x, y: a.y + d.y });
|
|
5466
|
+
const at = placementPoint(live.current.frame, next);
|
|
5467
|
+
const to = { x: at.x - a.x, y: at.y - a.y };
|
|
5468
|
+
offset.current = to;
|
|
5469
|
+
client.setMessengerLauncherPlacement(next);
|
|
5470
|
+
const done = ({ finished }) => {
|
|
5471
|
+
if (finished) dragging.current = false;
|
|
5472
|
+
setPlacement(next);
|
|
5473
|
+
};
|
|
5474
|
+
if (live.current.reduceMotion) {
|
|
5475
|
+
pan.setValue(to);
|
|
5476
|
+
done({ finished: true });
|
|
5477
|
+
} else import_react_native5.Animated.timing(pan, { toValue: to, duration: 300, easing: import_react_native5.Easing.out(import_react_native5.Easing.cubic), useNativeDriver: true }).start(done);
|
|
5478
|
+
};
|
|
5479
|
+
return import_react_native5.PanResponder.create({
|
|
5480
|
+
// Taps stay with the Pressable; only a real drag is taken (which cancels its press).
|
|
5481
|
+
onMoveShouldSetPanResponderCapture: (_, g) => moved(g),
|
|
5482
|
+
onMoveShouldSetPanResponder: (_, g) => moved(g),
|
|
5483
|
+
onPanResponderGrant: () => {
|
|
5484
|
+
dragging.current = true;
|
|
5485
|
+
start = offset.current;
|
|
5486
|
+
pan.stopAnimation((v) => {
|
|
5487
|
+
if (Number.isFinite(v?.x) && Number.isFinite(v?.y)) start = v;
|
|
5488
|
+
});
|
|
5489
|
+
},
|
|
5490
|
+
onPanResponderMove: (_, g) => pan.setValue(follow(g)),
|
|
5491
|
+
onPanResponderRelease: (_, g) => drop(g),
|
|
5492
|
+
onPanResponderTerminate: (_, g) => drop(g),
|
|
5493
|
+
onPanResponderTerminationRequest: () => false
|
|
5494
|
+
});
|
|
5495
|
+
}, [client, pan]);
|
|
4817
5496
|
if (snap.open) return null;
|
|
4818
5497
|
const label = snap.unread ? `Open ${snap.config.name}, ${snap.unread} unread` : `Open ${snap.config.name}`;
|
|
4819
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => store.open(), style: ({ pressed }) => [styles4.launcher,
|
|
5498
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Animated.View, { ...responder.panHandlers, style: [styles4.launcherWrap, style, { transform: pan.getTranslateTransform() }], children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => !dragging.current && store.open(), style: ({ pressed }) => [styles4.launcher, { backgroundColor: p.primary, opacity: pressed ? 0.85 : 1 }], children: [
|
|
4820
5499
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.launcherIcon, { color: p.onPrimary }], children: "\u{1F4AC}" }),
|
|
4821
5500
|
snap.unread ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: styles4.badge, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: styles4.badgeText, children: snap.unread }) }) : null
|
|
4822
|
-
] });
|
|
5501
|
+
] }) });
|
|
4823
5502
|
}
|
|
4824
5503
|
function MessengerSheet({ store, client, palette: p, topInset, bottomInset }) {
|
|
4825
5504
|
const snap = useMessenger(store);
|
|
@@ -5064,7 +5743,8 @@ function Article({ article, palette: p, client }) {
|
|
|
5064
5743
|
}
|
|
5065
5744
|
var styles4 = import_react_native5.StyleSheet.create({
|
|
5066
5745
|
flex: { flex: 1 },
|
|
5067
|
-
|
|
5746
|
+
launcherWrap: { position: "absolute", width: LAUNCHER_SIZE, height: LAUNCHER_SIZE, elevation: 8 },
|
|
5747
|
+
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 } },
|
|
5068
5748
|
launcherIcon: { fontSize: 24 },
|
|
5069
5749
|
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" },
|
|
5070
5750
|
badgeText: { color: "#ffffff", fontSize: 11, fontWeight: "800" },
|
|
@@ -5297,20 +5977,20 @@ function positionCard(target, size, viewport, insets, placement, offset) {
|
|
|
5297
5977
|
const order = rawSide === "auto" ? ["bottom", "top"] : [rawSide, opposite[rawSide], "bottom", "top"];
|
|
5298
5978
|
const space = { top: target.y - insets.top, bottom: viewport.height - insets.bottom - (target.y + target.height) };
|
|
5299
5979
|
const side = order.find(fits) ?? (space.bottom >= space.top ? "bottom" : "top");
|
|
5300
|
-
const
|
|
5980
|
+
const clamp3 = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
5301
5981
|
if (side === "top" || side === "bottom") {
|
|
5302
5982
|
const cx = target.x + target.width / 2;
|
|
5303
5983
|
let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width - size.width : cx - size.width / 2;
|
|
5304
|
-
x2 =
|
|
5984
|
+
x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN - size.width));
|
|
5305
5985
|
let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset - size.height;
|
|
5306
|
-
y2 =
|
|
5307
|
-
return { x: x2, y: y2, side, arrow:
|
|
5986
|
+
y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size.height));
|
|
5987
|
+
return { x: x2, y: y2, side, arrow: clamp3(cx - x2, ARROW_INSET, size.width - ARROW_INSET) };
|
|
5308
5988
|
}
|
|
5309
5989
|
const cy = target.y + target.height / 2;
|
|
5310
5990
|
let y = align === "start" ? target.y : align === "end" ? target.y + target.height - size.height : cy - size.height / 2;
|
|
5311
|
-
y =
|
|
5991
|
+
y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size.height));
|
|
5312
5992
|
const x = side === "right" ? target.x + target.width + offset : target.x - offset - size.width;
|
|
5313
|
-
return { x, y, side, arrow:
|
|
5993
|
+
return { x, y, side, arrow: clamp3(cy - y, ARROW_INSET, size.height - ARROW_INSET) };
|
|
5314
5994
|
}
|
|
5315
5995
|
|
|
5316
5996
|
// src/ui/StepCard.tsx
|
|
@@ -5926,6 +6606,7 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
5926
6606
|
const unread = showRcLauncher ? client.getUnreadAnnouncements() : 0;
|
|
5927
6607
|
const messengerLeft = messenger?.getSnapshot().config.launcher.position === "bottom-left";
|
|
5928
6608
|
const checklistOnSameSide = ui.checklists.some((c) => c.guide.config.checklist?.position === "bottom-left" === messengerLeft) && client.options.showChecklistLauncher !== false;
|
|
6609
|
+
const messengerBottom = insets.bottom + launcherOffset + (checklistOnSameSide ? 56 : 0);
|
|
5929
6610
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { ref, collapsable: false, pointerEvents: "box-none", style: import_react_native10.StyleSheet.absoluteFill, onLayout: measureOrigin, children: [
|
|
5930
6611
|
ui.checklists.map((c) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Checklist, { view: c, client, palette: palette(client.themeFor(c.guide), scheme), geometry, showLauncher: client.options.showChecklistLauncher !== false, launcherBottom: launcherOffset }, c.guide.id)),
|
|
5931
6612
|
showRcLauncher ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
@@ -5941,7 +6622,17 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
5941
6622
|
]
|
|
5942
6623
|
}
|
|
5943
6624
|
) : null,
|
|
5944
|
-
messenger && client.options.showMessengerLauncher !== false ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
6625
|
+
messenger && client.options.showMessengerLauncher !== false ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
6626
|
+
MessengerLauncher,
|
|
6627
|
+
{
|
|
6628
|
+
store: messenger,
|
|
6629
|
+
client,
|
|
6630
|
+
palette: basePalette,
|
|
6631
|
+
style: [messengerLeft ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: messengerBottom }],
|
|
6632
|
+
frame: { width, height, insets, size: LAUNCHER_SIZE, defaultBottom: messengerBottom },
|
|
6633
|
+
defaultSide: messengerLeft ? "left" : "right"
|
|
6634
|
+
}
|
|
6635
|
+
) : null,
|
|
5945
6636
|
ui.survey ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Survey, { view: ui.survey, client, palette: palette(client.themeFor(ui.survey.guide), scheme), geometry }) : null,
|
|
5946
6637
|
ui.steps.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StepLayer, { view: s, client, palette: palette(client.themeFor(s.guide), scheme, s.guide.config.appearance), geometry }, s.key)),
|
|
5947
6638
|
messenger ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessengerSheet, { store: messenger, client, palette: basePalette, topInset: insets.top, bottomInset: insets.bottom }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ResourceCenter, { client, palette: basePalette, open: ui.resourceCenterOpen, topInset: insets.top, bottomInset: insets.bottom })
|
|
@@ -5961,8 +6652,9 @@ function rcLauncherLabel(client) {
|
|
|
5961
6652
|
|
|
5962
6653
|
// src/ui/Autocapture.tsx
|
|
5963
6654
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
6655
|
+
var GUIDELY_MASK_NATIVE_ID = "guidely-mask";
|
|
5964
6656
|
function GuidelyMask({ children, ...rest }) {
|
|
5965
|
-
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native11.View, { ...rest, children });
|
|
6657
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native11.View, { collapsable: false, ...rest, nativeID: GUIDELY_MASK_NATIVE_ID, children });
|
|
5966
6658
|
}
|
|
5967
6659
|
var RESOLVE = {
|
|
5968
6660
|
isIgnored: (type) => type === GuidelyOverlay,
|
|
@@ -5973,6 +6665,18 @@ function useTapObserver(client) {
|
|
|
5973
6665
|
clientRef.current = client;
|
|
5974
6666
|
return (0, import_react8.useMemo)(() => {
|
|
5975
6667
|
const tracker = new TapTracker();
|
|
6668
|
+
let replayPointer = null;
|
|
6669
|
+
const replay = (phase, e) => {
|
|
6670
|
+
const c = clientRef.current;
|
|
6671
|
+
const n = e.nativeEvent;
|
|
6672
|
+
if (!c?.isRecordingTouches()) return void (replayPointer = null);
|
|
6673
|
+
if (phase === "down") {
|
|
6674
|
+
if (replayPointer !== null) return;
|
|
6675
|
+
replayPointer = n.identifier;
|
|
6676
|
+
} else if (n.identifier !== replayPointer) return;
|
|
6677
|
+
if (phase === "up" || phase === "cancel") replayPointer = null;
|
|
6678
|
+
c.recordTouch(phase, n.pageX, n.pageY);
|
|
6679
|
+
};
|
|
5976
6680
|
const guard = (fn) => (e) => {
|
|
5977
6681
|
try {
|
|
5978
6682
|
fn(e);
|
|
@@ -5982,12 +6686,17 @@ function useTapObserver(client) {
|
|
|
5982
6686
|
};
|
|
5983
6687
|
return {
|
|
5984
6688
|
onTouchStartCapture: guard((e) => {
|
|
6689
|
+
replay("down", e);
|
|
5985
6690
|
const n = e.nativeEvent;
|
|
5986
6691
|
if (!clientRef.current?.isTapCaptureOn()) return tracker.cancel();
|
|
5987
6692
|
tracker.begin(n.pageX, n.pageY, Date.now(), Array.isArray(n.touches) ? n.touches.length : 1, n.identifier);
|
|
5988
6693
|
}),
|
|
5989
|
-
onTouchMoveCapture: guard((e) =>
|
|
6694
|
+
onTouchMoveCapture: guard((e) => {
|
|
6695
|
+
replay("move", e);
|
|
6696
|
+
tracker.move(e.nativeEvent.pageX, e.nativeEvent.pageY, e.nativeEvent.identifier);
|
|
6697
|
+
}),
|
|
5990
6698
|
onTouchEndCapture: guard((e) => {
|
|
6699
|
+
replay("up", e);
|
|
5991
6700
|
const n = e.nativeEvent;
|
|
5992
6701
|
const tap = tracker.end(n.pageX, n.pageY, Date.now(), n.identifier);
|
|
5993
6702
|
const c = clientRef.current;
|
|
@@ -5995,7 +6704,10 @@ function useTapObserver(client) {
|
|
|
5995
6704
|
const r = resolveTapTarget(e, RESOLVE);
|
|
5996
6705
|
if (!r.ignored) c.recordTap({ x: tap.x, y: tap.y, t: Date.now(), element: r.element, hit: r.hit });
|
|
5997
6706
|
}),
|
|
5998
|
-
onTouchCancelCapture: guard(() =>
|
|
6707
|
+
onTouchCancelCapture: guard((e) => {
|
|
6708
|
+
replay("cancel", e);
|
|
6709
|
+
tracker.cancel();
|
|
6710
|
+
})
|
|
5999
6711
|
};
|
|
6000
6712
|
}, []);
|
|
6001
6713
|
}
|
|
@@ -6089,9 +6801,15 @@ function defaultBridge(appVersion) {
|
|
|
6089
6801
|
},
|
|
6090
6802
|
openExternal: (url) => void import_react_native13.Linking.openURL(url).catch(() => {
|
|
6091
6803
|
}),
|
|
6092
|
-
windowSize: () => import_react_native13.Dimensions.get("window")
|
|
6804
|
+
windowSize: () => import_react_native13.Dimensions.get("window"),
|
|
6805
|
+
capturer: nativeCapturer()
|
|
6093
6806
|
};
|
|
6094
6807
|
}
|
|
6808
|
+
function nativeCapturer() {
|
|
6809
|
+
const mod = import_react_native13.NativeModules.GuidelyReplay;
|
|
6810
|
+
if (!mod || typeof mod.capture !== "function") return void 0;
|
|
6811
|
+
return (options) => mod.capture(options);
|
|
6812
|
+
}
|
|
6095
6813
|
function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
|
|
6096
6814
|
const [client, setClient] = (0, import_react10.useState)(null);
|
|
6097
6815
|
const optionsRef = (0, import_react10.useRef)(options);
|
|
@@ -6161,7 +6879,7 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
6161
6879
|
const flags = (0, import_react10.useMemo)(() => ({ client: client?.flags ?? null, bootstrap }), [client, bootstrap]);
|
|
6162
6880
|
const tapObserver = useTapObserver(client);
|
|
6163
6881
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(GuidelyContext.Provider, { value: client, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(FlagsContext.Provider, { value: flags, children: [
|
|
6164
|
-
options.autocapture === false ? children : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native13.View, { style: styles10.root, ...tapObserver, children }),
|
|
6882
|
+
options.autocapture === false && !options.sessionReplay?.enabled ? children : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native13.View, { style: styles10.root, ...tapObserver, children }),
|
|
6165
6883
|
client ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(GuidelyOverlay, { client, insets, launcherOffset }) : null
|
|
6166
6884
|
] }) });
|
|
6167
6885
|
}
|
|
@@ -6181,6 +6899,9 @@ var NOOP_RESULTS = {
|
|
|
6181
6899
|
getMessenger: null,
|
|
6182
6900
|
isGuideCompleted: false,
|
|
6183
6901
|
isClientReady: false,
|
|
6902
|
+
isRecording: false,
|
|
6903
|
+
isRecordingTouches: false,
|
|
6904
|
+
getSessionId: "",
|
|
6184
6905
|
getScreen: "",
|
|
6185
6906
|
on: () => {
|
|
6186
6907
|
},
|
|
@@ -6315,8 +7036,11 @@ function useActiveGuide() {
|
|
|
6315
7036
|
InMemoryStickyBucketStore,
|
|
6316
7037
|
Messenger,
|
|
6317
7038
|
RageClickDetector,
|
|
7039
|
+
ReplayUploader,
|
|
7040
|
+
RrwebEvents,
|
|
6318
7041
|
SDK_NAME,
|
|
6319
7042
|
SDK_VERSION,
|
|
7043
|
+
SessionRecorder,
|
|
6320
7044
|
TapTracker,
|
|
6321
7045
|
TargetRegistry,
|
|
6322
7046
|
createFlagsClient,
|