@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.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-
|
|
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 =
|
|
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-
|
|
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-
|
|
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.
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
5243
|
-
return { x: x2, y: y2, side, arrow:
|
|
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 =
|
|
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:
|
|
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
|
|
6022
|
+
const opacity = useRef3(new Animated2.Value(0)).current;
|
|
5346
6023
|
useEffect5(() => {
|
|
5347
6024
|
opacity.setValue(0);
|
|
5348
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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) =>
|
|
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(() =>
|
|
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,
|