@guidelytech/react-native 0.15.0 → 0.17.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 +119 -2
- package/dist/index.cjs +1239 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +459 -183
- package/dist/index.d.ts +459 -183
- package/dist/index.js +1242 -98
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -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-YL65O3FS.js
|
|
100
100
|
function fnv1a(input) {
|
|
101
101
|
let h = 2166136261;
|
|
102
102
|
for (let i = 0; i < input.length; i++) {
|
|
@@ -109,18 +109,18 @@ function rolloutBucket(subject, salt) {
|
|
|
109
109
|
return fnv1a(`${salt}:${subject}`) % 100;
|
|
110
110
|
}
|
|
111
111
|
var ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
112
|
-
function randomId(
|
|
112
|
+
function randomId(size2 = 16) {
|
|
113
113
|
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
114
114
|
let bytes;
|
|
115
115
|
if (g.crypto && typeof g.crypto.getRandomValues === "function") {
|
|
116
|
-
bytes = new Uint8Array(
|
|
116
|
+
bytes = new Uint8Array(size2);
|
|
117
117
|
g.crypto.getRandomValues(bytes);
|
|
118
118
|
} else {
|
|
119
|
-
bytes = new Uint8Array(
|
|
120
|
-
for (let i = 0; i <
|
|
119
|
+
bytes = new Uint8Array(size2);
|
|
120
|
+
for (let i = 0; i < size2; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
121
121
|
}
|
|
122
122
|
let out = "";
|
|
123
|
-
for (let i = 0; i <
|
|
123
|
+
for (let i = 0; i < size2; i++) out += ALPHABET[bytes[i] % ALPHABET.length];
|
|
124
124
|
return out;
|
|
125
125
|
}
|
|
126
126
|
function getPath(obj, path) {
|
|
@@ -390,24 +390,92 @@ function evaluateRules(group, ctx) {
|
|
|
390
390
|
const value = group.op === "and" ? results.every(Boolean) : results.some(Boolean);
|
|
391
391
|
return group.negate ? !value : value;
|
|
392
392
|
}
|
|
393
|
+
var MAX_EXCEPTION_DETAIL_BYTES = 64 * 1024;
|
|
394
|
+
var MAX_EXCEPTION_DETAIL_RAW_BYTES = 1024 * 1024;
|
|
395
|
+
var MAX_BREADCRUMBS = 100;
|
|
396
|
+
var MAX_EXCEPTION_CHAIN = 20;
|
|
397
|
+
var MAX_DETAIL_FRAMES = 250;
|
|
398
|
+
var MAX_DETAIL_THREADS = 100;
|
|
399
|
+
var MAX_DEBUG_IMAGES = 500;
|
|
400
|
+
var MAX_ERROR_TAGS = 50;
|
|
401
|
+
var size = (v) => {
|
|
402
|
+
try {
|
|
403
|
+
return JSON.stringify(v)?.length ?? 0;
|
|
404
|
+
} catch {
|
|
405
|
+
return Infinity;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
function normalizeTags(tags) {
|
|
409
|
+
const out = {};
|
|
410
|
+
for (const [k, v] of Object.entries(tags ?? {})) {
|
|
411
|
+
if (Object.keys(out).length >= MAX_ERROR_TAGS) break;
|
|
412
|
+
if (!k || v === void 0 || v === null) continue;
|
|
413
|
+
out[k.slice(0, 32)] = String(v).slice(0, 200);
|
|
414
|
+
}
|
|
415
|
+
return out;
|
|
416
|
+
}
|
|
417
|
+
function trimExceptionDetail(detail, max = MAX_EXCEPTION_DETAIL_BYTES) {
|
|
418
|
+
const d = detail;
|
|
419
|
+
if (d.breadcrumbs?.values && d.breadcrumbs.values.length > MAX_BREADCRUMBS) d.breadcrumbs.values = d.breadcrumbs.values.slice(-MAX_BREADCRUMBS);
|
|
420
|
+
if (d.exception?.values && d.exception.values.length > MAX_EXCEPTION_CHAIN) d.exception.values = d.exception.values.slice(-MAX_EXCEPTION_CHAIN);
|
|
421
|
+
if (d.threads?.values && d.threads.values.length > MAX_DETAIL_THREADS) d.threads.values = d.threads.values.slice(0, MAX_DETAIL_THREADS);
|
|
422
|
+
if (d.debug_meta?.images && d.debug_meta.images.length > MAX_DEBUG_IMAGES) d.debug_meta.images = d.debug_meta.images.slice(0, MAX_DEBUG_IMAGES);
|
|
423
|
+
const stacks = () => [...d.exception?.values ?? [], ...d.threads?.values ?? []].map((x) => x.stacktrace).filter((s) => !!s);
|
|
424
|
+
for (const s of stacks()) if (s.frames && s.frames.length > MAX_DETAIL_FRAMES) s.frames = s.frames.slice(-MAX_DETAIL_FRAMES);
|
|
425
|
+
if (size(d) <= max) return d;
|
|
426
|
+
while (d.breadcrumbs?.values.length && size(d) > max) d.breadcrumbs.values = d.breadcrumbs.values.slice(Math.ceil(d.breadcrumbs.values.length / 2));
|
|
427
|
+
if (size(d) <= max) return d;
|
|
428
|
+
for (const s of stacks())
|
|
429
|
+
for (const f of s.frames ?? []) {
|
|
430
|
+
delete f.vars;
|
|
431
|
+
delete f.pre_context;
|
|
432
|
+
delete f.post_context;
|
|
433
|
+
}
|
|
434
|
+
if (size(d) <= max) return d;
|
|
435
|
+
if (d.threads?.values) d.threads.values = d.threads.values.filter((t) => t.crashed || t.current);
|
|
436
|
+
if (size(d) <= max) return d;
|
|
437
|
+
if (d.extra) d.extra = { _trimmed: true };
|
|
438
|
+
if (d.request) delete d.request.data;
|
|
439
|
+
for (const s of stacks()) while (s.frames && s.frames.length > 20 && size(d) > max) s.frames.splice(Math.floor(s.frames.length / 2) - 5, 10);
|
|
440
|
+
if (d.debug_meta?.images && size(d) > max) d.debug_meta.images = d.debug_meta.images.slice(0, 50);
|
|
441
|
+
return d;
|
|
442
|
+
}
|
|
443
|
+
var matches = (patterns, value) => patterns.some((p) => typeof p === "string" ? p !== "" && value.includes(p) : safeTest(p, value));
|
|
444
|
+
function safeTest(re, value) {
|
|
445
|
+
re.lastIndex = 0;
|
|
446
|
+
return re.test(value);
|
|
447
|
+
}
|
|
448
|
+
function errorFilterDrops(e, opts) {
|
|
449
|
+
const message = e.message ?? "";
|
|
450
|
+
if (opts.ignoreErrors?.length && (matches(opts.ignoreErrors, `${e.type || "Error"}: ${message}`) || matches(opts.ignoreErrors, message))) return true;
|
|
451
|
+
if (!e.file) return false;
|
|
452
|
+
if (opts.denyUrls?.length && matches(opts.denyUrls, e.file)) return true;
|
|
453
|
+
if (opts.allowUrls?.length && !matches(opts.allowUrls, e.file)) return true;
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
393
456
|
var SENSITIVE_PROPERTY_PATTERN = /(password|passwd|secret|token|credit|card|cvv|ssn|iban)/i;
|
|
394
457
|
var MAX_PROPERTY_BYTES = 8 * 1024;
|
|
395
458
|
function sanitizeProperties(props) {
|
|
396
459
|
if (!props) return {};
|
|
397
460
|
const out = {};
|
|
398
|
-
let
|
|
461
|
+
let size2 = 0;
|
|
399
462
|
for (const [k, v] of Object.entries(props)) {
|
|
400
463
|
if (SENSITIVE_PROPERTY_PATTERN.test(k)) continue;
|
|
401
464
|
if (typeof v === "function" || typeof v === "symbol") continue;
|
|
402
465
|
const str2 = typeof v === "string" ? v : JSON.stringify(v ?? null);
|
|
403
|
-
|
|
404
|
-
if (
|
|
466
|
+
size2 += k.length + (str2?.length ?? 0);
|
|
467
|
+
if (size2 > MAX_PROPERTY_BYTES) break;
|
|
405
468
|
out[k.slice(0, 100)] = typeof v === "string" ? v.slice(0, 1e3) : v;
|
|
406
469
|
}
|
|
407
470
|
return out;
|
|
408
471
|
}
|
|
472
|
+
function reactNativeBundleUrl(file) {
|
|
473
|
+
const base = file.replace(/[?#].*$/, "").split("/").pop() || file;
|
|
474
|
+
return `app:///${base}`;
|
|
475
|
+
}
|
|
409
476
|
var SOURCEMAP_MAX_FILE_BYTES = 100 * 1024 * 1024;
|
|
410
477
|
var MAX_COMPLAINT_ATTACHMENT_BYTES = 2 * 1024 * 1024;
|
|
478
|
+
var MAX_BUG_REPORT_SCREENSHOT_BYTES = 4 * 1024 * 1024;
|
|
411
479
|
var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
412
480
|
var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
|
|
413
481
|
var MEDIA_FILE_MAX_BYTES = 50 * 1024 * 1024;
|
|
@@ -505,6 +573,16 @@ function isSafeMediaUrl(url) {
|
|
|
505
573
|
return false;
|
|
506
574
|
}
|
|
507
575
|
}
|
|
576
|
+
function videoThumbnailUrl(url) {
|
|
577
|
+
const embed = url ? toEmbedUrl(url) : null;
|
|
578
|
+
if (embed?.provider !== "youtube") return null;
|
|
579
|
+
const id = embed.src.split("/embed/")[1]?.split("?")[0];
|
|
580
|
+
return id ? `https://i.ytimg.com/vi/${id}/hqdefault.jpg` : null;
|
|
581
|
+
}
|
|
582
|
+
function stepVideo(step) {
|
|
583
|
+
if (!step.videoUrl || !isSafeMediaUrl(step.videoUrl)) return null;
|
|
584
|
+
return { url: step.videoUrl, poster: (step.imageUrl && isSafeMediaUrl(step.imageUrl) ? step.imageUrl : null) ?? videoThumbnailUrl(step.videoUrl) };
|
|
585
|
+
}
|
|
508
586
|
var MAX_COLLECTION_DEPTH = 3;
|
|
509
587
|
function matchesAudiences(required, member) {
|
|
510
588
|
if (!required || required.length === 0) return true;
|
|
@@ -574,6 +652,13 @@ function unreadAnnouncements(items, lastSeenAt) {
|
|
|
574
652
|
if (Number.isNaN(seen)) return items.length;
|
|
575
653
|
return items.filter((a) => Date.parse(a.publishedAt) > seen).length;
|
|
576
654
|
}
|
|
655
|
+
var ARTICLE_HREF_PREFIX = "#guidely-article:";
|
|
656
|
+
var ARTICLE_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
657
|
+
function articleIdFromHref(href) {
|
|
658
|
+
if (!href || !href.startsWith(ARTICLE_HREF_PREFIX)) return null;
|
|
659
|
+
const id = href.slice(ARTICLE_HREF_PREFIX.length);
|
|
660
|
+
return ARTICLE_ID.test(id) ? id : null;
|
|
661
|
+
}
|
|
577
662
|
var CAPTURE_SCREENSHOT_MAX_BYTES = 8 * 1024 * 1024;
|
|
578
663
|
var DEFAULT_LOCALE = "en";
|
|
579
664
|
function normalizeLocale(input) {
|
|
@@ -1015,7 +1100,22 @@ var KEYS = {
|
|
|
1015
1100
|
launcher: (key) => `guidely:launcher:${key}`
|
|
1016
1101
|
};
|
|
1017
1102
|
|
|
1018
|
-
// ../sdk-core/dist/chunk-
|
|
1103
|
+
// ../sdk-core/dist/chunk-GFLVZRTS.js
|
|
1104
|
+
var MAX_PERSISTED_QUEUE_CHARS = 1024 * 1024;
|
|
1105
|
+
function fitPersisted(events) {
|
|
1106
|
+
let size2 = JSON.stringify(events).length;
|
|
1107
|
+
if (size2 <= MAX_PERSISTED_QUEUE_CHARS) return events;
|
|
1108
|
+
const out = events.slice();
|
|
1109
|
+
for (let i = 0; i < out.length && size2 > MAX_PERSISTED_QUEUE_CHARS; i++) {
|
|
1110
|
+
const e = out[i];
|
|
1111
|
+
if (!e.detail) continue;
|
|
1112
|
+
size2 -= JSON.stringify(e.detail).length;
|
|
1113
|
+
const rest = { ...e };
|
|
1114
|
+
delete rest.detail;
|
|
1115
|
+
out[i] = rest;
|
|
1116
|
+
}
|
|
1117
|
+
return out;
|
|
1118
|
+
}
|
|
1019
1119
|
var EventQueue = class {
|
|
1020
1120
|
constructor(opts) {
|
|
1021
1121
|
__publicField2(this, "opts", opts);
|
|
@@ -1071,7 +1171,7 @@ var EventQueue = class {
|
|
|
1071
1171
|
this.schedule();
|
|
1072
1172
|
}
|
|
1073
1173
|
persist() {
|
|
1074
|
-
writeJson(this.opts.store, this.opts.storageKey, this.events);
|
|
1174
|
+
writeJson(this.opts.store, this.opts.storageKey, fitPersisted(this.events));
|
|
1075
1175
|
}
|
|
1076
1176
|
schedule() {
|
|
1077
1177
|
if (this.timer || this.retryTimer || this.destroyed) return;
|
|
@@ -1321,6 +1421,13 @@ function dismissedAfterMerge(local, server) {
|
|
|
1321
1421
|
if (server.dismissResetAt && server.dismissResetAt !== local?.dismissResetAt) return server.dismissedAt ?? null;
|
|
1322
1422
|
return local?.dismissedAt ?? server.dismissedAt ?? null;
|
|
1323
1423
|
}
|
|
1424
|
+
function environmentFromKey(key) {
|
|
1425
|
+
const prefix = /^pk_([a-z0-9]+)_/.exec(key)?.[1];
|
|
1426
|
+
if (!prefix || prefix === "dev") return "development";
|
|
1427
|
+
if (prefix === "prod") return "production";
|
|
1428
|
+
if (prefix === "stg" || prefix === "stag") return "staging";
|
|
1429
|
+
return prefix;
|
|
1430
|
+
}
|
|
1324
1431
|
|
|
1325
1432
|
// src/core/client.ts
|
|
1326
1433
|
import { createFlagsClient as createFlagsClient2 } from "@guidelytech/flags";
|
|
@@ -1477,6 +1584,593 @@ var TapTracker = class {
|
|
|
1477
1584
|
}
|
|
1478
1585
|
};
|
|
1479
1586
|
|
|
1587
|
+
// src/core/breadcrumbs.ts
|
|
1588
|
+
var BreadcrumbBuffer = class {
|
|
1589
|
+
constructor(max = MAX_BREADCRUMBS) {
|
|
1590
|
+
__publicField(this, "max", max);
|
|
1591
|
+
__publicField(this, "items", []);
|
|
1592
|
+
}
|
|
1593
|
+
/** Adds a breadcrumb; `max` (at most the buffer's own) lowers how many are kept. */
|
|
1594
|
+
add(crumb, max = this.max) {
|
|
1595
|
+
const limit = Math.min(max, this.max);
|
|
1596
|
+
this.items.push(crumb);
|
|
1597
|
+
if (this.items.length > limit) this.items.splice(0, this.items.length - limit);
|
|
1598
|
+
}
|
|
1599
|
+
/** Copies, oldest first (an event keeps the crumbs it was captured with). */
|
|
1600
|
+
values() {
|
|
1601
|
+
return this.items.map((c) => ({ ...c, ...c.data ? { data: { ...c.data } } : {} }));
|
|
1602
|
+
}
|
|
1603
|
+
get size() {
|
|
1604
|
+
return this.items.length;
|
|
1605
|
+
}
|
|
1606
|
+
clear() {
|
|
1607
|
+
this.items = [];
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
function stripUrl(url) {
|
|
1611
|
+
return String(url).replace(/[?#].*$/, "").replace(/^([a-z][\w+.-]*:\/\/)[^/@]*@/i, "$1").slice(0, 500);
|
|
1612
|
+
}
|
|
1613
|
+
function describeArg(value) {
|
|
1614
|
+
if (typeof value === "string") return value.slice(0, 500);
|
|
1615
|
+
if (value instanceof Error) return `${value.name}: ${value.message}`.slice(0, 500);
|
|
1616
|
+
try {
|
|
1617
|
+
return (JSON.stringify(value) ?? String(value)).slice(0, 500);
|
|
1618
|
+
} catch {
|
|
1619
|
+
return String(value).slice(0, 500);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
function instrumentConsole(target, emit) {
|
|
1623
|
+
if (!target) return () => {
|
|
1624
|
+
};
|
|
1625
|
+
const undo = [];
|
|
1626
|
+
let inside = false;
|
|
1627
|
+
for (const [method, level] of [
|
|
1628
|
+
["warn", "warning"],
|
|
1629
|
+
["error", "error"]
|
|
1630
|
+
]) {
|
|
1631
|
+
const original = target[method];
|
|
1632
|
+
if (typeof original !== "function") continue;
|
|
1633
|
+
const wrapped = function(...args) {
|
|
1634
|
+
const result = original.apply(this, args);
|
|
1635
|
+
if (!inside && !(typeof args[0] === "string" && args[0].startsWith("[Guidely]"))) {
|
|
1636
|
+
inside = true;
|
|
1637
|
+
try {
|
|
1638
|
+
const parts = args.slice(0, 5).map(describeArg);
|
|
1639
|
+
emit({ type: "debug", category: "console", level, message: parts.join(" ").slice(0, 1e3), data: { arguments: parts, logger: "console" } });
|
|
1640
|
+
} catch {
|
|
1641
|
+
} finally {
|
|
1642
|
+
inside = false;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
return result;
|
|
1646
|
+
};
|
|
1647
|
+
target[method] = wrapped;
|
|
1648
|
+
undo.push(() => {
|
|
1649
|
+
if (target[method] === wrapped) target[method] = original;
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
return () => undo.forEach((fn) => fn());
|
|
1653
|
+
}
|
|
1654
|
+
function httpLevel(status) {
|
|
1655
|
+
if (!status) return "error";
|
|
1656
|
+
return status >= 500 ? "error" : status >= 400 ? "warning" : "info";
|
|
1657
|
+
}
|
|
1658
|
+
var XHR_INFO = "__guidelyXhr";
|
|
1659
|
+
function instrumentHttp(g, emit, skip, now = Date.now) {
|
|
1660
|
+
if (!g) return () => {
|
|
1661
|
+
};
|
|
1662
|
+
const undo = [];
|
|
1663
|
+
let inFetch = 0;
|
|
1664
|
+
const record2 = (category, method, url, start, status) => {
|
|
1665
|
+
try {
|
|
1666
|
+
const data = { method, url: stripUrl(url), ...status ? { status_code: status } : {}, duration_ms: Math.max(0, Math.round(now() - start)) };
|
|
1667
|
+
emit({ type: "http", category, level: httpLevel(status), data });
|
|
1668
|
+
} catch {
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
const originalFetch = g.fetch;
|
|
1672
|
+
if (typeof originalFetch === "function") {
|
|
1673
|
+
const wrappedFetch = function(...args) {
|
|
1674
|
+
const [input, init] = args;
|
|
1675
|
+
let url = "";
|
|
1676
|
+
let method = "GET";
|
|
1677
|
+
try {
|
|
1678
|
+
const req = input;
|
|
1679
|
+
url = typeof req === "string" ? req : typeof req?.url === "string" ? req.url : String(input);
|
|
1680
|
+
method = String(init?.method ?? (req && typeof req === "object" ? req.method : void 0) ?? "GET").toUpperCase();
|
|
1681
|
+
} catch {
|
|
1682
|
+
}
|
|
1683
|
+
if (skip(url)) return originalFetch.apply(this, args);
|
|
1684
|
+
const start = now();
|
|
1685
|
+
let result;
|
|
1686
|
+
inFetch += 1;
|
|
1687
|
+
try {
|
|
1688
|
+
result = originalFetch.apply(this, args);
|
|
1689
|
+
} finally {
|
|
1690
|
+
inFetch -= 1;
|
|
1691
|
+
}
|
|
1692
|
+
if (!result || typeof result.then !== "function") return result;
|
|
1693
|
+
return result.then(
|
|
1694
|
+
(res) => {
|
|
1695
|
+
record2("fetch", method, url, start, typeof res?.status === "number" ? res.status : void 0);
|
|
1696
|
+
return res;
|
|
1697
|
+
},
|
|
1698
|
+
(err) => {
|
|
1699
|
+
record2("fetch", method, url, start, void 0);
|
|
1700
|
+
throw err;
|
|
1701
|
+
}
|
|
1702
|
+
);
|
|
1703
|
+
};
|
|
1704
|
+
g.fetch = wrappedFetch;
|
|
1705
|
+
undo.push(() => {
|
|
1706
|
+
if (g.fetch === wrappedFetch) g.fetch = originalFetch;
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
const proto = g.XMLHttpRequest?.prototype;
|
|
1710
|
+
const originalOpen = proto?.open;
|
|
1711
|
+
const originalSend = proto?.send;
|
|
1712
|
+
if (proto && typeof originalOpen === "function" && typeof originalSend === "function") {
|
|
1713
|
+
const open = function(...args) {
|
|
1714
|
+
try {
|
|
1715
|
+
this[XHR_INFO] = { method: String(args[0] ?? "GET").toUpperCase(), url: String(args[1] ?? ""), viaFetch: inFetch > 0 };
|
|
1716
|
+
} catch {
|
|
1717
|
+
}
|
|
1718
|
+
return originalOpen.apply(this, args);
|
|
1719
|
+
};
|
|
1720
|
+
const send = function(...args) {
|
|
1721
|
+
try {
|
|
1722
|
+
const info = this[XHR_INFO];
|
|
1723
|
+
if (info && !info.viaFetch && !skip(info.url) && typeof this.addEventListener === "function") {
|
|
1724
|
+
info.start = now();
|
|
1725
|
+
this.addEventListener("readystatechange", () => {
|
|
1726
|
+
if (this.readyState !== 4 || info.done) return;
|
|
1727
|
+
info.done = true;
|
|
1728
|
+
record2("xhr", info.method, info.url, info.start ?? now(), typeof this.status === "number" && this.status > 0 ? this.status : void 0);
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
} catch {
|
|
1732
|
+
}
|
|
1733
|
+
return originalSend.apply(this, args);
|
|
1734
|
+
};
|
|
1735
|
+
proto.open = open;
|
|
1736
|
+
proto.send = send;
|
|
1737
|
+
undo.push(() => {
|
|
1738
|
+
if (proto.open === open) proto.open = originalOpen;
|
|
1739
|
+
if (proto.send === send) proto.send = originalSend;
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
return () => undo.forEach((fn) => fn());
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/core/crashHandlers.ts
|
|
1746
|
+
var FATAL_FLUSH_MS = 2e3;
|
|
1747
|
+
var isThenable = (v) => !!v && typeof v.then === "function";
|
|
1748
|
+
function installGlobalHandler(g, report, waitMs = FATAL_FLUSH_MS) {
|
|
1749
|
+
const eu = g.ErrorUtils;
|
|
1750
|
+
if (!eu || typeof eu.setGlobalHandler !== "function") return () => {
|
|
1751
|
+
};
|
|
1752
|
+
let previous;
|
|
1753
|
+
try {
|
|
1754
|
+
previous = eu.getGlobalHandler?.();
|
|
1755
|
+
} catch {
|
|
1756
|
+
previous = void 0;
|
|
1757
|
+
}
|
|
1758
|
+
let active = true;
|
|
1759
|
+
let handlingFatal = false;
|
|
1760
|
+
const handler = (error, isFatal) => {
|
|
1761
|
+
const fatal = !!isFatal;
|
|
1762
|
+
const callPrevious = () => previous?.(error, isFatal);
|
|
1763
|
+
if (!active || fatal && handlingFatal) return callPrevious();
|
|
1764
|
+
let pending;
|
|
1765
|
+
try {
|
|
1766
|
+
pending = report(error, fatal);
|
|
1767
|
+
} catch {
|
|
1768
|
+
}
|
|
1769
|
+
if (!fatal || g.__DEV__ || !isThenable(pending)) return callPrevious();
|
|
1770
|
+
handlingFatal = true;
|
|
1771
|
+
let done = false;
|
|
1772
|
+
const finish = () => {
|
|
1773
|
+
if (done) return;
|
|
1774
|
+
done = true;
|
|
1775
|
+
clearTimeout(timer);
|
|
1776
|
+
handlingFatal = false;
|
|
1777
|
+
try {
|
|
1778
|
+
callPrevious();
|
|
1779
|
+
} catch {
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
const timer = setTimeout(finish, waitMs);
|
|
1783
|
+
pending.then(finish, finish);
|
|
1784
|
+
};
|
|
1785
|
+
try {
|
|
1786
|
+
eu.setGlobalHandler(handler);
|
|
1787
|
+
} catch {
|
|
1788
|
+
return () => {
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
return () => {
|
|
1792
|
+
active = false;
|
|
1793
|
+
try {
|
|
1794
|
+
if (previous && eu.getGlobalHandler?.() === handler) eu.setGlobalHandler(previous);
|
|
1795
|
+
} catch {
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
function describeRejection(rejection) {
|
|
1800
|
+
if (rejection instanceof Error) return rejection.stack || `${rejection.name}: ${rejection.message}`;
|
|
1801
|
+
try {
|
|
1802
|
+
return typeof rejection === "string" ? rejection : JSON.stringify(rejection) ?? String(rejection);
|
|
1803
|
+
} catch {
|
|
1804
|
+
return String(rejection);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
function hermesTracker(g) {
|
|
1808
|
+
const h = g.HermesInternal;
|
|
1809
|
+
try {
|
|
1810
|
+
if (!h || typeof h.enablePromiseRejectionTracker !== "function" || typeof h.hasPromise === "function" && !h.hasPromise()) return null;
|
|
1811
|
+
} catch {
|
|
1812
|
+
return null;
|
|
1813
|
+
}
|
|
1814
|
+
return { enable: (options) => h.enablePromiseRejectionTracker(options) };
|
|
1815
|
+
}
|
|
1816
|
+
function installRejectionTracking(g, report, tracker) {
|
|
1817
|
+
let active = true;
|
|
1818
|
+
const warn = (message) => {
|
|
1819
|
+
try {
|
|
1820
|
+
g.console?.warn?.(message);
|
|
1821
|
+
} catch {
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
const options = {
|
|
1825
|
+
allRejections: true,
|
|
1826
|
+
onUnhandled: (id, rejection) => {
|
|
1827
|
+
if (active) {
|
|
1828
|
+
try {
|
|
1829
|
+
report(rejection);
|
|
1830
|
+
} catch {
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
if (g.__DEV__) warn(`Possible unhandled promise rejection (id: ${id}):
|
|
1834
|
+
${describeRejection(rejection)}`);
|
|
1835
|
+
},
|
|
1836
|
+
onHandled: (id) => {
|
|
1837
|
+
if (g.__DEV__) warn(`Promise rejection handled (id: ${id})
|
|
1838
|
+
This means you can ignore any previous messages of the form "Possible unhandled promise rejection (id: ${id}):"`);
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
const t = tracker ?? hermesTracker(g);
|
|
1842
|
+
if (t && typeof t.enable === "function") {
|
|
1843
|
+
try {
|
|
1844
|
+
t.enable(options);
|
|
1845
|
+
return () => {
|
|
1846
|
+
active = false;
|
|
1847
|
+
};
|
|
1848
|
+
} catch {
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
if (typeof g.addEventListener === "function") {
|
|
1852
|
+
const listener = (event) => {
|
|
1853
|
+
if (active) report(event?.reason);
|
|
1854
|
+
};
|
|
1855
|
+
try {
|
|
1856
|
+
g.addEventListener("unhandledrejection", listener);
|
|
1857
|
+
return () => {
|
|
1858
|
+
active = false;
|
|
1859
|
+
g.removeEventListener?.("unhandledrejection", listener);
|
|
1860
|
+
};
|
|
1861
|
+
} catch {
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return () => {
|
|
1865
|
+
active = false;
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
function runtimeContext(g) {
|
|
1869
|
+
const h = g.HermesInternal;
|
|
1870
|
+
if (!h) return { name: "jsc" };
|
|
1871
|
+
try {
|
|
1872
|
+
const version = h.getRuntimeProperties?.()?.["OSS Release Version"];
|
|
1873
|
+
return { name: "hermes", ...typeof version === "string" && version ? { version: version.slice(0, 50) } : {} };
|
|
1874
|
+
} catch {
|
|
1875
|
+
return { name: "hermes" };
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/core/stack.ts
|
|
1880
|
+
var AT = /^\s*at (?:(.+?) \()?(?:address at )?(.+?):(\d+):(\d+)\)?\s*$/;
|
|
1881
|
+
var AT_NATIVE = /^\s*at (?:(.+?) \()?(?:native|\[native code\])\)?\s*$/;
|
|
1882
|
+
var JSC = /^\s*([^@]*)@(.+?):(\d+):(\d+)\s*$/;
|
|
1883
|
+
var JSC_BARE = /^\s*(\S+?):(\d+):(\d+)\s*$/;
|
|
1884
|
+
var JSC_NATIVE = /^\s*(?:([^@]*)@)?\[native code\]\s*$/;
|
|
1885
|
+
var BUNDLE = /\.(jsbundle|bundle|hbc)$/i;
|
|
1886
|
+
function normalizeBundleFile(file) {
|
|
1887
|
+
const f = file.trim();
|
|
1888
|
+
if (/^https?:\/\//i.test(f)) return f.replace(/[?#].*$/, "");
|
|
1889
|
+
const path = f.replace(/^file:\/\//i, "").replace(/[?#].*$/, "");
|
|
1890
|
+
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
1891
|
+
return BUNDLE.test(base) ? reactNativeBundleUrl(path) : path;
|
|
1892
|
+
}
|
|
1893
|
+
function inAppFor(file) {
|
|
1894
|
+
if (!file) return false;
|
|
1895
|
+
if (/(^|\/)node_modules\//.test(file) || /(^|\/)InternalBytecode\.js$/.test(file)) return false;
|
|
1896
|
+
if (file.startsWith("app:///") || /^https?:\/\//i.test(file)) return void 0;
|
|
1897
|
+
return true;
|
|
1898
|
+
}
|
|
1899
|
+
function frame(fn, file, line, col) {
|
|
1900
|
+
const name = (fn || "").trim();
|
|
1901
|
+
const normalized = file ? normalizeBundleFile(file).slice(0, 500) : void 0;
|
|
1902
|
+
const inApp = inAppFor(normalized);
|
|
1903
|
+
return {
|
|
1904
|
+
...name ? { fn: name.slice(0, 200) } : {},
|
|
1905
|
+
...normalized ? { file: normalized } : {},
|
|
1906
|
+
...line ? { line: Number(line) } : {},
|
|
1907
|
+
...col ? { col: Number(col) } : {},
|
|
1908
|
+
...inApp !== void 0 ? { inApp } : {}
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
function parseReactNativeStack(stack, max = MAX_DETAIL_FRAMES) {
|
|
1912
|
+
if (!stack || typeof stack !== "string") return [];
|
|
1913
|
+
const frames = [];
|
|
1914
|
+
for (const raw of stack.split("\n")) {
|
|
1915
|
+
if (frames.length >= max) break;
|
|
1916
|
+
const line = raw.slice(0, 1e3);
|
|
1917
|
+
let m;
|
|
1918
|
+
if (m = AT_NATIVE.exec(line)) frames.push(frame(m[1], void 0));
|
|
1919
|
+
else if (m = AT.exec(line)) frames.push(frame(m[1], m[2], m[3], m[4]));
|
|
1920
|
+
else if (m = JSC_NATIVE.exec(line)) frames.push(frame(m[1], void 0));
|
|
1921
|
+
else if (m = JSC.exec(line)) frames.push(frame(m[1], m[2], m[3], m[4]));
|
|
1922
|
+
else if (m = JSC_BARE.exec(line)) frames.push(frame(void 0, m[1], m[2], m[3]));
|
|
1923
|
+
}
|
|
1924
|
+
return frames;
|
|
1925
|
+
}
|
|
1926
|
+
function toDetailFrames(frames) {
|
|
1927
|
+
return frames.slice().reverse().map((f) => ({
|
|
1928
|
+
...f.fn ? { function: f.fn } : {},
|
|
1929
|
+
...f.file ? { filename: f.file, abs_path: f.file } : {},
|
|
1930
|
+
...f.line !== void 0 ? { lineno: f.line } : {},
|
|
1931
|
+
...f.col !== void 0 ? { colno: f.col } : {},
|
|
1932
|
+
...f.inApp !== void 0 ? { in_app: f.inApp } : {}
|
|
1933
|
+
}));
|
|
1934
|
+
}
|
|
1935
|
+
var MAX_FRAMES_BYTES = 6e3;
|
|
1936
|
+
function fitPropertyFrames(frames) {
|
|
1937
|
+
const out = frames.slice(0, 30);
|
|
1938
|
+
while (out.length > 1 && JSON.stringify(out).length > MAX_FRAMES_BYTES) out.pop();
|
|
1939
|
+
return out;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/core/errors.ts
|
|
1943
|
+
var CONTEXT_KEYS = ["tags", "extra", "level", "fingerprint", "contexts", "user"];
|
|
1944
|
+
function splitHint(hint) {
|
|
1945
|
+
const ctx = {};
|
|
1946
|
+
const properties = {};
|
|
1947
|
+
for (const [k, v] of Object.entries(hint && typeof hint === "object" ? hint : {})) (CONTEXT_KEYS.includes(k) ? ctx : properties)[k] = v;
|
|
1948
|
+
return { ctx, properties };
|
|
1949
|
+
}
|
|
1950
|
+
var nowSeconds = () => Date.now() / 1e3;
|
|
1951
|
+
var ErrorScope = class {
|
|
1952
|
+
constructor(opts) {
|
|
1953
|
+
__publicField(this, "opts", opts);
|
|
1954
|
+
__publicField(this, "tags", {});
|
|
1955
|
+
__publicField(this, "extra", {});
|
|
1956
|
+
__publicField(this, "contexts", {});
|
|
1957
|
+
__publicField(this, "user", {});
|
|
1958
|
+
__publicField(this, "crumbs", new BreadcrumbBuffer(MAX_BREADCRUMBS));
|
|
1959
|
+
}
|
|
1960
|
+
get max() {
|
|
1961
|
+
return Math.max(0, Math.min(MAX_BREADCRUMBS, this.opts()?.maxBreadcrumbs ?? MAX_BREADCRUMBS));
|
|
1962
|
+
}
|
|
1963
|
+
addBreadcrumb(crumb, hint) {
|
|
1964
|
+
if (!crumb || typeof crumb !== "object") return;
|
|
1965
|
+
let c = { timestamp: nowSeconds(), ...crumb };
|
|
1966
|
+
const before = this.opts()?.beforeBreadcrumb;
|
|
1967
|
+
if (before) {
|
|
1968
|
+
try {
|
|
1969
|
+
c = before(c, hint);
|
|
1970
|
+
} catch {
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
const max = this.max;
|
|
1974
|
+
if (!c || !max) return;
|
|
1975
|
+
this.crumbs.add(c, max);
|
|
1976
|
+
}
|
|
1977
|
+
breadcrumbs() {
|
|
1978
|
+
return this.crumbs.values();
|
|
1979
|
+
}
|
|
1980
|
+
clear() {
|
|
1981
|
+
this.tags = {};
|
|
1982
|
+
this.extra = {};
|
|
1983
|
+
this.contexts = {};
|
|
1984
|
+
this.user = {};
|
|
1985
|
+
this.crumbs.clear();
|
|
1986
|
+
}
|
|
1987
|
+
};
|
|
1988
|
+
var isError = (v) => v instanceof Error || !!v && typeof v === "object" && typeof v.message === "string" && typeof v.stack === "string";
|
|
1989
|
+
function describe(value) {
|
|
1990
|
+
if (typeof value === "string") return value;
|
|
1991
|
+
try {
|
|
1992
|
+
return JSON.stringify(value) ?? String(value);
|
|
1993
|
+
} catch {
|
|
1994
|
+
return String(value);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
function exceptionFromValue(error, source, handled, fallbackType = "Error") {
|
|
1998
|
+
const e = error;
|
|
1999
|
+
const ok = isError(error);
|
|
2000
|
+
return {
|
|
2001
|
+
type: (ok && e.name || fallbackType).slice(0, 200),
|
|
2002
|
+
message: (ok ? e.message : describe(error)).slice(0, 1e3),
|
|
2003
|
+
frames: ok ? fitPropertyFrames(parseReactNativeStack(e.stack)) : [],
|
|
2004
|
+
source,
|
|
2005
|
+
handled
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
function exceptionChain(error, main, mechanism) {
|
|
2009
|
+
const mainFrames = isError(error) ? parseReactNativeStack(error.stack) : main.frames;
|
|
2010
|
+
const chain = [{ type: main.type, value: main.message, mechanism: { type: mechanism, handled: main.handled !== false, exception_id: 0 }, ...mainFrames.length ? { stacktrace: { frames: toDetailFrames(mainFrames) } } : {} }];
|
|
2011
|
+
const seen = /* @__PURE__ */ new Set([error]);
|
|
2012
|
+
let cur = isError(error) ? error.cause : void 0;
|
|
2013
|
+
while (cur !== void 0 && cur !== null && !seen.has(cur) && chain.length < MAX_EXCEPTION_CHAIN) {
|
|
2014
|
+
seen.add(cur);
|
|
2015
|
+
const e = cur;
|
|
2016
|
+
const id = chain.length;
|
|
2017
|
+
const frames = isError(e) ? parseReactNativeStack(e.stack, 50) : [];
|
|
2018
|
+
chain.push({
|
|
2019
|
+
type: (isError(e) && e.name || "Error").slice(0, 200),
|
|
2020
|
+
value: (isError(e) ? e.message : describe(cur)).slice(0, 1e3),
|
|
2021
|
+
mechanism: { type: "chained", source: "cause", handled: true, exception_id: id, parent_id: id - 1 },
|
|
2022
|
+
...frames.length ? { stacktrace: { frames: toDetailFrames(frames) } } : {}
|
|
2023
|
+
});
|
|
2024
|
+
cur = isError(e) ? e.cause : void 0;
|
|
2025
|
+
}
|
|
2026
|
+
return chain.reverse();
|
|
2027
|
+
}
|
|
2028
|
+
var SENSITIVE_KEY = new RegExp(`${SENSITIVE_PROPERTY_PATTERN.source}|authorization|cookie|api[-_]?key`, "i");
|
|
2029
|
+
function normalizeValue(value, depth = 5, seen = /* @__PURE__ */ new Set()) {
|
|
2030
|
+
if (value === null || typeof value === "boolean") return value;
|
|
2031
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
|
|
2032
|
+
if (typeof value === "string") return value.slice(0, 5e3);
|
|
2033
|
+
if (typeof value === "bigint") return value.toString();
|
|
2034
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") return void 0;
|
|
2035
|
+
if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.toISOString() : String(value);
|
|
2036
|
+
if (isError(value)) return { name: value.name, message: value.message.slice(0, 1e3) };
|
|
2037
|
+
if (typeof value !== "object" || seen.has(value)) return "[Circular]";
|
|
2038
|
+
if (depth <= 0) return Array.isArray(value) ? "[Array]" : "[Object]";
|
|
2039
|
+
seen.add(value);
|
|
2040
|
+
try {
|
|
2041
|
+
if (Array.isArray(value)) return value.slice(0, MAX_DETAIL_FRAMES).map((v) => normalizeValue(v, depth - 1, seen) ?? null);
|
|
2042
|
+
const out = {};
|
|
2043
|
+
let n = 0;
|
|
2044
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2045
|
+
if (n++ >= 100) break;
|
|
2046
|
+
if (SENSITIVE_KEY.test(k)) out[k] = "[Filtered]";
|
|
2047
|
+
else {
|
|
2048
|
+
const nv = normalizeValue(v, depth - 1, seen);
|
|
2049
|
+
if (nv !== void 0) out[k] = nv;
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
return out;
|
|
2053
|
+
} finally {
|
|
2054
|
+
seen.delete(value);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
var record = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
2058
|
+
var jsonSize = (v) => {
|
|
2059
|
+
try {
|
|
2060
|
+
return JSON.stringify(v)?.length ?? 0;
|
|
2061
|
+
} catch {
|
|
2062
|
+
return Infinity;
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
2065
|
+
function finalizeDetail(detail, max = MAX_EXCEPTION_DETAIL_BYTES) {
|
|
2066
|
+
let d = normalizeValue(detail, 12);
|
|
2067
|
+
if (!d || typeof d !== "object") return void 0;
|
|
2068
|
+
if (d.tags) d.tags = normalizeTags(Object.fromEntries(Object.entries(d.tags).filter(([k]) => !SENSITIVE_KEY.test(k))));
|
|
2069
|
+
d = trimExceptionDetail(d, max);
|
|
2070
|
+
if (jsonSize(d) <= max) return d;
|
|
2071
|
+
const slim = { ...d.level ? { level: d.level } : {}, ...d.exception ? { exception: d.exception } : {}, ...d.tags ? { tags: d.tags } : {}, ...d.fingerprint ? { fingerprint: d.fingerprint } : {}, ...d.release ? { release: d.release } : {}, ...d.dist ? { dist: d.dist } : {} };
|
|
2072
|
+
return jsonSize(slim) <= max ? slim : void 0;
|
|
2073
|
+
}
|
|
2074
|
+
function buildDetail(input) {
|
|
2075
|
+
const { scope, ctx } = input;
|
|
2076
|
+
const contexts = { ...input.contexts, ...scope.contexts };
|
|
2077
|
+
for (const [k, v] of Object.entries(record(ctx.contexts))) {
|
|
2078
|
+
if (v && typeof v === "object") contexts[k] = v;
|
|
2079
|
+
else delete contexts[k];
|
|
2080
|
+
}
|
|
2081
|
+
const user = { ...input.userId ? { id: input.userId } : {}, ...scope.user, ...record(ctx.user) };
|
|
2082
|
+
const message = input.props.type === "Message";
|
|
2083
|
+
return {
|
|
2084
|
+
level: input.level,
|
|
2085
|
+
platform: "javascript",
|
|
2086
|
+
timestamp: nowSeconds(),
|
|
2087
|
+
...input.release ? { release: input.release } : {},
|
|
2088
|
+
...input.dist ? { dist: input.dist } : {},
|
|
2089
|
+
...input.environment ? { environment: input.environment } : {},
|
|
2090
|
+
...message ? { message: { formatted: input.props.message } } : { exception: { values: exceptionChain(input.error, input.props, input.mechanism) } },
|
|
2091
|
+
breadcrumbs: { values: scope.breadcrumbs() },
|
|
2092
|
+
contexts,
|
|
2093
|
+
tags: normalizeTags({ ...scope.tags, ...record(ctx.tags) }),
|
|
2094
|
+
extra: { ...scope.extra, ...record(ctx.extra) },
|
|
2095
|
+
...Object.keys(user).length ? { user } : {},
|
|
2096
|
+
...Array.isArray(ctx.fingerprint) && ctx.fingerprint.length ? { fingerprint: ctx.fingerprint.map(String) } : {},
|
|
2097
|
+
sdk: input.sdk
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
function prepareError(props, extraProperties, detail, opts, fatal, release) {
|
|
2101
|
+
const file = props.frames.find((f) => f.file)?.file;
|
|
2102
|
+
if (opts && errorFilterDrops({ type: props.type, message: props.message, file }, opts)) return null;
|
|
2103
|
+
if (typeof opts?.sampleRate === "number" && opts.sampleRate < 1 && Math.random() >= Math.max(0, opts.sampleRate)) return null;
|
|
2104
|
+
const values = detail.exception?.values ?? [];
|
|
2105
|
+
const thrown = values[values.length - 1];
|
|
2106
|
+
const mechanism = thrown?.mechanism?.type ?? props.source;
|
|
2107
|
+
let properties = { ...props, level: detail.level ?? "error", platform: "javascript", ...mechanism ? { mechanism } : {} };
|
|
2108
|
+
if (release) properties.release = release.slice(0, 100);
|
|
2109
|
+
if (detail.fingerprint?.length) properties.fingerprintParts = detail.fingerprint.slice(0, 20);
|
|
2110
|
+
for (const [k, v] of Object.entries(extraProperties)) if (!(k in properties)) properties[k] = v;
|
|
2111
|
+
let d = detail;
|
|
2112
|
+
if (opts?.beforeSend) {
|
|
2113
|
+
try {
|
|
2114
|
+
const out = opts.beforeSend({ properties, detail: d });
|
|
2115
|
+
if (out === null) return null;
|
|
2116
|
+
if (out && typeof out === "object") {
|
|
2117
|
+
if (out.properties && typeof out.properties === "object") properties = out.properties;
|
|
2118
|
+
if (out.detail && typeof out.detail === "object") d = out.detail;
|
|
2119
|
+
}
|
|
2120
|
+
} catch {
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
const finalDetail = finalizeDetail(d);
|
|
2124
|
+
return { eventId: randomId(24), occurredAt: (/* @__PURE__ */ new Date()).toISOString(), properties, ...finalDetail ? { detail: finalDetail } : {}, fatal };
|
|
2125
|
+
}
|
|
2126
|
+
var ExceptionThrottle = class {
|
|
2127
|
+
constructor(max = 5) {
|
|
2128
|
+
__publicField(this, "max", max);
|
|
2129
|
+
__publicField(this, "seen", /* @__PURE__ */ new Map());
|
|
2130
|
+
}
|
|
2131
|
+
allow(props) {
|
|
2132
|
+
const frames = Array.isArray(props.frames) ? props.frames : [];
|
|
2133
|
+
const top = frames[0];
|
|
2134
|
+
const key = `${String(props.type)}|${String(props.message)}|${top ? `${top.file}:${top.line}:${top.col}` : ""}`;
|
|
2135
|
+
const n = this.seen.get(key) ?? 0;
|
|
2136
|
+
if (n >= this.max) return false;
|
|
2137
|
+
if (this.seen.size > 500) this.seen.clear();
|
|
2138
|
+
this.seen.set(key, n + 1);
|
|
2139
|
+
return true;
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
function rnVersion(v) {
|
|
2143
|
+
if (!v) return void 0;
|
|
2144
|
+
if (typeof v === "string") return v;
|
|
2145
|
+
if (typeof v.major !== "number") return void 0;
|
|
2146
|
+
return `${v.major}.${v.minor ?? 0}.${v.patch ?? 0}${v.prerelease ? `-${v.prerelease}` : ""}`;
|
|
2147
|
+
}
|
|
2148
|
+
function reactNativeContexts(device, app, g) {
|
|
2149
|
+
const runtime = g ? runtimeContext(g) : void 0;
|
|
2150
|
+
const version = rnVersion(device.reactNativeVersion);
|
|
2151
|
+
return {
|
|
2152
|
+
os: { name: device.platform === "android" ? "Android" : "iOS", ...device.osVersion ? { version: device.osVersion } : {} },
|
|
2153
|
+
device: {
|
|
2154
|
+
family: device.deviceType === "tablet" ? "Tablet" : "Mobile",
|
|
2155
|
+
...device.model ? { model: device.model } : {},
|
|
2156
|
+
...device.manufacturer ? { manufacturer: device.manufacturer } : {},
|
|
2157
|
+
...device.brand ? { brand: device.brand } : {},
|
|
2158
|
+
...device.simulator !== void 0 ? { simulator: device.simulator } : {}
|
|
2159
|
+
},
|
|
2160
|
+
app: {
|
|
2161
|
+
...app.appVersion ? { app_version: app.appVersion } : {},
|
|
2162
|
+
...app.build ? { app_build: app.build } : {},
|
|
2163
|
+
...app.inForeground !== void 0 ? { in_foreground: app.inForeground } : {}
|
|
2164
|
+
},
|
|
2165
|
+
...runtime ? { runtime } : {},
|
|
2166
|
+
react_native_context: {
|
|
2167
|
+
...runtime ? { js_engine: runtime.name } : {},
|
|
2168
|
+
...version ? { react_native_version: version } : {},
|
|
2169
|
+
...g ? { turbo_module: !!g.__turboModuleProxy || !!g.RN$Bridgeless, fabric: !!g.nativeFabricUIManager, expo: !!g.expo } : {}
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
|
|
1480
2174
|
// src/core/targets.ts
|
|
1481
2175
|
var TargetTimeoutError = class extends Error {
|
|
1482
2176
|
constructor(ids) {
|
|
@@ -2807,38 +3501,38 @@ var SessionRecorder = class {
|
|
|
2807
3501
|
this.capturing = true;
|
|
2808
3502
|
this.captureSoon = false;
|
|
2809
3503
|
const gen = this.generation;
|
|
2810
|
-
let
|
|
3504
|
+
let frame2 = null;
|
|
2811
3505
|
try {
|
|
2812
|
-
|
|
3506
|
+
frame2 = await this.opts.capturer(await this.opts.captureOptions());
|
|
2813
3507
|
} catch (err) {
|
|
2814
3508
|
this.opts.onError?.(err);
|
|
2815
3509
|
}
|
|
2816
3510
|
if (gen !== this.generation) return;
|
|
2817
3511
|
this.capturing = false;
|
|
2818
|
-
if (
|
|
3512
|
+
if (frame2) this.onFrame(frame2, this.now());
|
|
2819
3513
|
if (this.sid !== null && this.foreground) this.schedule(this.captureSoon ? TOUCH_SETTLE_MS : this.currentIntervalMs);
|
|
2820
3514
|
}
|
|
2821
|
-
onFrame(
|
|
3515
|
+
onFrame(frame2, at) {
|
|
2822
3516
|
const id = this.sid;
|
|
2823
|
-
if (id === null || !(
|
|
2824
|
-
const sizeChanged =
|
|
3517
|
+
if (id === null || !(frame2.width > 0) || !(frame2.height > 0) || !frame2.base64) return;
|
|
3518
|
+
const sizeChanged = frame2.width !== this.viewportWidth || frame2.height !== this.viewportHeight;
|
|
2825
3519
|
const keyframe = this.needKeyframe || !this.hasKeyframe || sizeChanged;
|
|
2826
|
-
if (!keyframe && this.lastFrame ===
|
|
3520
|
+
if (!keyframe && this.lastFrame === frame2.base64) {
|
|
2827
3521
|
this.unchanged++;
|
|
2828
3522
|
return;
|
|
2829
3523
|
}
|
|
2830
3524
|
this.unchanged = 0;
|
|
2831
|
-
this.lastFrame =
|
|
2832
|
-
this.originX =
|
|
2833
|
-
this.originY =
|
|
2834
|
-
this.viewportWidth =
|
|
2835
|
-
this.viewportHeight =
|
|
2836
|
-
const url = RrwebEvents.dataUrl(
|
|
3525
|
+
this.lastFrame = frame2.base64;
|
|
3526
|
+
this.originX = frame2.originX ?? 0;
|
|
3527
|
+
this.originY = frame2.originY ?? 0;
|
|
3528
|
+
this.viewportWidth = frame2.width;
|
|
3529
|
+
this.viewportHeight = frame2.height;
|
|
3530
|
+
const url = RrwebEvents.dataUrl(frame2.base64);
|
|
2837
3531
|
const up = this.opts.uploader;
|
|
2838
3532
|
if (!keyframe) return up.push(id, RrwebEvents.frame(at, url));
|
|
2839
3533
|
this.needKeyframe = false;
|
|
2840
|
-
up.push(id, RrwebEvents.meta(at, this.opts.href(),
|
|
2841
|
-
up.push(id, RrwebEvents.fullSnapshot(at, url,
|
|
3534
|
+
up.push(id, RrwebEvents.meta(at, this.opts.href(), frame2.width, frame2.height));
|
|
3535
|
+
up.push(id, RrwebEvents.fullSnapshot(at, url, frame2.width, frame2.height));
|
|
2842
3536
|
if (!this.hasKeyframe) {
|
|
2843
3537
|
this.hasKeyframe = true;
|
|
2844
3538
|
for (const [ts, payload] of this.pendingCustom) up.push(id, RrwebEvents.custom(Math.max(ts, at), "guidely", payload));
|
|
@@ -2900,7 +3594,7 @@ var PersistentStore = class {
|
|
|
2900
3594
|
|
|
2901
3595
|
// src/core/client.ts
|
|
2902
3596
|
var SDK_NAME = "@guidelytech/react-native";
|
|
2903
|
-
var SDK_VERSION = "0.
|
|
3597
|
+
var SDK_VERSION = "0.17.0";
|
|
2904
3598
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
2905
3599
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2906
3600
|
var CACHED_START_MS = 1500;
|
|
@@ -2915,6 +3609,10 @@ function settlesWithin(promise, ms) {
|
|
|
2915
3609
|
});
|
|
2916
3610
|
}
|
|
2917
3611
|
var appVersionKey = (key) => `guidely:app:${key}`;
|
|
3612
|
+
var crashKey = (key) => `guidely:crash:${key}`;
|
|
3613
|
+
var earlyCrashKey = (key) => `guidely:crash-early:${key}`;
|
|
3614
|
+
var MAX_STORED_CRASHES = 10;
|
|
3615
|
+
var MAX_SESSION_EXCEPTIONS = 50;
|
|
2918
3616
|
var UNKNOWN_VIEW = { selector: "View", tag: "View" };
|
|
2919
3617
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2920
3618
|
var emptyState = () => ({ guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] });
|
|
@@ -2931,6 +3629,8 @@ var GuidelyClient = class {
|
|
|
2931
3629
|
/** Survey answers, guide completions/dismissals and checklist progress, kept until the server has them. */
|
|
2932
3630
|
__publicField(this, "outbox", null);
|
|
2933
3631
|
__publicField(this, "config", null);
|
|
3632
|
+
/** The `environment` option as passed (the resolved one is in `options.environment`). */
|
|
3633
|
+
__publicField(this, "envOption");
|
|
2934
3634
|
__publicField(this, "configFetchedAt", 0);
|
|
2935
3635
|
__publicField(this, "etag", null);
|
|
2936
3636
|
__publicField(this, "anonymousId", "");
|
|
@@ -2952,6 +3652,8 @@ var GuidelyClient = class {
|
|
|
2952
3652
|
__publicField(this, "expanded", /* @__PURE__ */ new Set());
|
|
2953
3653
|
__publicField(this, "surveyView", null);
|
|
2954
3654
|
__publicField(this, "resourceCenterOpen", false);
|
|
3655
|
+
/** The article a help-article link asked the resource center to show (`seq` tells repeats apart). */
|
|
3656
|
+
__publicField(this, "resourceCenterArticle", null);
|
|
2955
3657
|
__publicField(this, "cleanups", []);
|
|
2956
3658
|
__publicField(this, "timers", /* @__PURE__ */ new Set());
|
|
2957
3659
|
__publicField(this, "visibilityWatchers", /* @__PURE__ */ new Map());
|
|
@@ -2992,6 +3694,16 @@ var GuidelyClient = class {
|
|
|
2992
3694
|
__publicField(this, "localizedRc", null);
|
|
2993
3695
|
/** "What's new" last-seen time when the resource center opened (entries after it read as new). */
|
|
2994
3696
|
__publicField(this, "announcementsSeenAtOpen", null);
|
|
3697
|
+
// Error monitoring.
|
|
3698
|
+
__publicField(this, "errorScope", new ErrorScope(() => this.options.errors));
|
|
3699
|
+
__publicField(this, "exceptionThrottle", new ExceptionThrottle());
|
|
3700
|
+
/** Errors captured before storage and a config were loaded (whether error tracking is on is unknown until then). */
|
|
3701
|
+
__publicField(this, "earlyErrors", []);
|
|
3702
|
+
/** Crash reports left by earlier launches, sent once a config says error tracking is on. */
|
|
3703
|
+
__publicField(this, "storedCrashes", []);
|
|
3704
|
+
/** This launch's crash reports (normally one: the app ends right after). */
|
|
3705
|
+
__publicField(this, "launchCrashes", []);
|
|
3706
|
+
__publicField(this, "lastAppState", null);
|
|
2995
3707
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
2996
3708
|
__publicField(this, "eventBus", {
|
|
2997
3709
|
on: (name, cb) => {
|
|
@@ -3005,9 +3717,10 @@ var GuidelyClient = class {
|
|
|
3005
3717
|
}
|
|
3006
3718
|
});
|
|
3007
3719
|
if (!options || !options.projectId) throw new Error("[Guidely] `projectId` (your environment public key, e.g. pk_dev_\u2026) is required.");
|
|
3720
|
+
this.envOption = options.environment || void 0;
|
|
3008
3721
|
this.options = {
|
|
3009
3722
|
...options,
|
|
3010
|
-
environment: options.environment
|
|
3723
|
+
environment: options.environment || environmentFromKey(options.projectId),
|
|
3011
3724
|
apiUrl: options.apiUrl ?? DEFAULT_API_URL,
|
|
3012
3725
|
analytics: options.analytics ?? true,
|
|
3013
3726
|
configRefreshMs: options.configRefreshMs ?? 5 * 60 * 1e3,
|
|
@@ -3023,6 +3736,7 @@ var GuidelyClient = class {
|
|
|
3023
3736
|
if (options.user) this.user = { ...options.user };
|
|
3024
3737
|
this.locale = options.locale ? normalizeLocale(options.locale) || null : null;
|
|
3025
3738
|
this.flags = this.createFlags(options.flags);
|
|
3739
|
+
this.installErrorMonitoring();
|
|
3026
3740
|
}
|
|
3027
3741
|
createFlags(opts) {
|
|
3028
3742
|
const base = { sdkKey: this.options.projectId, apiUrl: this.options.apiUrl, fetch: this.options.fetch, sdk: { name: SDK_NAME, version: SDK_VERSION } };
|
|
@@ -3070,7 +3784,7 @@ var GuidelyClient = class {
|
|
|
3070
3784
|
async bootstrap() {
|
|
3071
3785
|
const key = this.options.projectId;
|
|
3072
3786
|
try {
|
|
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]);
|
|
3787
|
+
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, crashKey(key), earlyCrashKey(key)]);
|
|
3074
3788
|
if (this.destroyed) return;
|
|
3075
3789
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
3076
3790
|
this.hadIdentity = !!this.anonymousId;
|
|
@@ -3083,7 +3797,9 @@ var GuidelyClient = class {
|
|
|
3083
3797
|
this.state = { ...emptyState(), ...readJson(this.store, KEYS.state(key), {}) };
|
|
3084
3798
|
this.user = this.options.user ? { ...this.options.user } : readJson(this.store, KEYS.user(key), null);
|
|
3085
3799
|
const saved = readJson(this.store, KEYS.session, null);
|
|
3086
|
-
this.
|
|
3800
|
+
this.loadStoredCrashes();
|
|
3801
|
+
const crashed = !!saved?.id && this.storedCrashes.some((c) => c.sessionId === saved.id);
|
|
3802
|
+
this.session = saved?.id && !crashed && !this.sessionExpired(Date.parse(saved.lastActiveAt)) ? saved : newSession();
|
|
3087
3803
|
this.persistSession();
|
|
3088
3804
|
this.lastAudiences = new Set(this.state.audienceIds);
|
|
3089
3805
|
this.syncFlagAttributes();
|
|
@@ -3102,9 +3818,10 @@ var GuidelyClient = class {
|
|
|
3102
3818
|
this.hydrated = true;
|
|
3103
3819
|
const cached = readJson(this.store, KEYS.config(key), null);
|
|
3104
3820
|
this.etag = this.store.get(KEYS.etag(key));
|
|
3105
|
-
if (cached && Array.isArray(cached.guides)) this.
|
|
3821
|
+
if (cached && Array.isArray(cached.guides)) this.useConfig(forPlatform(cached, this.bridge.device.platform));
|
|
3106
3822
|
this.syncMessenger();
|
|
3107
3823
|
this.pending.splice(0).forEach((fn) => fn());
|
|
3824
|
+
this.drainErrors();
|
|
3108
3825
|
const before = this.config?.configVersion;
|
|
3109
3826
|
const fresh = this.fetchConfig();
|
|
3110
3827
|
if (!this.config) await fresh;
|
|
@@ -3129,22 +3846,33 @@ var GuidelyClient = class {
|
|
|
3129
3846
|
if (this.hydrated) fn();
|
|
3130
3847
|
else this.pending.push(fn);
|
|
3131
3848
|
}
|
|
3849
|
+
/** Environment guides and flags target: the `environment` option, else the one the loaded config names, else a guess from the key. */
|
|
3850
|
+
get environment() {
|
|
3851
|
+
return this.envOption ?? this.config?.environment ?? this.options.environment;
|
|
3852
|
+
}
|
|
3853
|
+
useConfig(config) {
|
|
3854
|
+
const env = this.environment;
|
|
3855
|
+
this.config = config;
|
|
3856
|
+
if (this.environment !== env) this.syncFlagAttributes();
|
|
3857
|
+
}
|
|
3132
3858
|
async fetchConfig() {
|
|
3133
3859
|
try {
|
|
3134
3860
|
const res = await this.api.get("/config", { etag: this.etag, params: { platform: this.bridge.device.platform } });
|
|
3135
3861
|
this.configFetchedAt = Date.now();
|
|
3136
3862
|
if (res.status === 304) return true;
|
|
3137
3863
|
if (res.ok && res.data && Array.isArray(res.data.guides)) {
|
|
3138
|
-
|
|
3864
|
+
const config = forPlatform(res.data, this.bridge.device.platform);
|
|
3865
|
+
this.useConfig(config);
|
|
3139
3866
|
this.etag = res.etag;
|
|
3140
3867
|
writeJson(this.store, KEYS.config(this.options.projectId), res.data);
|
|
3141
3868
|
if (res.etag) this.store.set(KEYS.etag(this.options.projectId), res.etag);
|
|
3142
3869
|
else this.store.remove(KEYS.etag(this.options.projectId));
|
|
3143
3870
|
this.syncMessenger();
|
|
3144
3871
|
if (this.hydrated) this.checkLaunch();
|
|
3145
|
-
this.
|
|
3872
|
+
this.drainErrors();
|
|
3873
|
+
this.emitter.emit("config", config);
|
|
3146
3874
|
this.syncReplay();
|
|
3147
|
-
this.log("config loaded",
|
|
3875
|
+
this.log("config loaded", config.configVersion, `${config.guides.length} guides`);
|
|
3148
3876
|
return true;
|
|
3149
3877
|
}
|
|
3150
3878
|
this.reportError(new Error(`[Guidely] config request failed (${res.status})${this.config ? "; using cached config" : ""}`));
|
|
@@ -3185,7 +3913,12 @@ var GuidelyClient = class {
|
|
|
3185
3913
|
}
|
|
3186
3914
|
/** Called by the provider with React Native `AppState` changes. */
|
|
3187
3915
|
setAppState(state) {
|
|
3188
|
-
if (this.destroyed
|
|
3916
|
+
if (this.destroyed) return;
|
|
3917
|
+
if (state !== this.lastAppState) {
|
|
3918
|
+
this.lastAppState = state;
|
|
3919
|
+
this.autoBreadcrumb("appLifecycle", { type: "navigation", category: "app.lifecycle", message: state, data: { state } });
|
|
3920
|
+
}
|
|
3921
|
+
if (!this.hydrated) return;
|
|
3189
3922
|
if (state === "background") {
|
|
3190
3923
|
const wasForeground = this.backgroundedAt === null;
|
|
3191
3924
|
this.backgroundedAt = Date.now();
|
|
@@ -3370,8 +4103,13 @@ var GuidelyClient = class {
|
|
|
3370
4103
|
setConsent(consent) {
|
|
3371
4104
|
this.consent = consent;
|
|
3372
4105
|
this.whenHydrated(() => {
|
|
3373
|
-
if (consent === "denied")
|
|
3374
|
-
|
|
4106
|
+
if (consent === "denied") {
|
|
4107
|
+
this.queue?.clear();
|
|
4108
|
+
this.earlyErrors = [];
|
|
4109
|
+
this.storedCrashes = [];
|
|
4110
|
+
this.launchCrashes = [];
|
|
4111
|
+
void this.writeCrashes();
|
|
4112
|
+
} else if (consent === "granted") void this.queue?.flush();
|
|
3375
4113
|
this.syncReplay();
|
|
3376
4114
|
});
|
|
3377
4115
|
}
|
|
@@ -3395,6 +4133,7 @@ var GuidelyClient = class {
|
|
|
3395
4133
|
accountId: this.user?.account?.id || null,
|
|
3396
4134
|
sessionId: this.session.id,
|
|
3397
4135
|
url: this.collectUrls() && this.screenName ? this.currentUrl() : void 0,
|
|
4136
|
+
...this.appInfo().appVersion ? { appVersion: this.appInfo().appVersion } : {},
|
|
3398
4137
|
...extra
|
|
3399
4138
|
};
|
|
3400
4139
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
@@ -3434,6 +4173,7 @@ var GuidelyClient = class {
|
|
|
3434
4173
|
this.whenHydrated(() => {
|
|
3435
4174
|
const changed = name !== this.screenName;
|
|
3436
4175
|
if (changed) {
|
|
4176
|
+
this.autoBreadcrumb("navigation", { type: "navigation", category: "navigation", data: { ...this.screenName ? { from: this.screenName } : {}, to: name } });
|
|
3437
4177
|
this.leaveScreen();
|
|
3438
4178
|
this.flushHeatmap();
|
|
3439
4179
|
}
|
|
@@ -3597,7 +4337,11 @@ var GuidelyClient = class {
|
|
|
3597
4337
|
}
|
|
3598
4338
|
/** True when taps are worth observing (clicks, rage taps or heatmaps are on); the provider skips resolving views otherwise. */
|
|
3599
4339
|
isTapCaptureOn() {
|
|
3600
|
-
return this.hydrated && (this.autocaptureOn("clicks") || this.autocaptureOn("rageClicks") || this.autocaptureOn("heatmaps"));
|
|
4340
|
+
return this.hydrated && (this.autocaptureOn("clicks") || this.autocaptureOn("rageClicks") || this.autocaptureOn("heatmaps") || this.tapBreadcrumbsOn());
|
|
4341
|
+
}
|
|
4342
|
+
/** Taps become `ui.click` breadcrumbs while errors can be reported (even with click autocapture off). */
|
|
4343
|
+
tapBreadcrumbsOn() {
|
|
4344
|
+
return this.crumbsOn("taps") && this.errorTrackingAllowed();
|
|
3601
4345
|
}
|
|
3602
4346
|
/**
|
|
3603
4347
|
* Records a tap observed by the provider: `click` for an interactive element, `rage_click` for
|
|
@@ -3607,8 +4351,8 @@ var GuidelyClient = class {
|
|
|
3607
4351
|
recordTap(tap) {
|
|
3608
4352
|
try {
|
|
3609
4353
|
if (!this.hydrated || this.destroyed || !Number.isFinite(tap.x) || !Number.isFinite(tap.y)) return;
|
|
3610
|
-
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps") };
|
|
3611
|
-
if (!on.clicks && !on.rage && !on.heat) return;
|
|
4354
|
+
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps"), crumb: this.tapBreadcrumbsOn() };
|
|
4355
|
+
if (!on.clicks && !on.rage && !on.heat && !on.crumb) return;
|
|
3612
4356
|
const t = tap.t ?? Date.now();
|
|
3613
4357
|
const url = this.screenName ? this.currentUrl() : "";
|
|
3614
4358
|
this.tapChain = this.tapChain.then(() => this.processTap(tap, t, url, on)).catch((err) => this.reportError(err));
|
|
@@ -3625,6 +4369,8 @@ var GuidelyClient = class {
|
|
|
3625
4369
|
rect = target.rect;
|
|
3626
4370
|
}
|
|
3627
4371
|
if (this.destroyed) return;
|
|
4372
|
+
if (on.crumb && el) this.autoBreadcrumb("taps", { timestamp: t / 1e3, type: "user", category: "ui.click", message: el.selector, data: { selector: el.selector } });
|
|
4373
|
+
if (!on.clicks && !on.rage && !on.heat) return;
|
|
3628
4374
|
const platform = this.bridge.device.platform;
|
|
3629
4375
|
const maskAll = !!this.config?.settings?.replay?.maskAllText || !!this.options.sessionReplay?.maskAllText;
|
|
3630
4376
|
const eventUrl = this.collectUrls() && url ? url : void 0;
|
|
@@ -3723,6 +4469,277 @@ var GuidelyClient = class {
|
|
|
3723
4469
|
}
|
|
3724
4470
|
}
|
|
3725
4471
|
// ---------------------------------------------------------------------------
|
|
4472
|
+
// Error monitoring (docs/error-monitoring/sdks.md)
|
|
4473
|
+
// ---------------------------------------------------------------------------
|
|
4474
|
+
/**
|
|
4475
|
+
* Reports a caught error as an `exception` event (`handled: true`), grouped into an issue on the
|
|
4476
|
+
* Errors page. The second argument takes Sentry's capture context (`tags`, `extra`, `level`,
|
|
4477
|
+
* `fingerprint`, `contexts`, `user`); any other key is attached to the occurrence as a property.
|
|
4478
|
+
* Needs the project's error tracking on (plus analytics and consent); `errors.captureErrors: false`
|
|
4479
|
+
* does not stop it. Returns the event id (empty when nothing is sent).
|
|
4480
|
+
*/
|
|
4481
|
+
captureException(error, hint) {
|
|
4482
|
+
if (this.destroyed) return "";
|
|
4483
|
+
try {
|
|
4484
|
+
const { ctx, properties } = splitHint(hint);
|
|
4485
|
+
const props = exceptionFromValue(error, "manual", true);
|
|
4486
|
+
return this.deliverError(this.prepareCapture(error, props, properties, ctx, "generic", ctx.level ?? "error", false));
|
|
4487
|
+
} catch (err) {
|
|
4488
|
+
this.reportError(err);
|
|
4489
|
+
return "";
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
/** Reports a message (default level `info`) as its own issue, grouped by the message. */
|
|
4493
|
+
captureMessage(message, level = "info", hint) {
|
|
4494
|
+
if (this.destroyed) return "";
|
|
4495
|
+
try {
|
|
4496
|
+
const { ctx } = splitHint(hint);
|
|
4497
|
+
const props = { type: "Message", message: String(message).slice(0, 1e3), frames: [], source: "message", handled: true };
|
|
4498
|
+
return this.deliverError(this.prepareCapture(void 0, props, {}, ctx, "message", ctx.level ?? level, false));
|
|
4499
|
+
} catch (err) {
|
|
4500
|
+
this.reportError(err);
|
|
4501
|
+
return "";
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
/** Adds a breadcrumb (sent with the next errors; the last 100 are kept). */
|
|
4505
|
+
addBreadcrumb(crumb, hint) {
|
|
4506
|
+
if (!this.destroyed) this.errorScope.addBreadcrumb(crumb, hint);
|
|
4507
|
+
}
|
|
4508
|
+
/** Tags every later error (searchable on the issue page); `null` removes the tag. */
|
|
4509
|
+
setTag(key, value) {
|
|
4510
|
+
if (value === null || value === void 0) delete this.errorScope.tags[key];
|
|
4511
|
+
else this.errorScope.tags[key] = String(value);
|
|
4512
|
+
}
|
|
4513
|
+
setTags(tags) {
|
|
4514
|
+
for (const [k, v] of Object.entries(tags ?? {})) this.setTag(k, v);
|
|
4515
|
+
}
|
|
4516
|
+
/** Arbitrary data attached to every later error. */
|
|
4517
|
+
setExtra(key, value) {
|
|
4518
|
+
if (value === void 0) delete this.errorScope.extra[key];
|
|
4519
|
+
else this.errorScope.extra[key] = value;
|
|
4520
|
+
}
|
|
4521
|
+
setExtras(extras) {
|
|
4522
|
+
for (const [k, v] of Object.entries(extras ?? {})) this.setExtra(k, v);
|
|
4523
|
+
}
|
|
4524
|
+
/** A named context (`setContext("cart", { items: 3 })`) on every later error; `null` removes it. */
|
|
4525
|
+
setContext(name, context) {
|
|
4526
|
+
if (context && typeof context === "object") this.errorScope.contexts[name] = context;
|
|
4527
|
+
else delete this.errorScope.contexts[name];
|
|
4528
|
+
}
|
|
4529
|
+
/**
|
|
4530
|
+
* Sentry's `setUser`: identifies the user by `id` (like `identify`) and keeps `email` / `username`
|
|
4531
|
+
* on their errors. `null` forgets the user (`reset()`).
|
|
4532
|
+
*/
|
|
4533
|
+
setUser(user) {
|
|
4534
|
+
if (!user) {
|
|
4535
|
+
this.errorScope.user = {};
|
|
4536
|
+
return this.reset();
|
|
4537
|
+
}
|
|
4538
|
+
const { id, email, username, ...traits } = user;
|
|
4539
|
+
this.errorScope.user = { ...email ? { email: String(email) } : {}, ...username ? { username: String(username) } : {} };
|
|
4540
|
+
if (id !== void 0 && id !== null && String(id)) void this.identify(String(id), { ...traits, ...email ? { email } : {}, ...username ? { username } : {} });
|
|
4541
|
+
}
|
|
4542
|
+
crumbsOn(kind) {
|
|
4543
|
+
const b = this.options.errors?.breadcrumbs;
|
|
4544
|
+
return b !== false && !(b && b[kind] === false);
|
|
4545
|
+
}
|
|
4546
|
+
autoBreadcrumb(kind, crumb, hint) {
|
|
4547
|
+
if (!this.destroyed && this.crumbsOn(kind)) this.errorScope.addBreadcrumb(crumb, hint);
|
|
4548
|
+
}
|
|
4549
|
+
/** Errors may be captured: analytics on, consent not denied and, once a config says, the project's error tracking on. */
|
|
4550
|
+
errorsWanted() {
|
|
4551
|
+
if (this.destroyed || !this.options.analytics || this.consent === "denied") return false;
|
|
4552
|
+
const s = this.config?.settings;
|
|
4553
|
+
return !this.config || (s?.analyticsEnabled ?? true) && !!s?.errorTracking?.enabled;
|
|
4554
|
+
}
|
|
4555
|
+
/** Errors may be queued now (a config is known and allows them). */
|
|
4556
|
+
errorTrackingAllowed() {
|
|
4557
|
+
return !!this.config && this.errorsWanted();
|
|
4558
|
+
}
|
|
4559
|
+
/** Requests to the Guidely deployment never become breadcrumbs. */
|
|
4560
|
+
isGuidelyUrl(url) {
|
|
4561
|
+
const base = this.options.apiUrl.replace(/\/+$/, "");
|
|
4562
|
+
return !!base && (url === base || url.startsWith(`${base}/`));
|
|
4563
|
+
}
|
|
4564
|
+
release() {
|
|
4565
|
+
return this.options.release ?? this.appInfo().appVersion;
|
|
4566
|
+
}
|
|
4567
|
+
/** Builds and filters one error (`null` when dropped). */
|
|
4568
|
+
prepareCapture(error, props, properties, ctx, mechanism, level, fatal) {
|
|
4569
|
+
if (!this.errorsWanted()) return null;
|
|
4570
|
+
const info = this.appInfo();
|
|
4571
|
+
const release = this.release();
|
|
4572
|
+
const detail = buildDetail({
|
|
4573
|
+
error,
|
|
4574
|
+
props,
|
|
4575
|
+
mechanism,
|
|
4576
|
+
scope: this.errorScope,
|
|
4577
|
+
ctx,
|
|
4578
|
+
level,
|
|
4579
|
+
contexts: reactNativeContexts(this.bridge.device, { ...info, inForeground: this.backgroundedAt === null }, this.bridge.globals),
|
|
4580
|
+
release,
|
|
4581
|
+
dist: this.options.errors?.dist ?? info.build,
|
|
4582
|
+
environment: this.environment,
|
|
4583
|
+
userId: this.user?.id || null,
|
|
4584
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION }
|
|
4585
|
+
});
|
|
4586
|
+
return prepareError(props, properties, detail, this.options.errors, fatal, release);
|
|
4587
|
+
}
|
|
4588
|
+
/** Queues a prepared error, or holds it until storage and a config are loaded. Returns its event id ("" when dropped). */
|
|
4589
|
+
deliverError(p) {
|
|
4590
|
+
if (!p) return "";
|
|
4591
|
+
if (!this.hydrated || !this.config) {
|
|
4592
|
+
if (this.earlyErrors.length >= 10) return "";
|
|
4593
|
+
this.earlyErrors.push(p);
|
|
4594
|
+
return p.eventId;
|
|
4595
|
+
}
|
|
4596
|
+
return this.sendError(p) ? p.eventId : "";
|
|
4597
|
+
}
|
|
4598
|
+
sendError(p) {
|
|
4599
|
+
if (!this.errorTrackingAllowed()) return false;
|
|
4600
|
+
if (!p.fatal && !this.exceptionThrottle.allow(p.properties)) return false;
|
|
4601
|
+
const count = this.session.exceptions ?? 0;
|
|
4602
|
+
if (!p.fatal && count >= MAX_SESSION_EXCEPTIONS) return false;
|
|
4603
|
+
const event = this.enqueue("exception", { eventId: p.eventId, occurredAt: p.occurredAt, properties: p.properties, ...p.detail ? { detail: p.detail } : {} });
|
|
4604
|
+
if (!event) return false;
|
|
4605
|
+
this.session.exceptions = count + 1;
|
|
4606
|
+
this.persistSession();
|
|
4607
|
+
return true;
|
|
4608
|
+
}
|
|
4609
|
+
/** Sends what waited for storage and a config: earlier launches' crash reports, then early errors. */
|
|
4610
|
+
drainErrors() {
|
|
4611
|
+
if (!this.hydrated || !this.config || this.destroyed) return;
|
|
4612
|
+
const stored = this.storedCrashes.splice(0);
|
|
4613
|
+
if (stored.length) {
|
|
4614
|
+
if (this.errorTrackingAllowed()) {
|
|
4615
|
+
for (const c of stored) this.queue?.enqueue({ ...c, anonymousId: c.anonymousId || this.anonymousId, sessionId: c.sessionId || this.session.id });
|
|
4616
|
+
}
|
|
4617
|
+
void this.writeCrashes();
|
|
4618
|
+
}
|
|
4619
|
+
this.earlyErrors.splice(0).forEach((p) => this.sendError(p));
|
|
4620
|
+
}
|
|
4621
|
+
loadStoredCrashes() {
|
|
4622
|
+
const key = this.options.projectId;
|
|
4623
|
+
const valid = (c) => !!c && typeof c === "object" && typeof c.eventId === "string" && c.name === "exception";
|
|
4624
|
+
const read2 = (k) => {
|
|
4625
|
+
const v = readJson(this.store, k, []);
|
|
4626
|
+
return Array.isArray(v) ? v.filter(valid) : [];
|
|
4627
|
+
};
|
|
4628
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4629
|
+
this.storedCrashes = [...read2(crashKey(key)), ...read2(earlyCrashKey(key))].filter((c) => !seen.has(c.eventId) && !!seen.add(c.eventId)).slice(-MAX_STORED_CRASHES);
|
|
4630
|
+
this.store.remove(earlyCrashKey(key));
|
|
4631
|
+
if (this.storedCrashes.length) void this.writeCrashes();
|
|
4632
|
+
}
|
|
4633
|
+
/**
|
|
4634
|
+
* Writes the unsent crash reports to storage, directly (not behind other pending writes) so the
|
|
4635
|
+
* write starts before the app ends. Resolves when the storage has it.
|
|
4636
|
+
*/
|
|
4637
|
+
writeCrashes() {
|
|
4638
|
+
const storage = this.options.storage;
|
|
4639
|
+
const key = this.options.projectId;
|
|
4640
|
+
const direct = (op) => {
|
|
4641
|
+
try {
|
|
4642
|
+
return Promise.resolve(op()).catch((err) => this.log("crash report storage error", err));
|
|
4643
|
+
} catch (err) {
|
|
4644
|
+
this.log("crash report storage error", err);
|
|
4645
|
+
return Promise.resolve();
|
|
4646
|
+
}
|
|
4647
|
+
};
|
|
4648
|
+
if (!this.hydrated) {
|
|
4649
|
+
const json2 = JSON.stringify(this.launchCrashes.slice(-MAX_STORED_CRASHES));
|
|
4650
|
+
return storage ? direct(() => storage.setItem(earlyCrashKey(key), json2)) : Promise.resolve();
|
|
4651
|
+
}
|
|
4652
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4653
|
+
const all = [...this.storedCrashes, ...this.launchCrashes].filter((c) => !seen.has(c.eventId) && !!seen.add(c.eventId)).slice(-MAX_STORED_CRASHES);
|
|
4654
|
+
if (!all.length) {
|
|
4655
|
+
this.store.remove(crashKey(key));
|
|
4656
|
+
return storage ? direct(() => storage.removeItem(crashKey(key))) : Promise.resolve();
|
|
4657
|
+
}
|
|
4658
|
+
const json = JSON.stringify(all);
|
|
4659
|
+
this.store.set(crashKey(key), json);
|
|
4660
|
+
return storage ? direct(() => storage.setItem(crashKey(key), json)) : Promise.resolve();
|
|
4661
|
+
}
|
|
4662
|
+
/**
|
|
4663
|
+
* A fatal JS error: React Native ends a release app right after the global handler, so the report
|
|
4664
|
+
* is written to storage at once (and sent on the next launch), and sent now as well when the
|
|
4665
|
+
* network allows. The promise tells the handler when React Native's own handler may run.
|
|
4666
|
+
*/
|
|
4667
|
+
saveCrash(p) {
|
|
4668
|
+
if (!this.errorsWanted()) return void 0;
|
|
4669
|
+
const info = this.appInfo();
|
|
4670
|
+
const event = {
|
|
4671
|
+
eventId: p.eventId,
|
|
4672
|
+
name: "exception",
|
|
4673
|
+
occurredAt: p.occurredAt,
|
|
4674
|
+
anonymousId: this.anonymousId,
|
|
4675
|
+
userId: this.user?.id || null,
|
|
4676
|
+
accountId: this.user?.account?.id || null,
|
|
4677
|
+
...this.hydrated ? { sessionId: this.session.id } : {},
|
|
4678
|
+
...this.collectUrls() && this.screenName ? { url: this.currentUrl() } : {},
|
|
4679
|
+
...info.appVersion ? { appVersion: info.appVersion } : {},
|
|
4680
|
+
properties: sanitizeProperties(p.properties),
|
|
4681
|
+
...p.detail ? { detail: p.detail } : {}
|
|
4682
|
+
};
|
|
4683
|
+
this.launchCrashes.push(event);
|
|
4684
|
+
const written = this.writeCrashes();
|
|
4685
|
+
if (!this.hydrated || !this.config || !this.sendError(p)) return written;
|
|
4686
|
+
const sent = this.queue?.flush() ?? Promise.resolve();
|
|
4687
|
+
return Promise.all([written, sent]).then(async () => {
|
|
4688
|
+
if (this.queue?.peek().some((e) => e.eventId === event.eventId)) return;
|
|
4689
|
+
this.launchCrashes = this.launchCrashes.filter((c) => c.eventId !== event.eventId);
|
|
4690
|
+
await this.writeCrashes();
|
|
4691
|
+
});
|
|
4692
|
+
}
|
|
4693
|
+
/** An uncaught JS error from `ErrorUtils` (fatal ones end the app in release builds). */
|
|
4694
|
+
captureGlobalError(error, fatal) {
|
|
4695
|
+
try {
|
|
4696
|
+
const props = exceptionFromValue(error, "onerror", false);
|
|
4697
|
+
if (props.message.startsWith("[Guidely]")) return void 0;
|
|
4698
|
+
const p = this.prepareCapture(error, props, {}, {}, "onerror", fatal ? "fatal" : "error", fatal);
|
|
4699
|
+
if (!p) return void 0;
|
|
4700
|
+
if (fatal) return this.saveCrash(p);
|
|
4701
|
+
this.deliverError(p);
|
|
4702
|
+
} catch (err) {
|
|
4703
|
+
this.log("error capture failed", err);
|
|
4704
|
+
}
|
|
4705
|
+
return void 0;
|
|
4706
|
+
}
|
|
4707
|
+
captureRejection(reason) {
|
|
4708
|
+
try {
|
|
4709
|
+
const props = exceptionFromValue(reason, "unhandledrejection", false, "UnhandledRejection");
|
|
4710
|
+
if (props.message.startsWith("[Guidely]")) return;
|
|
4711
|
+
this.deliverError(this.prepareCapture(reason, props, {}, {}, "unhandledrejection", "error", false));
|
|
4712
|
+
} catch (err) {
|
|
4713
|
+
this.log("error capture failed", err);
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
/**
|
|
4717
|
+
* Guide breadcrumbs, and (with the bridge's JS globals) the global error handler, promise
|
|
4718
|
+
* rejection tracking and console / network breadcrumbs. Undone by `destroy()`.
|
|
4719
|
+
*/
|
|
4720
|
+
installErrorMonitoring() {
|
|
4721
|
+
const guideCrumb = (message, guide, stepIndex) => this.autoBreadcrumb("guides", { type: "info", category: "guide", message, data: { guideId: guide.id, ...stepIndex !== void 0 ? { stepIndex } : {} } });
|
|
4722
|
+
this.cleanups.push(
|
|
4723
|
+
this.emitter.on("guide:start", (g2) => guideCrumb("Guide shown", g2)),
|
|
4724
|
+
this.emitter.on("guide:step", (s) => guideCrumb("Guide step", s.guide, s.stepIndex)),
|
|
4725
|
+
this.emitter.on("guide:complete", (g2) => guideCrumb("Guide completed", g2)),
|
|
4726
|
+
this.emitter.on("guide:dismiss", (g2) => guideCrumb("Guide dismissed", g2)),
|
|
4727
|
+
this.emitter.on("guide:skip", (g2) => guideCrumb("Guide skipped", g2))
|
|
4728
|
+
);
|
|
4729
|
+
const g = this.bridge.globals;
|
|
4730
|
+
if (!g) return;
|
|
4731
|
+
try {
|
|
4732
|
+
if (this.crumbsOn("console")) this.cleanups.push(instrumentConsole(g.console, (c) => this.autoBreadcrumb("console", c)));
|
|
4733
|
+
if (this.crumbsOn("http")) this.cleanups.push(instrumentHttp(g, (c) => this.autoBreadcrumb("http", c), (url) => this.isGuidelyUrl(url)));
|
|
4734
|
+
if (this.options.errors?.captureErrors !== false) {
|
|
4735
|
+
this.cleanups.push(installGlobalHandler(g, (error, fatal) => this.captureGlobalError(error, fatal)));
|
|
4736
|
+
this.cleanups.push(installRejectionTracking(g, (reason) => this.captureRejection(reason), this.options.errors?.rejectionTracker));
|
|
4737
|
+
}
|
|
4738
|
+
} catch (err) {
|
|
4739
|
+
this.reportError(err);
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
// ---------------------------------------------------------------------------
|
|
3726
4743
|
// Targeting + triggers
|
|
3727
4744
|
// ---------------------------------------------------------------------------
|
|
3728
4745
|
/** Builds the targeting context for the current moment. */
|
|
@@ -3748,7 +4765,7 @@ var GuidelyClient = class {
|
|
|
3748
4765
|
completedChecklists: new Set(keysOf(Object.entries(this.state.checklists).filter(([, p]) => !!p.completedAt).map(([id]) => id))),
|
|
3749
4766
|
audienceIds: new Set(this.state.audienceIds),
|
|
3750
4767
|
now: /* @__PURE__ */ new Date(),
|
|
3751
|
-
environment: this.
|
|
4768
|
+
environment: this.environment,
|
|
3752
4769
|
locale: device.locale,
|
|
3753
4770
|
platform: device.platform,
|
|
3754
4771
|
screen: this.screenName,
|
|
@@ -4117,6 +5134,8 @@ var GuidelyClient = class {
|
|
|
4117
5134
|
*/
|
|
4118
5135
|
navigate(url) {
|
|
4119
5136
|
if (!url) return;
|
|
5137
|
+
const articleId = articleIdFromHref(url);
|
|
5138
|
+
if (articleId) return this.openArticle(articleId);
|
|
4120
5139
|
try {
|
|
4121
5140
|
if (this.options.onNavigate?.(url) === true) return;
|
|
4122
5141
|
if (/^[a-z][a-z0-9+.-]*:/i.test(url)) this.bridge.openExternal(url);
|
|
@@ -4140,7 +5159,7 @@ var GuidelyClient = class {
|
|
|
4140
5159
|
for (const f of [...this.flows.values(), ...this.active ? [this.active] : []]) if (f.view) steps.push({ ...f.view, expanded: this.expanded.has(f.guide.id) });
|
|
4141
5160
|
const checklists = [];
|
|
4142
5161
|
this.checklists.forEach((w, id) => checklists.push({ guide: w.guide, progress: this.getChecklistProgress(id), open: w.open }));
|
|
4143
|
-
this.ui = { steps, checklists, survey: this.surveyView, resourceCenterOpen: this.resourceCenterOpen, theme: this.themeFor(), messenger: !!this.messenger };
|
|
5162
|
+
this.ui = { steps, checklists, survey: this.surveyView, resourceCenterOpen: this.resourceCenterOpen, resourceCenterArticle: this.resourceCenterArticle, theme: this.themeFor(), messenger: !!this.messenger };
|
|
4144
5163
|
for (const cb of Array.from(this.uiListeners)) {
|
|
4145
5164
|
try {
|
|
4146
5165
|
cb();
|
|
@@ -4160,6 +5179,12 @@ var GuidelyClient = class {
|
|
|
4160
5179
|
closeStep(guideId) {
|
|
4161
5180
|
this.flowFor(guideId)?.controller.dismiss();
|
|
4162
5181
|
}
|
|
5182
|
+
/** The video on a step was tapped: records `guide_video_clicked`, then opens the video outside the app. */
|
|
5183
|
+
openVideo(guideId, url) {
|
|
5184
|
+
const v = this.flowFor(guideId)?.view;
|
|
5185
|
+
if (v) this.enqueue("guide_video_clicked", { guideId: v.guide.id, guideVersion: v.guide.version, stepId: v.step.id, stepIndex: v.index, properties: { videoUrl: url } });
|
|
5186
|
+
this.navigate(url);
|
|
5187
|
+
}
|
|
4163
5188
|
/** The dimmed backdrop behind a step was tapped. */
|
|
4164
5189
|
backdropPressed(guideId) {
|
|
4165
5190
|
const f = this.flowFor(guideId);
|
|
@@ -4432,10 +5457,40 @@ var GuidelyClient = class {
|
|
|
4432
5457
|
this.emitter.emit("resource-center:open");
|
|
4433
5458
|
this.notify();
|
|
4434
5459
|
}
|
|
5460
|
+
/**
|
|
5461
|
+
* Opens one help article: in Scout's Help tab when the messenger is on, otherwise in the resource
|
|
5462
|
+
* center. Help-article links in guides and articles (`#guidely-article:<id>`) call this.
|
|
5463
|
+
*/
|
|
5464
|
+
openArticle(articleId) {
|
|
5465
|
+
if (this.messenger) {
|
|
5466
|
+
this.whenHydrated(() => void this.messenger?.openArticle(articleId));
|
|
5467
|
+
return;
|
|
5468
|
+
}
|
|
5469
|
+
if (!this.getResourceCenter()) {
|
|
5470
|
+
this.log("openArticle: the resource center is not enabled or published for this environment");
|
|
5471
|
+
return;
|
|
5472
|
+
}
|
|
5473
|
+
this.resourceCenterArticle = { id: articleId, seq: (this.resourceCenterArticle?.seq ?? 0) + 1 };
|
|
5474
|
+
if (this.resourceCenterOpen) this.notify();
|
|
5475
|
+
else this.openResourceCenter();
|
|
5476
|
+
}
|
|
5477
|
+
/** A resource-center article with its body: from the config, or fetched when the config left it out. */
|
|
5478
|
+
async loadArticle(articleId) {
|
|
5479
|
+
const listed = this.getResourceCenter()?.articles.find((a) => a.id === articleId);
|
|
5480
|
+
if (listed && !listed.bodyOmitted) return listed;
|
|
5481
|
+
try {
|
|
5482
|
+
const locale = this.articleLocale();
|
|
5483
|
+
const res = await this.api.get(`/articles/${encodeURIComponent(articleId)}`, locale ? { params: { locale } } : {});
|
|
5484
|
+
return res.ok && res.data ? res.data.article : null;
|
|
5485
|
+
} catch {
|
|
5486
|
+
return null;
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
4435
5489
|
closeResourceCenter() {
|
|
4436
5490
|
if (this.messenger) return this.closeMessenger();
|
|
4437
5491
|
if (!this.resourceCenterOpen) return;
|
|
4438
5492
|
this.resourceCenterOpen = false;
|
|
5493
|
+
this.resourceCenterArticle = null;
|
|
4439
5494
|
this.notify();
|
|
4440
5495
|
}
|
|
4441
5496
|
// ---------------------------------------------------------------------------
|
|
@@ -4531,7 +5586,7 @@ var GuidelyClient = class {
|
|
|
4531
5586
|
locale: d.locale,
|
|
4532
5587
|
appVersion: this.options.appVersion ?? d.appVersion,
|
|
4533
5588
|
osVersion: d.osVersion,
|
|
4534
|
-
environment: this.
|
|
5589
|
+
environment: this.environment,
|
|
4535
5590
|
...this.screenName ? { screen: this.screenName, path: this.currentUrl() } : {},
|
|
4536
5591
|
...this.user?.traits ?? {},
|
|
4537
5592
|
id: this.user?.id || this.anonymousId || void 0,
|
|
@@ -4656,6 +5711,35 @@ function detectAppVersion(src) {
|
|
|
4656
5711
|
return {};
|
|
4657
5712
|
}
|
|
4658
5713
|
}
|
|
5714
|
+
function detectDeviceModel(src) {
|
|
5715
|
+
try {
|
|
5716
|
+
const expo = src.expoModules?.ExpoDevice ?? src.nativeModules.ExpoDevice;
|
|
5717
|
+
const info = src.nativeModules.RNDeviceInfo;
|
|
5718
|
+
const c = src.platform.constants ?? {};
|
|
5719
|
+
const android = src.platform.OS === "android";
|
|
5720
|
+
const model = text(constant(expo, "modelName")) ?? text(constant(info, "model")) ?? (android ? text(c.Model) : void 0);
|
|
5721
|
+
const manufacturer = text(constant(expo, "manufacturer")) ?? (android ? text(c.Manufacturer) : "Apple");
|
|
5722
|
+
const brand = text(constant(expo, "brand")) ?? text(constant(info, "brand")) ?? (android ? text(c.Brand) : void 0);
|
|
5723
|
+
const isDevice = constant(expo, "isDevice");
|
|
5724
|
+
return {
|
|
5725
|
+
...model ? { model } : {},
|
|
5726
|
+
...manufacturer ? { manufacturer } : {},
|
|
5727
|
+
...brand ? { brand } : {},
|
|
5728
|
+
...typeof isDevice === "boolean" ? { simulator: !isDevice } : {}
|
|
5729
|
+
};
|
|
5730
|
+
} catch {
|
|
5731
|
+
return {};
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
function detectReactNativeVersion(src) {
|
|
5735
|
+
try {
|
|
5736
|
+
const v = src.platform.constants?.reactNativeVersion;
|
|
5737
|
+
if (!v || typeof v.major !== "number") return void 0;
|
|
5738
|
+
return { major: v.major, minor: Number(v.minor) || 0, patch: Number(v.patch) || 0, ...v.prerelease != null ? { prerelease: String(v.prerelease) } : {} };
|
|
5739
|
+
} catch {
|
|
5740
|
+
return void 0;
|
|
5741
|
+
}
|
|
5742
|
+
}
|
|
4659
5743
|
function detectOsVersion(src) {
|
|
4660
5744
|
try {
|
|
4661
5745
|
const c = src.platform.constants ?? {};
|
|
@@ -4788,7 +5872,7 @@ function measureHost(host) {
|
|
|
4788
5872
|
done(null);
|
|
4789
5873
|
});
|
|
4790
5874
|
}
|
|
4791
|
-
function
|
|
5875
|
+
function describe2(f, masked, opts, withText) {
|
|
4792
5876
|
const props = f.memoizedProps && typeof f.memoizedProps === "object" ? f.memoizedProps : {};
|
|
4793
5877
|
const host = firstHost(f);
|
|
4794
5878
|
const el = { selector: idSelector(f) ?? pathSelector(f), tag: componentName(f.type) ?? "View" };
|
|
@@ -4833,8 +5917,8 @@ function resolveFiber(target, opts = {}) {
|
|
|
4833
5917
|
const element = press ?? role;
|
|
4834
5918
|
return {
|
|
4835
5919
|
ignored: false,
|
|
4836
|
-
element: element ?
|
|
4837
|
-
hit: hitFiber ?
|
|
5920
|
+
element: element ? describe2(element, masked, opts, true) : null,
|
|
5921
|
+
hit: hitFiber ? describe2(hitFiber, masked, opts, componentName(hitFiber.type) === "Text") : null
|
|
4838
5922
|
};
|
|
4839
5923
|
} catch {
|
|
4840
5924
|
return none;
|
|
@@ -4846,7 +5930,7 @@ function resolveTapTarget(event, opts = {}) {
|
|
|
4846
5930
|
|
|
4847
5931
|
// src/ui/Overlay.tsx
|
|
4848
5932
|
import { useCallback as useCallback2, useEffect as useEffect7, useRef as useRef4, useState as useState7, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
4849
|
-
import { Platform as Platform5, Pressable as
|
|
5933
|
+
import { Platform as Platform5, Pressable as Pressable9, StatusBar, StyleSheet as StyleSheet9, Text as Text8, useColorScheme, useWindowDimensions, View as View9 } from "react-native";
|
|
4850
5934
|
|
|
4851
5935
|
// src/ui/Checklist.tsx
|
|
4852
5936
|
import { Pressable as Pressable3, ScrollView as ScrollView2, StyleSheet as StyleSheet3, Text as Text3, View as View3 } from "react-native";
|
|
@@ -4967,7 +6051,7 @@ function parseHtml(html) {
|
|
|
4967
6051
|
if (last && last.bold === run.bold && last.italic === run.italic && last.underline === run.underline && last.code === run.code && last.href === run.href) last.text += text2;
|
|
4968
6052
|
else current.runs.push(run);
|
|
4969
6053
|
};
|
|
4970
|
-
const
|
|
6054
|
+
const size2 = (attrs, name) => {
|
|
4971
6055
|
const n = Number(attr(attrs, name));
|
|
4972
6056
|
return n > 0 ? n : void 0;
|
|
4973
6057
|
};
|
|
@@ -5029,8 +6113,8 @@ function parseHtml(html) {
|
|
|
5029
6113
|
}
|
|
5030
6114
|
} else if (tag === "img" && !closing) {
|
|
5031
6115
|
const src = attr(attrs, "src");
|
|
5032
|
-
const w =
|
|
5033
|
-
const h =
|
|
6116
|
+
const w = size2(attrs, "width");
|
|
6117
|
+
const h = size2(attrs, "height");
|
|
5034
6118
|
if (isSafeMediaUrl(src)) pdf.pages.push({ src, alt: attr(attrs, "alt") ?? "", ...w && h ? { width: w, height: h } : {} });
|
|
5035
6119
|
}
|
|
5036
6120
|
continue;
|
|
@@ -5204,10 +6288,10 @@ function BodyPdf({ block, palette: p, onLink }) {
|
|
|
5204
6288
|
] }) })
|
|
5205
6289
|
] });
|
|
5206
6290
|
}
|
|
5207
|
-
function RichText({ html, palette: p, onLink, size = 15 }) {
|
|
6291
|
+
function RichText({ html, palette: p, onLink, size: size2 = 15 }) {
|
|
5208
6292
|
const blocks = useMemo2(() => parseHtml(html), [html]);
|
|
5209
6293
|
if (!blocks.length) return null;
|
|
5210
|
-
const base = { color: p.text, fontSize:
|
|
6294
|
+
const base = { color: p.text, fontSize: size2, lineHeight: Math.round(size2 * 1.42), fontFamily: p.fontFamily };
|
|
5211
6295
|
return /* @__PURE__ */ jsx3(View2, { style: styles2.wrap, children: blocks.map((b, i) => {
|
|
5212
6296
|
if (b.kind === "image") return /* @__PURE__ */ jsx3(BodyImage, { block: b, palette: p }, i);
|
|
5213
6297
|
if (b.kind === "video") return /* @__PURE__ */ jsx3(BodyVideo, { block: b, palette: p, onLink }, i);
|
|
@@ -5410,17 +6494,17 @@ function useReduceMotion() {
|
|
|
5410
6494
|
}, []);
|
|
5411
6495
|
return on;
|
|
5412
6496
|
}
|
|
5413
|
-
function MessengerLauncher({ store, client, palette: p, style, frame, defaultSide }) {
|
|
6497
|
+
function MessengerLauncher({ store, client, palette: p, style, frame: frame2, defaultSide }) {
|
|
5414
6498
|
const snap = useMessenger(store);
|
|
5415
6499
|
const reduceMotion = useReduceMotion();
|
|
5416
6500
|
const [placement, setPlacement] = useState3(() => client.getMessengerLauncherPlacement());
|
|
5417
6501
|
const pan = useRef2(new Animated.ValueXY()).current;
|
|
5418
6502
|
const offset = useRef2({ x: 0, y: 0 });
|
|
5419
6503
|
const dragging = useRef2(false);
|
|
5420
|
-
const live = useRef2({ frame, defaultSide, reduceMotion });
|
|
5421
|
-
live.current = { frame, defaultSide, reduceMotion };
|
|
5422
|
-
const { width, height, size, defaultBottom } =
|
|
5423
|
-
const { top, bottom, left, right } =
|
|
6504
|
+
const live = useRef2({ frame: frame2, defaultSide, reduceMotion });
|
|
6505
|
+
live.current = { frame: frame2, defaultSide, reduceMotion };
|
|
6506
|
+
const { width, height, size: size2, defaultBottom } = frame2;
|
|
6507
|
+
const { top, bottom, left, right } = frame2.insets;
|
|
5424
6508
|
useLayoutEffect(() => {
|
|
5425
6509
|
if (dragging.current) return;
|
|
5426
6510
|
const f = live.current.frame;
|
|
@@ -5428,7 +6512,7 @@ function MessengerLauncher({ store, client, palette: p, style, frame, defaultSid
|
|
|
5428
6512
|
const at = placement ? placementPoint(f, placement) : anchor;
|
|
5429
6513
|
offset.current = { x: at.x - anchor.x, y: at.y - anchor.y };
|
|
5430
6514
|
pan.setValue(offset.current);
|
|
5431
|
-
}, [pan, placement, defaultSide, width, height,
|
|
6515
|
+
}, [pan, placement, defaultSide, width, height, size2, defaultBottom, top, bottom, left, right]);
|
|
5432
6516
|
const responder = useMemo3(() => {
|
|
5433
6517
|
const moved = (g) => Math.hypot(g.dx, g.dy) > LAUNCHER_DRAG_THRESHOLD;
|
|
5434
6518
|
const anchor = () => placementPoint(live.current.frame, { side: live.current.defaultSide, fraction: 0 });
|
|
@@ -5781,7 +6865,7 @@ var styles4 = StyleSheet4.create({
|
|
|
5781
6865
|
import { Fragment as Fragment4, useEffect as useEffect4, useMemo as useMemo4, useState as useState4 } from "react";
|
|
5782
6866
|
import { Modal as Modal3, Platform as Platform3, Pressable as Pressable5, ScrollView as ScrollView4, StyleSheet as StyleSheet5, Text as Text5, TextInput as TextInput2, View as View5 } from "react-native";
|
|
5783
6867
|
import { Fragment as Fragment5, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
5784
|
-
function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
6868
|
+
function ResourceCenter({ client, palette: p, open, articleRequest, topInset, bottomInset }) {
|
|
5785
6869
|
const rc = client.getResourceCenter();
|
|
5786
6870
|
const [query, setQuery] = useState4("");
|
|
5787
6871
|
const [results, setResults] = useState4(null);
|
|
@@ -5794,6 +6878,19 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
5794
6878
|
setArticle(null);
|
|
5795
6879
|
}
|
|
5796
6880
|
}, [open]);
|
|
6881
|
+
useEffect4(() => {
|
|
6882
|
+
if (!open || !articleRequest) return;
|
|
6883
|
+
let alive = true;
|
|
6884
|
+
void client.loadArticle(articleRequest.id).then((a) => {
|
|
6885
|
+
if (!alive || !a) return;
|
|
6886
|
+
if (a.externalUrl) return client.navigate(a.externalUrl);
|
|
6887
|
+
client.articleViewed(a);
|
|
6888
|
+
setArticle(a);
|
|
6889
|
+
});
|
|
6890
|
+
return () => {
|
|
6891
|
+
alive = false;
|
|
6892
|
+
};
|
|
6893
|
+
}, [client, open, articleRequest?.seq]);
|
|
5797
6894
|
useEffect4(() => {
|
|
5798
6895
|
const q = query.trim();
|
|
5799
6896
|
if (q.length < 2) {
|
|
@@ -5933,24 +7030,24 @@ var styles5 = StyleSheet5.create({
|
|
|
5933
7030
|
|
|
5934
7031
|
// src/ui/StepLayer.tsx
|
|
5935
7032
|
import { useEffect as useEffect5, useRef as useRef3, useState as useState5 } from "react";
|
|
5936
|
-
import { AccessibilityInfo as AccessibilityInfo2, Animated as Animated2, Easing as Easing2, Pressable as
|
|
7033
|
+
import { AccessibilityInfo as AccessibilityInfo2, Animated as Animated2, Easing as Easing2, Pressable as Pressable7, StyleSheet as StyleSheet7, View as View7 } from "react-native";
|
|
5937
7034
|
|
|
5938
7035
|
// src/core/position.ts
|
|
5939
7036
|
var MARGIN = 12;
|
|
5940
7037
|
var ARROW_INSET = 18;
|
|
5941
7038
|
var opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
5942
|
-
function positionCard(target,
|
|
7039
|
+
function positionCard(target, size2, viewport, insets, placement, offset) {
|
|
5943
7040
|
const [rawSide, align] = placement.split("-");
|
|
5944
7041
|
const fits = (side2) => {
|
|
5945
7042
|
switch (side2) {
|
|
5946
7043
|
case "bottom":
|
|
5947
|
-
return target.y + target.height + offset +
|
|
7044
|
+
return target.y + target.height + offset + size2.height <= viewport.height - insets.bottom - MARGIN;
|
|
5948
7045
|
case "top":
|
|
5949
|
-
return target.y - offset -
|
|
7046
|
+
return target.y - offset - size2.height >= insets.top + MARGIN;
|
|
5950
7047
|
case "right":
|
|
5951
|
-
return target.x + target.width + offset +
|
|
7048
|
+
return target.x + target.width + offset + size2.width <= viewport.width - insets.right - MARGIN;
|
|
5952
7049
|
case "left":
|
|
5953
|
-
return target.x - offset -
|
|
7050
|
+
return target.x - offset - size2.width >= insets.left + MARGIN;
|
|
5954
7051
|
}
|
|
5955
7052
|
};
|
|
5956
7053
|
const order = rawSide === "auto" ? ["bottom", "top"] : [rawSide, opposite[rawSide], "bottom", "top"];
|
|
@@ -5959,21 +7056,21 @@ function positionCard(target, size, viewport, insets, placement, offset) {
|
|
|
5959
7056
|
const clamp3 = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
5960
7057
|
if (side === "top" || side === "bottom") {
|
|
5961
7058
|
const cx = target.x + target.width / 2;
|
|
5962
|
-
let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width -
|
|
5963
|
-
x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN -
|
|
5964
|
-
let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset -
|
|
5965
|
-
y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN -
|
|
5966
|
-
return { x: x2, y: y2, side, arrow: clamp3(cx - x2, ARROW_INSET,
|
|
7059
|
+
let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width - size2.width : cx - size2.width / 2;
|
|
7060
|
+
x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN - size2.width));
|
|
7061
|
+
let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset - size2.height;
|
|
7062
|
+
y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size2.height));
|
|
7063
|
+
return { x: x2, y: y2, side, arrow: clamp3(cx - x2, ARROW_INSET, size2.width - ARROW_INSET) };
|
|
5967
7064
|
}
|
|
5968
7065
|
const cy = target.y + target.height / 2;
|
|
5969
|
-
let y = align === "start" ? target.y : align === "end" ? target.y + target.height -
|
|
5970
|
-
y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN -
|
|
5971
|
-
const x = side === "right" ? target.x + target.width + offset : target.x - offset -
|
|
5972
|
-
return { x, y, side, arrow: clamp3(cy - y, ARROW_INSET,
|
|
7066
|
+
let y = align === "start" ? target.y : align === "end" ? target.y + target.height - size2.height : cy - size2.height / 2;
|
|
7067
|
+
y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size2.height));
|
|
7068
|
+
const x = side === "right" ? target.x + target.width + offset : target.x - offset - size2.width;
|
|
7069
|
+
return { x, y, side, arrow: clamp3(cy - y, ARROW_INSET, size2.height - ARROW_INSET) };
|
|
5973
7070
|
}
|
|
5974
7071
|
|
|
5975
7072
|
// src/ui/StepCard.tsx
|
|
5976
|
-
import { Image as Image2, StyleSheet as StyleSheet6, Text as Text6, View as View6 } from "react-native";
|
|
7073
|
+
import { Image as Image2, Pressable as Pressable6, StyleSheet as StyleSheet6, Text as Text6, View as View6 } from "react-native";
|
|
5977
7074
|
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
5978
7075
|
function StepCard({ view, client, palette: p, width, style, compact }) {
|
|
5979
7076
|
const { step, guide } = view;
|
|
@@ -5981,8 +7078,25 @@ function StepCard({ view, client, palette: p, width, style, compact }) {
|
|
|
5981
7078
|
const body = step.type === "custom" ? step.customHtml || step.body : step.body;
|
|
5982
7079
|
const showProgress = step.showProgress && view.total > 1;
|
|
5983
7080
|
const onLink = (url) => client.navigate(url);
|
|
7081
|
+
const video = stepVideo(step);
|
|
5984
7082
|
return /* @__PURE__ */ jsx7(Surface, { palette: p, style: [{ width }, style], children: /* @__PURE__ */ jsxs5(View6, { accessible: false, accessibilityViewIsModal: view.asModal || step.type === "modal", style: compact ? styles6.compact : styles6.inner, children: [
|
|
5985
|
-
|
|
7083
|
+
video ? (
|
|
7084
|
+
// The video opens outside the app (browser or the provider's app), like a new tab on the web.
|
|
7085
|
+
/* @__PURE__ */ jsxs5(
|
|
7086
|
+
Pressable6,
|
|
7087
|
+
{
|
|
7088
|
+
onPress: () => client.openVideo(guide.id, video.url),
|
|
7089
|
+
accessibilityRole: "button",
|
|
7090
|
+
accessibilityLabel: step.title ? `Play video: ${step.title}` : "Play video",
|
|
7091
|
+
accessibilityHint: "Opens the video outside the app",
|
|
7092
|
+
style: [styles6.video, { borderRadius: Math.max(0, p.radius - 4) }],
|
|
7093
|
+
children: [
|
|
7094
|
+
video.poster ? /* @__PURE__ */ jsx7(Image2, { source: { uri: video.poster }, style: StyleSheet6.absoluteFill, resizeMode: "cover", accessibilityIgnoresInvertColors: true, accessible: false }) : null,
|
|
7095
|
+
/* @__PURE__ */ jsx7(View6, { style: styles6.play, children: /* @__PURE__ */ jsx7(View6, { style: styles6.playTriangle }) })
|
|
7096
|
+
]
|
|
7097
|
+
}
|
|
7098
|
+
)
|
|
7099
|
+
) : step.imageUrl ? (
|
|
5986
7100
|
// An empty description marks the image as decorative: screen readers skip it.
|
|
5987
7101
|
/* @__PURE__ */ jsx7(
|
|
5988
7102
|
Image2,
|
|
@@ -6023,6 +7137,10 @@ var styles6 = StyleSheet6.create({
|
|
|
6023
7137
|
inner: { padding: 16, gap: 10 },
|
|
6024
7138
|
compact: { paddingHorizontal: 14, paddingVertical: 10, gap: 6 },
|
|
6025
7139
|
image: { width: "100%", height: 140, marginBottom: 2 },
|
|
7140
|
+
video: { width: "100%", aspectRatio: 16 / 9, maxHeight: 200, marginBottom: 2, overflow: "hidden", backgroundColor: "#0f172a", alignItems: "center", justifyContent: "center" },
|
|
7141
|
+
play: { width: 52, height: 52, borderRadius: 26, backgroundColor: "rgba(15,23,42,0.72)", alignItems: "center", justifyContent: "center", paddingLeft: 4 },
|
|
7142
|
+
// A CSS-border triangle: no icon font needed.
|
|
7143
|
+
playTriangle: { width: 0, height: 0, borderTopWidth: 10, borderBottomWidth: 10, borderLeftWidth: 16, borderTopColor: "transparent", borderBottomColor: "transparent", borderLeftColor: "#fff" },
|
|
6026
7144
|
header: { flexDirection: "row", alignItems: "flex-start", gap: 8 },
|
|
6027
7145
|
title: { flex: 1, fontSize: 17, fontWeight: "700", lineHeight: 22 },
|
|
6028
7146
|
flex: { flex: 1 },
|
|
@@ -6082,7 +7200,7 @@ function StepLayer({ view, client, palette: p, geometry }) {
|
|
|
6082
7200
|
if (step.type === "hotspot" && !view.expanded) return;
|
|
6083
7201
|
AccessibilityInfo2.announceForAccessibility?.(announcement(view));
|
|
6084
7202
|
}, [view.key, view.expanded]);
|
|
6085
|
-
const backdrop = (children) => /* @__PURE__ */ jsx8(
|
|
7203
|
+
const backdrop = (children) => /* @__PURE__ */ jsx8(Pressable7, { accessible: false, style: [StyleSheet7.absoluteFill, { backgroundColor: p.overlay }], onPress: () => client.backdropPressed(view.guide.id), children });
|
|
6086
7204
|
const anchored = !view.asModal && ["tooltip", "popover", "spotlight", "hotspot"].includes(step.type);
|
|
6087
7205
|
if (!anchored && step.type === "banner") {
|
|
6088
7206
|
const top = step.position.startsWith("top");
|
|
@@ -6102,9 +7220,9 @@ function StepLayer({ view, client, palette: p, geometry }) {
|
|
|
6102
7220
|
}
|
|
6103
7221
|
if (!rect) return null;
|
|
6104
7222
|
if (step.type === "hotspot") {
|
|
6105
|
-
const
|
|
7223
|
+
const size2 = step.hotspot?.size ?? 14;
|
|
6106
7224
|
return /* @__PURE__ */ jsxs6(View7, { pointerEvents: "box-none", style: StyleSheet7.absoluteFill, children: [
|
|
6107
|
-
/* @__PURE__ */ jsx8(Hotspot, { x: rect.x + rect.width -
|
|
7225
|
+
/* @__PURE__ */ jsx8(Hotspot, { x: rect.x + rect.width - size2 / 2, y: rect.y - size2 / 2, size: size2, pulse: step.hotspot?.pulse ?? true, color: p.primary, onPress: () => client.toggleHotspot(view.guide.id), label: step.title || "Show tip" }),
|
|
6108
7226
|
view.expanded ? /* @__PURE__ */ jsx8(Anchored, { view, client, palette: p, geometry, rect, width: cardWidth }) : null
|
|
6109
7227
|
] });
|
|
6110
7228
|
}
|
|
@@ -6121,10 +7239,10 @@ function StepLayer({ view, client, palette: p, geometry }) {
|
|
|
6121
7239
|
[hole.x + hole.width - r, hole.y + hole.height - r, -2 * r, -2 * r]
|
|
6122
7240
|
];
|
|
6123
7241
|
return /* @__PURE__ */ jsxs6(Animated2.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, { opacity }], children: [
|
|
6124
|
-
/* @__PURE__ */ jsx8(
|
|
6125
|
-
/* @__PURE__ */ jsx8(
|
|
6126
|
-
/* @__PURE__ */ jsx8(
|
|
6127
|
-
/* @__PURE__ */ jsx8(
|
|
7242
|
+
/* @__PURE__ */ jsx8(Pressable7, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: 0, height: Math.max(0, hole.y) }] }),
|
|
7243
|
+
/* @__PURE__ */ jsx8(Pressable7, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: hole.y + hole.height, bottom: 0 }] }),
|
|
7244
|
+
/* @__PURE__ */ jsx8(Pressable7, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, width: Math.max(0, hole.x), top: hole.y, height: hole.height }] }),
|
|
7245
|
+
/* @__PURE__ */ jsx8(Pressable7, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: hole.x + hole.width, right: 0, top: hole.y, height: hole.height }] }),
|
|
6128
7246
|
r > 0 ? corners.map(([left, top, dx, dy], i) => /* @__PURE__ */ jsx8(View7, { pointerEvents: "none", style: [styles7.abs, { left, top, width: r, height: r, overflow: "hidden" }], children: /* @__PURE__ */ jsx8(View7, { style: [styles7.abs, { left: dx, top: dy, width: 4 * r, height: 4 * r, borderRadius: 2 * r, borderWidth: r, borderColor: p.overlay }] }) }, i)) : null,
|
|
6129
7247
|
/* @__PURE__ */ jsx8(View7, { pointerEvents: "none", style: [styles7.abs, { left: hole.x, top: hole.y, width: hole.width, height: hole.height, borderRadius: r, borderWidth: 2, borderColor: p.primary }] }),
|
|
6130
7248
|
/* @__PURE__ */ jsx8(Anchored, { view, client, palette: p, geometry, rect: hole, width: cardWidth })
|
|
@@ -6151,7 +7269,7 @@ function Arrow({ side, at, color, border }) {
|
|
|
6151
7269
|
const place = side === "bottom" ? { top: -half, left: at - half } : side === "top" ? { bottom: -half, left: at - half } : side === "right" ? { left: -half, top: at - half } : { right: -half, top: at - half };
|
|
6152
7270
|
return /* @__PURE__ */ jsx8(View7, { pointerEvents: "none", style: [styles7.abs, styles7.arrow, place, { backgroundColor: color, borderColor: border }] });
|
|
6153
7271
|
}
|
|
6154
|
-
function Hotspot({ x, y, size, pulse, color, onPress, label }) {
|
|
7272
|
+
function Hotspot({ x, y, size: size2, pulse, color, onPress, label }) {
|
|
6155
7273
|
const scale = useRef3(new Animated2.Value(0)).current;
|
|
6156
7274
|
const [reduceMotion, setReduceMotion] = useState5(false);
|
|
6157
7275
|
useEffect5(() => {
|
|
@@ -6168,15 +7286,15 @@ function Hotspot({ x, y, size, pulse, color, onPress, label }) {
|
|
|
6168
7286
|
loop.start();
|
|
6169
7287
|
return () => loop.stop();
|
|
6170
7288
|
}, [pulse, reduceMotion, scale]);
|
|
6171
|
-
return /* @__PURE__ */ jsxs6(
|
|
7289
|
+
return /* @__PURE__ */ jsxs6(Pressable7, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width: size2, height: size2 }], children: [
|
|
6172
7290
|
pulse && !reduceMotion ? /* @__PURE__ */ jsx8(
|
|
6173
7291
|
Animated2.View,
|
|
6174
7292
|
{
|
|
6175
7293
|
pointerEvents: "none",
|
|
6176
|
-
style: [styles7.abs, { width:
|
|
7294
|
+
style: [styles7.abs, { width: size2, height: size2, borderRadius: size2 / 2, backgroundColor: color, opacity: scale.interpolate({ inputRange: [0, 1], outputRange: [0.55, 0] }), transform: [{ scale: scale.interpolate({ inputRange: [0, 1], outputRange: [1, 2.6] }) }] }]
|
|
6177
7295
|
}
|
|
6178
7296
|
) : null,
|
|
6179
|
-
/* @__PURE__ */ jsx8(View7, { style: { width:
|
|
7297
|
+
/* @__PURE__ */ jsx8(View7, { style: { width: size2, height: size2, borderRadius: size2 / 2, backgroundColor: color, borderWidth: 2, borderColor: "#ffffff" } })
|
|
6180
7298
|
] });
|
|
6181
7299
|
}
|
|
6182
7300
|
var styles7 = StyleSheet7.create({
|
|
@@ -6192,7 +7310,7 @@ import {
|
|
|
6192
7310
|
AccessibilityInfo as AccessibilityInfo3,
|
|
6193
7311
|
KeyboardAvoidingView,
|
|
6194
7312
|
Platform as Platform4,
|
|
6195
|
-
Pressable as
|
|
7313
|
+
Pressable as Pressable8,
|
|
6196
7314
|
ScrollView as ScrollView5,
|
|
6197
7315
|
StyleSheet as StyleSheet8,
|
|
6198
7316
|
Text as Text7,
|
|
@@ -6250,7 +7368,7 @@ function Survey({
|
|
|
6250
7368
|
style: StyleSheet8.absoluteFill,
|
|
6251
7369
|
children: [
|
|
6252
7370
|
centered ? /* @__PURE__ */ jsx9(
|
|
6253
|
-
|
|
7371
|
+
Pressable8,
|
|
6254
7372
|
{
|
|
6255
7373
|
accessible: false,
|
|
6256
7374
|
style: [StyleSheet8.absoluteFill, { backgroundColor: p.overlay }],
|
|
@@ -6347,7 +7465,7 @@ function QuestionInput({
|
|
|
6347
7465
|
const values = Array.from({ length: max - min + 1 }, (_, i) => min + i);
|
|
6348
7466
|
return /* @__PURE__ */ jsxs7(View8, { children: [
|
|
6349
7467
|
/* @__PURE__ */ jsx9(View8, { style: styles8.scale, accessibilityRole: "radiogroup", accessibilityLabel: q.title, children: values.map((n) => /* @__PURE__ */ jsx9(
|
|
6350
|
-
|
|
7468
|
+
Pressable8,
|
|
6351
7469
|
{
|
|
6352
7470
|
accessibilityRole: "radio",
|
|
6353
7471
|
accessibilityState: { selected: value === n },
|
|
@@ -6367,7 +7485,7 @@ function QuestionInput({
|
|
|
6367
7485
|
case "rating": {
|
|
6368
7486
|
const max = q.scaleMax ?? 5;
|
|
6369
7487
|
return /* @__PURE__ */ jsx9(View8, { style: styles8.stars, accessibilityRole: "radiogroup", accessibilityLabel: q.title, children: Array.from({ length: max }, (_, i) => i + 1).map((n) => /* @__PURE__ */ jsx9(
|
|
6370
|
-
|
|
7488
|
+
Pressable8,
|
|
6371
7489
|
{
|
|
6372
7490
|
accessibilityRole: "radio",
|
|
6373
7491
|
accessibilityLabel: `${n} of ${max}`,
|
|
@@ -6393,7 +7511,7 @@ function QuestionInput({
|
|
|
6393
7511
|
[true, "\u{1F44D}", "Thumbs up"],
|
|
6394
7512
|
[false, "\u{1F44E}", "Thumbs down"]
|
|
6395
7513
|
].map(([val, icon, label]) => /* @__PURE__ */ jsx9(
|
|
6396
|
-
|
|
7514
|
+
Pressable8,
|
|
6397
7515
|
{
|
|
6398
7516
|
accessibilityRole: "radio",
|
|
6399
7517
|
accessibilityLabel: label,
|
|
@@ -6413,7 +7531,7 @@ function QuestionInput({
|
|
|
6413
7531
|
return /* @__PURE__ */ jsx9(View8, { style: styles8.choices, children: q.options.map((o) => {
|
|
6414
7532
|
const on = selected.has(o.id);
|
|
6415
7533
|
return /* @__PURE__ */ jsxs7(
|
|
6416
|
-
|
|
7534
|
+
Pressable8,
|
|
6417
7535
|
{
|
|
6418
7536
|
accessibilityRole: multi ? "checkbox" : "radio",
|
|
6419
7537
|
accessibilityState: { checked: on },
|
|
@@ -6599,7 +7717,7 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
6599
7717
|
return /* @__PURE__ */ jsxs8(View9, { ref, collapsable: false, pointerEvents: "box-none", style: StyleSheet9.absoluteFill, onLayout: measureOrigin, children: [
|
|
6600
7718
|
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)),
|
|
6601
7719
|
showRcLauncher ? /* @__PURE__ */ jsxs8(
|
|
6602
|
-
|
|
7720
|
+
Pressable9,
|
|
6603
7721
|
{
|
|
6604
7722
|
accessibilityRole: "button",
|
|
6605
7723
|
accessibilityLabel: rcLauncherLabel(client),
|
|
@@ -6624,7 +7742,7 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
6624
7742
|
) : null,
|
|
6625
7743
|
ui.survey ? /* @__PURE__ */ jsx10(Survey, { view: ui.survey, client, palette: palette(client.themeFor(ui.survey.guide), scheme), geometry }) : null,
|
|
6626
7744
|
ui.steps.map((s) => /* @__PURE__ */ jsx10(StepLayer, { view: s, client, palette: palette(client.themeFor(s.guide), scheme, s.guide.config.appearance), geometry }, s.key)),
|
|
6627
|
-
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 })
|
|
7745
|
+
messenger ? /* @__PURE__ */ jsx10(MessengerSheet, { store: messenger, client, palette: basePalette, topInset: insets.top, bottomInset: insets.bottom }) : /* @__PURE__ */ jsx10(ResourceCenter, { client, palette: basePalette, open: ui.resourceCenterOpen, articleRequest: ui.resourceCenterArticle ?? null, topInset: insets.top, bottomInset: insets.bottom })
|
|
6628
7746
|
] });
|
|
6629
7747
|
}
|
|
6630
7748
|
var styles9 = StyleSheet9.create({
|
|
@@ -6797,12 +7915,16 @@ function defaultBridge(appVersion, appBuild) {
|
|
|
6797
7915
|
locale: deviceLocale(),
|
|
6798
7916
|
appVersion: appVersion ?? detected.appVersion,
|
|
6799
7917
|
appBuild: appBuild ?? detected.appBuild,
|
|
6800
|
-
osVersion: detectOsVersion(sources)
|
|
7918
|
+
osVersion: detectOsVersion(sources),
|
|
7919
|
+
...detectDeviceModel(sources),
|
|
7920
|
+
reactNativeVersion: detectReactNativeVersion(sources)
|
|
6801
7921
|
},
|
|
6802
7922
|
openExternal: (url) => void Linking.openURL(url).catch(() => {
|
|
6803
7923
|
}),
|
|
6804
7924
|
windowSize: () => Dimensions.get("window"),
|
|
6805
|
-
capturer: nativeCapturer()
|
|
7925
|
+
capturer: nativeCapturer(),
|
|
7926
|
+
// ErrorUtils, HermesInternal, console, fetch, XMLHttpRequest: crash capture and breadcrumbs.
|
|
7927
|
+
globals: globalThis
|
|
6806
7928
|
};
|
|
6807
7929
|
}
|
|
6808
7930
|
function nativeCapturer() {
|
|
@@ -6810,6 +7932,20 @@ function nativeCapturer() {
|
|
|
6810
7932
|
if (!mod || typeof mod.capture !== "function") return void 0;
|
|
6811
7933
|
return (options) => mod.capture(options);
|
|
6812
7934
|
}
|
|
7935
|
+
function latestErrorOptions(initial, latest) {
|
|
7936
|
+
if (!initial) return void 0;
|
|
7937
|
+
return {
|
|
7938
|
+
...initial,
|
|
7939
|
+
beforeSend: (event) => {
|
|
7940
|
+
const hook = latest()?.beforeSend;
|
|
7941
|
+
return hook ? hook(event) : event;
|
|
7942
|
+
},
|
|
7943
|
+
beforeBreadcrumb: (crumb, hint) => {
|
|
7944
|
+
const hook = latest()?.beforeBreadcrumb;
|
|
7945
|
+
return hook ? hook(crumb, hint) : crumb;
|
|
7946
|
+
}
|
|
7947
|
+
};
|
|
7948
|
+
}
|
|
6813
7949
|
function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
|
|
6814
7950
|
const [client, setClient] = useState8(null);
|
|
6815
7951
|
const optionsRef = useRef7(options);
|
|
@@ -6840,7 +7976,8 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
6840
7976
|
user: userRef.current?.id ? userRef.current : void 0,
|
|
6841
7977
|
flags: latest.flags === false ? false : { ...latest.flags ?? {}, payload: payloadRef.current ?? void 0, attributes: appAttributesRef.current },
|
|
6842
7978
|
onNavigate: (url) => optionsRef.current.onNavigate?.(url),
|
|
6843
|
-
onError: (err) => optionsRef.current.onError?.(err)
|
|
7979
|
+
onError: (err) => optionsRef.current.onError?.(err),
|
|
7980
|
+
errors: latestErrorOptions(latest.errors, () => optionsRef.current.errors)
|
|
6844
7981
|
},
|
|
6845
7982
|
defaultBridge(latest.appVersion, latest.appBuild)
|
|
6846
7983
|
);
|
|
@@ -6903,6 +8040,8 @@ var NOOP_RESULTS = {
|
|
|
6903
8040
|
isRecordingTouches: false,
|
|
6904
8041
|
getSessionId: "",
|
|
6905
8042
|
getScreen: "",
|
|
8043
|
+
captureException: "",
|
|
8044
|
+
captureMessage: "",
|
|
6906
8045
|
on: () => {
|
|
6907
8046
|
},
|
|
6908
8047
|
subscribeUi: () => () => {
|
|
@@ -7021,6 +8160,7 @@ function useActiveGuide() {
|
|
|
7021
8160
|
}
|
|
7022
8161
|
export {
|
|
7023
8162
|
DEFAULT_API_URL,
|
|
8163
|
+
FATAL_FLUSH_MS,
|
|
7024
8164
|
Feature,
|
|
7025
8165
|
FeatureFlag,
|
|
7026
8166
|
FeatureValue,
|
|
@@ -7045,11 +8185,15 @@ export {
|
|
|
7045
8185
|
createFlagsClient3 as createFlagsClient,
|
|
7046
8186
|
createSseParser,
|
|
7047
8187
|
detectAppVersion,
|
|
8188
|
+
detectDeviceModel,
|
|
7048
8189
|
detectOsVersion,
|
|
8190
|
+
detectReactNativeVersion,
|
|
7049
8191
|
guidelyScreenLayout,
|
|
7050
8192
|
htmlToText,
|
|
7051
8193
|
noopClient,
|
|
8194
|
+
normalizeBundleFile,
|
|
7052
8195
|
parseHtml,
|
|
8196
|
+
parseReactNativeStack,
|
|
7053
8197
|
positionCard,
|
|
7054
8198
|
targetIdsFor,
|
|
7055
8199
|
useActiveGuide,
|