@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.cjs
CHANGED
|
@@ -23,6 +23,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
23
23
|
var index_exports = {};
|
|
24
24
|
__export(index_exports, {
|
|
25
25
|
DEFAULT_API_URL: () => DEFAULT_API_URL,
|
|
26
|
+
FATAL_FLUSH_MS: () => FATAL_FLUSH_MS,
|
|
26
27
|
Feature: () => Feature,
|
|
27
28
|
FeatureFlag: () => FeatureFlag,
|
|
28
29
|
FeatureValue: () => FeatureValue,
|
|
@@ -47,11 +48,15 @@ __export(index_exports, {
|
|
|
47
48
|
createFlagsClient: () => import_flags6.createFlagsClient,
|
|
48
49
|
createSseParser: () => createSseParser,
|
|
49
50
|
detectAppVersion: () => detectAppVersion,
|
|
51
|
+
detectDeviceModel: () => detectDeviceModel,
|
|
50
52
|
detectOsVersion: () => detectOsVersion,
|
|
53
|
+
detectReactNativeVersion: () => detectReactNativeVersion,
|
|
51
54
|
guidelyScreenLayout: () => guidelyScreenLayout,
|
|
52
55
|
htmlToText: () => htmlToText,
|
|
53
56
|
noopClient: () => noopClient,
|
|
57
|
+
normalizeBundleFile: () => normalizeBundleFile,
|
|
54
58
|
parseHtml: () => parseHtml,
|
|
59
|
+
parseReactNativeStack: () => parseReactNativeStack,
|
|
55
60
|
positionCard: () => positionCard,
|
|
56
61
|
targetIdsFor: () => targetIdsFor,
|
|
57
62
|
useActiveGuide: () => useActiveGuide,
|
|
@@ -165,7 +170,7 @@ function FlagsProvider({ children, client: external, payload, ...options }) {
|
|
|
165
170
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FlagsContext.Provider, { value, children });
|
|
166
171
|
}
|
|
167
172
|
|
|
168
|
-
// ../shared/dist/chunk-
|
|
173
|
+
// ../shared/dist/chunk-YL65O3FS.js
|
|
169
174
|
function fnv1a(input) {
|
|
170
175
|
let h = 2166136261;
|
|
171
176
|
for (let i = 0; i < input.length; i++) {
|
|
@@ -178,18 +183,18 @@ function rolloutBucket(subject, salt) {
|
|
|
178
183
|
return fnv1a(`${salt}:${subject}`) % 100;
|
|
179
184
|
}
|
|
180
185
|
var ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
181
|
-
function randomId(
|
|
186
|
+
function randomId(size2 = 16) {
|
|
182
187
|
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
183
188
|
let bytes;
|
|
184
189
|
if (g.crypto && typeof g.crypto.getRandomValues === "function") {
|
|
185
|
-
bytes = new Uint8Array(
|
|
190
|
+
bytes = new Uint8Array(size2);
|
|
186
191
|
g.crypto.getRandomValues(bytes);
|
|
187
192
|
} else {
|
|
188
|
-
bytes = new Uint8Array(
|
|
189
|
-
for (let i = 0; i <
|
|
193
|
+
bytes = new Uint8Array(size2);
|
|
194
|
+
for (let i = 0; i < size2; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
190
195
|
}
|
|
191
196
|
let out = "";
|
|
192
|
-
for (let i = 0; i <
|
|
197
|
+
for (let i = 0; i < size2; i++) out += ALPHABET[bytes[i] % ALPHABET.length];
|
|
193
198
|
return out;
|
|
194
199
|
}
|
|
195
200
|
function getPath(obj, path) {
|
|
@@ -459,24 +464,92 @@ function evaluateRules(group, ctx) {
|
|
|
459
464
|
const value = group.op === "and" ? results.every(Boolean) : results.some(Boolean);
|
|
460
465
|
return group.negate ? !value : value;
|
|
461
466
|
}
|
|
467
|
+
var MAX_EXCEPTION_DETAIL_BYTES = 64 * 1024;
|
|
468
|
+
var MAX_EXCEPTION_DETAIL_RAW_BYTES = 1024 * 1024;
|
|
469
|
+
var MAX_BREADCRUMBS = 100;
|
|
470
|
+
var MAX_EXCEPTION_CHAIN = 20;
|
|
471
|
+
var MAX_DETAIL_FRAMES = 250;
|
|
472
|
+
var MAX_DETAIL_THREADS = 100;
|
|
473
|
+
var MAX_DEBUG_IMAGES = 500;
|
|
474
|
+
var MAX_ERROR_TAGS = 50;
|
|
475
|
+
var size = (v) => {
|
|
476
|
+
try {
|
|
477
|
+
return JSON.stringify(v)?.length ?? 0;
|
|
478
|
+
} catch {
|
|
479
|
+
return Infinity;
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
function normalizeTags(tags) {
|
|
483
|
+
const out = {};
|
|
484
|
+
for (const [k, v] of Object.entries(tags ?? {})) {
|
|
485
|
+
if (Object.keys(out).length >= MAX_ERROR_TAGS) break;
|
|
486
|
+
if (!k || v === void 0 || v === null) continue;
|
|
487
|
+
out[k.slice(0, 32)] = String(v).slice(0, 200);
|
|
488
|
+
}
|
|
489
|
+
return out;
|
|
490
|
+
}
|
|
491
|
+
function trimExceptionDetail(detail, max = MAX_EXCEPTION_DETAIL_BYTES) {
|
|
492
|
+
const d = detail;
|
|
493
|
+
if (d.breadcrumbs?.values && d.breadcrumbs.values.length > MAX_BREADCRUMBS) d.breadcrumbs.values = d.breadcrumbs.values.slice(-MAX_BREADCRUMBS);
|
|
494
|
+
if (d.exception?.values && d.exception.values.length > MAX_EXCEPTION_CHAIN) d.exception.values = d.exception.values.slice(-MAX_EXCEPTION_CHAIN);
|
|
495
|
+
if (d.threads?.values && d.threads.values.length > MAX_DETAIL_THREADS) d.threads.values = d.threads.values.slice(0, MAX_DETAIL_THREADS);
|
|
496
|
+
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);
|
|
497
|
+
const stacks = () => [...d.exception?.values ?? [], ...d.threads?.values ?? []].map((x) => x.stacktrace).filter((s) => !!s);
|
|
498
|
+
for (const s of stacks()) if (s.frames && s.frames.length > MAX_DETAIL_FRAMES) s.frames = s.frames.slice(-MAX_DETAIL_FRAMES);
|
|
499
|
+
if (size(d) <= max) return d;
|
|
500
|
+
while (d.breadcrumbs?.values.length && size(d) > max) d.breadcrumbs.values = d.breadcrumbs.values.slice(Math.ceil(d.breadcrumbs.values.length / 2));
|
|
501
|
+
if (size(d) <= max) return d;
|
|
502
|
+
for (const s of stacks())
|
|
503
|
+
for (const f of s.frames ?? []) {
|
|
504
|
+
delete f.vars;
|
|
505
|
+
delete f.pre_context;
|
|
506
|
+
delete f.post_context;
|
|
507
|
+
}
|
|
508
|
+
if (size(d) <= max) return d;
|
|
509
|
+
if (d.threads?.values) d.threads.values = d.threads.values.filter((t) => t.crashed || t.current);
|
|
510
|
+
if (size(d) <= max) return d;
|
|
511
|
+
if (d.extra) d.extra = { _trimmed: true };
|
|
512
|
+
if (d.request) delete d.request.data;
|
|
513
|
+
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);
|
|
514
|
+
if (d.debug_meta?.images && size(d) > max) d.debug_meta.images = d.debug_meta.images.slice(0, 50);
|
|
515
|
+
return d;
|
|
516
|
+
}
|
|
517
|
+
var matches = (patterns, value) => patterns.some((p) => typeof p === "string" ? p !== "" && value.includes(p) : safeTest(p, value));
|
|
518
|
+
function safeTest(re, value) {
|
|
519
|
+
re.lastIndex = 0;
|
|
520
|
+
return re.test(value);
|
|
521
|
+
}
|
|
522
|
+
function errorFilterDrops(e, opts) {
|
|
523
|
+
const message = e.message ?? "";
|
|
524
|
+
if (opts.ignoreErrors?.length && (matches(opts.ignoreErrors, `${e.type || "Error"}: ${message}`) || matches(opts.ignoreErrors, message))) return true;
|
|
525
|
+
if (!e.file) return false;
|
|
526
|
+
if (opts.denyUrls?.length && matches(opts.denyUrls, e.file)) return true;
|
|
527
|
+
if (opts.allowUrls?.length && !matches(opts.allowUrls, e.file)) return true;
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
462
530
|
var SENSITIVE_PROPERTY_PATTERN = /(password|passwd|secret|token|credit|card|cvv|ssn|iban)/i;
|
|
463
531
|
var MAX_PROPERTY_BYTES = 8 * 1024;
|
|
464
532
|
function sanitizeProperties(props) {
|
|
465
533
|
if (!props) return {};
|
|
466
534
|
const out = {};
|
|
467
|
-
let
|
|
535
|
+
let size2 = 0;
|
|
468
536
|
for (const [k, v] of Object.entries(props)) {
|
|
469
537
|
if (SENSITIVE_PROPERTY_PATTERN.test(k)) continue;
|
|
470
538
|
if (typeof v === "function" || typeof v === "symbol") continue;
|
|
471
539
|
const str2 = typeof v === "string" ? v : JSON.stringify(v ?? null);
|
|
472
|
-
|
|
473
|
-
if (
|
|
540
|
+
size2 += k.length + (str2?.length ?? 0);
|
|
541
|
+
if (size2 > MAX_PROPERTY_BYTES) break;
|
|
474
542
|
out[k.slice(0, 100)] = typeof v === "string" ? v.slice(0, 1e3) : v;
|
|
475
543
|
}
|
|
476
544
|
return out;
|
|
477
545
|
}
|
|
546
|
+
function reactNativeBundleUrl(file) {
|
|
547
|
+
const base = file.replace(/[?#].*$/, "").split("/").pop() || file;
|
|
548
|
+
return `app:///${base}`;
|
|
549
|
+
}
|
|
478
550
|
var SOURCEMAP_MAX_FILE_BYTES = 100 * 1024 * 1024;
|
|
479
551
|
var MAX_COMPLAINT_ATTACHMENT_BYTES = 2 * 1024 * 1024;
|
|
552
|
+
var MAX_BUG_REPORT_SCREENSHOT_BYTES = 4 * 1024 * 1024;
|
|
480
553
|
var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
481
554
|
var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
|
|
482
555
|
var MEDIA_FILE_MAX_BYTES = 50 * 1024 * 1024;
|
|
@@ -574,6 +647,16 @@ function isSafeMediaUrl(url) {
|
|
|
574
647
|
return false;
|
|
575
648
|
}
|
|
576
649
|
}
|
|
650
|
+
function videoThumbnailUrl(url) {
|
|
651
|
+
const embed = url ? toEmbedUrl(url) : null;
|
|
652
|
+
if (embed?.provider !== "youtube") return null;
|
|
653
|
+
const id = embed.src.split("/embed/")[1]?.split("?")[0];
|
|
654
|
+
return id ? `https://i.ytimg.com/vi/${id}/hqdefault.jpg` : null;
|
|
655
|
+
}
|
|
656
|
+
function stepVideo(step) {
|
|
657
|
+
if (!step.videoUrl || !isSafeMediaUrl(step.videoUrl)) return null;
|
|
658
|
+
return { url: step.videoUrl, poster: (step.imageUrl && isSafeMediaUrl(step.imageUrl) ? step.imageUrl : null) ?? videoThumbnailUrl(step.videoUrl) };
|
|
659
|
+
}
|
|
577
660
|
var MAX_COLLECTION_DEPTH = 3;
|
|
578
661
|
function matchesAudiences(required, member) {
|
|
579
662
|
if (!required || required.length === 0) return true;
|
|
@@ -643,6 +726,13 @@ function unreadAnnouncements(items, lastSeenAt) {
|
|
|
643
726
|
if (Number.isNaN(seen)) return items.length;
|
|
644
727
|
return items.filter((a) => Date.parse(a.publishedAt) > seen).length;
|
|
645
728
|
}
|
|
729
|
+
var ARTICLE_HREF_PREFIX = "#guidely-article:";
|
|
730
|
+
var ARTICLE_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
731
|
+
function articleIdFromHref(href) {
|
|
732
|
+
if (!href || !href.startsWith(ARTICLE_HREF_PREFIX)) return null;
|
|
733
|
+
const id = href.slice(ARTICLE_HREF_PREFIX.length);
|
|
734
|
+
return ARTICLE_ID.test(id) ? id : null;
|
|
735
|
+
}
|
|
646
736
|
var CAPTURE_SCREENSHOT_MAX_BYTES = 8 * 1024 * 1024;
|
|
647
737
|
var DEFAULT_LOCALE = "en";
|
|
648
738
|
function normalizeLocale(input) {
|
|
@@ -1084,7 +1174,22 @@ var KEYS = {
|
|
|
1084
1174
|
launcher: (key) => `guidely:launcher:${key}`
|
|
1085
1175
|
};
|
|
1086
1176
|
|
|
1087
|
-
// ../sdk-core/dist/chunk-
|
|
1177
|
+
// ../sdk-core/dist/chunk-GFLVZRTS.js
|
|
1178
|
+
var MAX_PERSISTED_QUEUE_CHARS = 1024 * 1024;
|
|
1179
|
+
function fitPersisted(events) {
|
|
1180
|
+
let size2 = JSON.stringify(events).length;
|
|
1181
|
+
if (size2 <= MAX_PERSISTED_QUEUE_CHARS) return events;
|
|
1182
|
+
const out = events.slice();
|
|
1183
|
+
for (let i = 0; i < out.length && size2 > MAX_PERSISTED_QUEUE_CHARS; i++) {
|
|
1184
|
+
const e = out[i];
|
|
1185
|
+
if (!e.detail) continue;
|
|
1186
|
+
size2 -= JSON.stringify(e.detail).length;
|
|
1187
|
+
const rest = { ...e };
|
|
1188
|
+
delete rest.detail;
|
|
1189
|
+
out[i] = rest;
|
|
1190
|
+
}
|
|
1191
|
+
return out;
|
|
1192
|
+
}
|
|
1088
1193
|
var EventQueue = class {
|
|
1089
1194
|
constructor(opts) {
|
|
1090
1195
|
__publicField2(this, "opts", opts);
|
|
@@ -1140,7 +1245,7 @@ var EventQueue = class {
|
|
|
1140
1245
|
this.schedule();
|
|
1141
1246
|
}
|
|
1142
1247
|
persist() {
|
|
1143
|
-
writeJson(this.opts.store, this.opts.storageKey, this.events);
|
|
1248
|
+
writeJson(this.opts.store, this.opts.storageKey, fitPersisted(this.events));
|
|
1144
1249
|
}
|
|
1145
1250
|
schedule() {
|
|
1146
1251
|
if (this.timer || this.retryTimer || this.destroyed) return;
|
|
@@ -1390,6 +1495,13 @@ function dismissedAfterMerge(local, server) {
|
|
|
1390
1495
|
if (server.dismissResetAt && server.dismissResetAt !== local?.dismissResetAt) return server.dismissedAt ?? null;
|
|
1391
1496
|
return local?.dismissedAt ?? server.dismissedAt ?? null;
|
|
1392
1497
|
}
|
|
1498
|
+
function environmentFromKey(key) {
|
|
1499
|
+
const prefix = /^pk_([a-z0-9]+)_/.exec(key)?.[1];
|
|
1500
|
+
if (!prefix || prefix === "dev") return "development";
|
|
1501
|
+
if (prefix === "prod") return "production";
|
|
1502
|
+
if (prefix === "stg" || prefix === "stag") return "staging";
|
|
1503
|
+
return prefix;
|
|
1504
|
+
}
|
|
1393
1505
|
|
|
1394
1506
|
// src/core/client.ts
|
|
1395
1507
|
var import_flags2 = require("@guidelytech/flags");
|
|
@@ -1546,6 +1658,593 @@ var TapTracker = class {
|
|
|
1546
1658
|
}
|
|
1547
1659
|
};
|
|
1548
1660
|
|
|
1661
|
+
// src/core/breadcrumbs.ts
|
|
1662
|
+
var BreadcrumbBuffer = class {
|
|
1663
|
+
constructor(max = MAX_BREADCRUMBS) {
|
|
1664
|
+
__publicField(this, "max", max);
|
|
1665
|
+
__publicField(this, "items", []);
|
|
1666
|
+
}
|
|
1667
|
+
/** Adds a breadcrumb; `max` (at most the buffer's own) lowers how many are kept. */
|
|
1668
|
+
add(crumb, max = this.max) {
|
|
1669
|
+
const limit = Math.min(max, this.max);
|
|
1670
|
+
this.items.push(crumb);
|
|
1671
|
+
if (this.items.length > limit) this.items.splice(0, this.items.length - limit);
|
|
1672
|
+
}
|
|
1673
|
+
/** Copies, oldest first (an event keeps the crumbs it was captured with). */
|
|
1674
|
+
values() {
|
|
1675
|
+
return this.items.map((c) => ({ ...c, ...c.data ? { data: { ...c.data } } : {} }));
|
|
1676
|
+
}
|
|
1677
|
+
get size() {
|
|
1678
|
+
return this.items.length;
|
|
1679
|
+
}
|
|
1680
|
+
clear() {
|
|
1681
|
+
this.items = [];
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
function stripUrl(url) {
|
|
1685
|
+
return String(url).replace(/[?#].*$/, "").replace(/^([a-z][\w+.-]*:\/\/)[^/@]*@/i, "$1").slice(0, 500);
|
|
1686
|
+
}
|
|
1687
|
+
function describeArg(value) {
|
|
1688
|
+
if (typeof value === "string") return value.slice(0, 500);
|
|
1689
|
+
if (value instanceof Error) return `${value.name}: ${value.message}`.slice(0, 500);
|
|
1690
|
+
try {
|
|
1691
|
+
return (JSON.stringify(value) ?? String(value)).slice(0, 500);
|
|
1692
|
+
} catch {
|
|
1693
|
+
return String(value).slice(0, 500);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
function instrumentConsole(target, emit) {
|
|
1697
|
+
if (!target) return () => {
|
|
1698
|
+
};
|
|
1699
|
+
const undo = [];
|
|
1700
|
+
let inside = false;
|
|
1701
|
+
for (const [method, level] of [
|
|
1702
|
+
["warn", "warning"],
|
|
1703
|
+
["error", "error"]
|
|
1704
|
+
]) {
|
|
1705
|
+
const original = target[method];
|
|
1706
|
+
if (typeof original !== "function") continue;
|
|
1707
|
+
const wrapped = function(...args) {
|
|
1708
|
+
const result = original.apply(this, args);
|
|
1709
|
+
if (!inside && !(typeof args[0] === "string" && args[0].startsWith("[Guidely]"))) {
|
|
1710
|
+
inside = true;
|
|
1711
|
+
try {
|
|
1712
|
+
const parts = args.slice(0, 5).map(describeArg);
|
|
1713
|
+
emit({ type: "debug", category: "console", level, message: parts.join(" ").slice(0, 1e3), data: { arguments: parts, logger: "console" } });
|
|
1714
|
+
} catch {
|
|
1715
|
+
} finally {
|
|
1716
|
+
inside = false;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return result;
|
|
1720
|
+
};
|
|
1721
|
+
target[method] = wrapped;
|
|
1722
|
+
undo.push(() => {
|
|
1723
|
+
if (target[method] === wrapped) target[method] = original;
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
return () => undo.forEach((fn) => fn());
|
|
1727
|
+
}
|
|
1728
|
+
function httpLevel(status) {
|
|
1729
|
+
if (!status) return "error";
|
|
1730
|
+
return status >= 500 ? "error" : status >= 400 ? "warning" : "info";
|
|
1731
|
+
}
|
|
1732
|
+
var XHR_INFO = "__guidelyXhr";
|
|
1733
|
+
function instrumentHttp(g, emit, skip, now = Date.now) {
|
|
1734
|
+
if (!g) return () => {
|
|
1735
|
+
};
|
|
1736
|
+
const undo = [];
|
|
1737
|
+
let inFetch = 0;
|
|
1738
|
+
const record2 = (category, method, url, start, status) => {
|
|
1739
|
+
try {
|
|
1740
|
+
const data = { method, url: stripUrl(url), ...status ? { status_code: status } : {}, duration_ms: Math.max(0, Math.round(now() - start)) };
|
|
1741
|
+
emit({ type: "http", category, level: httpLevel(status), data });
|
|
1742
|
+
} catch {
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
const originalFetch = g.fetch;
|
|
1746
|
+
if (typeof originalFetch === "function") {
|
|
1747
|
+
const wrappedFetch = function(...args) {
|
|
1748
|
+
const [input, init] = args;
|
|
1749
|
+
let url = "";
|
|
1750
|
+
let method = "GET";
|
|
1751
|
+
try {
|
|
1752
|
+
const req = input;
|
|
1753
|
+
url = typeof req === "string" ? req : typeof req?.url === "string" ? req.url : String(input);
|
|
1754
|
+
method = String(init?.method ?? (req && typeof req === "object" ? req.method : void 0) ?? "GET").toUpperCase();
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
if (skip(url)) return originalFetch.apply(this, args);
|
|
1758
|
+
const start = now();
|
|
1759
|
+
let result;
|
|
1760
|
+
inFetch += 1;
|
|
1761
|
+
try {
|
|
1762
|
+
result = originalFetch.apply(this, args);
|
|
1763
|
+
} finally {
|
|
1764
|
+
inFetch -= 1;
|
|
1765
|
+
}
|
|
1766
|
+
if (!result || typeof result.then !== "function") return result;
|
|
1767
|
+
return result.then(
|
|
1768
|
+
(res) => {
|
|
1769
|
+
record2("fetch", method, url, start, typeof res?.status === "number" ? res.status : void 0);
|
|
1770
|
+
return res;
|
|
1771
|
+
},
|
|
1772
|
+
(err) => {
|
|
1773
|
+
record2("fetch", method, url, start, void 0);
|
|
1774
|
+
throw err;
|
|
1775
|
+
}
|
|
1776
|
+
);
|
|
1777
|
+
};
|
|
1778
|
+
g.fetch = wrappedFetch;
|
|
1779
|
+
undo.push(() => {
|
|
1780
|
+
if (g.fetch === wrappedFetch) g.fetch = originalFetch;
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
const proto = g.XMLHttpRequest?.prototype;
|
|
1784
|
+
const originalOpen = proto?.open;
|
|
1785
|
+
const originalSend = proto?.send;
|
|
1786
|
+
if (proto && typeof originalOpen === "function" && typeof originalSend === "function") {
|
|
1787
|
+
const open = function(...args) {
|
|
1788
|
+
try {
|
|
1789
|
+
this[XHR_INFO] = { method: String(args[0] ?? "GET").toUpperCase(), url: String(args[1] ?? ""), viaFetch: inFetch > 0 };
|
|
1790
|
+
} catch {
|
|
1791
|
+
}
|
|
1792
|
+
return originalOpen.apply(this, args);
|
|
1793
|
+
};
|
|
1794
|
+
const send = function(...args) {
|
|
1795
|
+
try {
|
|
1796
|
+
const info = this[XHR_INFO];
|
|
1797
|
+
if (info && !info.viaFetch && !skip(info.url) && typeof this.addEventListener === "function") {
|
|
1798
|
+
info.start = now();
|
|
1799
|
+
this.addEventListener("readystatechange", () => {
|
|
1800
|
+
if (this.readyState !== 4 || info.done) return;
|
|
1801
|
+
info.done = true;
|
|
1802
|
+
record2("xhr", info.method, info.url, info.start ?? now(), typeof this.status === "number" && this.status > 0 ? this.status : void 0);
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
} catch {
|
|
1806
|
+
}
|
|
1807
|
+
return originalSend.apply(this, args);
|
|
1808
|
+
};
|
|
1809
|
+
proto.open = open;
|
|
1810
|
+
proto.send = send;
|
|
1811
|
+
undo.push(() => {
|
|
1812
|
+
if (proto.open === open) proto.open = originalOpen;
|
|
1813
|
+
if (proto.send === send) proto.send = originalSend;
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
return () => undo.forEach((fn) => fn());
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// src/core/crashHandlers.ts
|
|
1820
|
+
var FATAL_FLUSH_MS = 2e3;
|
|
1821
|
+
var isThenable = (v) => !!v && typeof v.then === "function";
|
|
1822
|
+
function installGlobalHandler(g, report, waitMs = FATAL_FLUSH_MS) {
|
|
1823
|
+
const eu = g.ErrorUtils;
|
|
1824
|
+
if (!eu || typeof eu.setGlobalHandler !== "function") return () => {
|
|
1825
|
+
};
|
|
1826
|
+
let previous;
|
|
1827
|
+
try {
|
|
1828
|
+
previous = eu.getGlobalHandler?.();
|
|
1829
|
+
} catch {
|
|
1830
|
+
previous = void 0;
|
|
1831
|
+
}
|
|
1832
|
+
let active = true;
|
|
1833
|
+
let handlingFatal = false;
|
|
1834
|
+
const handler = (error, isFatal) => {
|
|
1835
|
+
const fatal = !!isFatal;
|
|
1836
|
+
const callPrevious = () => previous?.(error, isFatal);
|
|
1837
|
+
if (!active || fatal && handlingFatal) return callPrevious();
|
|
1838
|
+
let pending;
|
|
1839
|
+
try {
|
|
1840
|
+
pending = report(error, fatal);
|
|
1841
|
+
} catch {
|
|
1842
|
+
}
|
|
1843
|
+
if (!fatal || g.__DEV__ || !isThenable(pending)) return callPrevious();
|
|
1844
|
+
handlingFatal = true;
|
|
1845
|
+
let done = false;
|
|
1846
|
+
const finish = () => {
|
|
1847
|
+
if (done) return;
|
|
1848
|
+
done = true;
|
|
1849
|
+
clearTimeout(timer);
|
|
1850
|
+
handlingFatal = false;
|
|
1851
|
+
try {
|
|
1852
|
+
callPrevious();
|
|
1853
|
+
} catch {
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
const timer = setTimeout(finish, waitMs);
|
|
1857
|
+
pending.then(finish, finish);
|
|
1858
|
+
};
|
|
1859
|
+
try {
|
|
1860
|
+
eu.setGlobalHandler(handler);
|
|
1861
|
+
} catch {
|
|
1862
|
+
return () => {
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
return () => {
|
|
1866
|
+
active = false;
|
|
1867
|
+
try {
|
|
1868
|
+
if (previous && eu.getGlobalHandler?.() === handler) eu.setGlobalHandler(previous);
|
|
1869
|
+
} catch {
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
function describeRejection(rejection) {
|
|
1874
|
+
if (rejection instanceof Error) return rejection.stack || `${rejection.name}: ${rejection.message}`;
|
|
1875
|
+
try {
|
|
1876
|
+
return typeof rejection === "string" ? rejection : JSON.stringify(rejection) ?? String(rejection);
|
|
1877
|
+
} catch {
|
|
1878
|
+
return String(rejection);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
function hermesTracker(g) {
|
|
1882
|
+
const h = g.HermesInternal;
|
|
1883
|
+
try {
|
|
1884
|
+
if (!h || typeof h.enablePromiseRejectionTracker !== "function" || typeof h.hasPromise === "function" && !h.hasPromise()) return null;
|
|
1885
|
+
} catch {
|
|
1886
|
+
return null;
|
|
1887
|
+
}
|
|
1888
|
+
return { enable: (options) => h.enablePromiseRejectionTracker(options) };
|
|
1889
|
+
}
|
|
1890
|
+
function installRejectionTracking(g, report, tracker) {
|
|
1891
|
+
let active = true;
|
|
1892
|
+
const warn = (message) => {
|
|
1893
|
+
try {
|
|
1894
|
+
g.console?.warn?.(message);
|
|
1895
|
+
} catch {
|
|
1896
|
+
}
|
|
1897
|
+
};
|
|
1898
|
+
const options = {
|
|
1899
|
+
allRejections: true,
|
|
1900
|
+
onUnhandled: (id, rejection) => {
|
|
1901
|
+
if (active) {
|
|
1902
|
+
try {
|
|
1903
|
+
report(rejection);
|
|
1904
|
+
} catch {
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
if (g.__DEV__) warn(`Possible unhandled promise rejection (id: ${id}):
|
|
1908
|
+
${describeRejection(rejection)}`);
|
|
1909
|
+
},
|
|
1910
|
+
onHandled: (id) => {
|
|
1911
|
+
if (g.__DEV__) warn(`Promise rejection handled (id: ${id})
|
|
1912
|
+
This means you can ignore any previous messages of the form "Possible unhandled promise rejection (id: ${id}):"`);
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
const t = tracker ?? hermesTracker(g);
|
|
1916
|
+
if (t && typeof t.enable === "function") {
|
|
1917
|
+
try {
|
|
1918
|
+
t.enable(options);
|
|
1919
|
+
return () => {
|
|
1920
|
+
active = false;
|
|
1921
|
+
};
|
|
1922
|
+
} catch {
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
if (typeof g.addEventListener === "function") {
|
|
1926
|
+
const listener = (event) => {
|
|
1927
|
+
if (active) report(event?.reason);
|
|
1928
|
+
};
|
|
1929
|
+
try {
|
|
1930
|
+
g.addEventListener("unhandledrejection", listener);
|
|
1931
|
+
return () => {
|
|
1932
|
+
active = false;
|
|
1933
|
+
g.removeEventListener?.("unhandledrejection", listener);
|
|
1934
|
+
};
|
|
1935
|
+
} catch {
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
return () => {
|
|
1939
|
+
active = false;
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
function runtimeContext(g) {
|
|
1943
|
+
const h = g.HermesInternal;
|
|
1944
|
+
if (!h) return { name: "jsc" };
|
|
1945
|
+
try {
|
|
1946
|
+
const version = h.getRuntimeProperties?.()?.["OSS Release Version"];
|
|
1947
|
+
return { name: "hermes", ...typeof version === "string" && version ? { version: version.slice(0, 50) } : {} };
|
|
1948
|
+
} catch {
|
|
1949
|
+
return { name: "hermes" };
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// src/core/stack.ts
|
|
1954
|
+
var AT = /^\s*at (?:(.+?) \()?(?:address at )?(.+?):(\d+):(\d+)\)?\s*$/;
|
|
1955
|
+
var AT_NATIVE = /^\s*at (?:(.+?) \()?(?:native|\[native code\])\)?\s*$/;
|
|
1956
|
+
var JSC = /^\s*([^@]*)@(.+?):(\d+):(\d+)\s*$/;
|
|
1957
|
+
var JSC_BARE = /^\s*(\S+?):(\d+):(\d+)\s*$/;
|
|
1958
|
+
var JSC_NATIVE = /^\s*(?:([^@]*)@)?\[native code\]\s*$/;
|
|
1959
|
+
var BUNDLE = /\.(jsbundle|bundle|hbc)$/i;
|
|
1960
|
+
function normalizeBundleFile(file) {
|
|
1961
|
+
const f = file.trim();
|
|
1962
|
+
if (/^https?:\/\//i.test(f)) return f.replace(/[?#].*$/, "");
|
|
1963
|
+
const path = f.replace(/^file:\/\//i, "").replace(/[?#].*$/, "");
|
|
1964
|
+
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
1965
|
+
return BUNDLE.test(base) ? reactNativeBundleUrl(path) : path;
|
|
1966
|
+
}
|
|
1967
|
+
function inAppFor(file) {
|
|
1968
|
+
if (!file) return false;
|
|
1969
|
+
if (/(^|\/)node_modules\//.test(file) || /(^|\/)InternalBytecode\.js$/.test(file)) return false;
|
|
1970
|
+
if (file.startsWith("app:///") || /^https?:\/\//i.test(file)) return void 0;
|
|
1971
|
+
return true;
|
|
1972
|
+
}
|
|
1973
|
+
function frame(fn, file, line, col) {
|
|
1974
|
+
const name = (fn || "").trim();
|
|
1975
|
+
const normalized = file ? normalizeBundleFile(file).slice(0, 500) : void 0;
|
|
1976
|
+
const inApp = inAppFor(normalized);
|
|
1977
|
+
return {
|
|
1978
|
+
...name ? { fn: name.slice(0, 200) } : {},
|
|
1979
|
+
...normalized ? { file: normalized } : {},
|
|
1980
|
+
...line ? { line: Number(line) } : {},
|
|
1981
|
+
...col ? { col: Number(col) } : {},
|
|
1982
|
+
...inApp !== void 0 ? { inApp } : {}
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
function parseReactNativeStack(stack, max = MAX_DETAIL_FRAMES) {
|
|
1986
|
+
if (!stack || typeof stack !== "string") return [];
|
|
1987
|
+
const frames = [];
|
|
1988
|
+
for (const raw of stack.split("\n")) {
|
|
1989
|
+
if (frames.length >= max) break;
|
|
1990
|
+
const line = raw.slice(0, 1e3);
|
|
1991
|
+
let m;
|
|
1992
|
+
if (m = AT_NATIVE.exec(line)) frames.push(frame(m[1], void 0));
|
|
1993
|
+
else if (m = AT.exec(line)) frames.push(frame(m[1], m[2], m[3], m[4]));
|
|
1994
|
+
else if (m = JSC_NATIVE.exec(line)) frames.push(frame(m[1], void 0));
|
|
1995
|
+
else if (m = JSC.exec(line)) frames.push(frame(m[1], m[2], m[3], m[4]));
|
|
1996
|
+
else if (m = JSC_BARE.exec(line)) frames.push(frame(void 0, m[1], m[2], m[3]));
|
|
1997
|
+
}
|
|
1998
|
+
return frames;
|
|
1999
|
+
}
|
|
2000
|
+
function toDetailFrames(frames) {
|
|
2001
|
+
return frames.slice().reverse().map((f) => ({
|
|
2002
|
+
...f.fn ? { function: f.fn } : {},
|
|
2003
|
+
...f.file ? { filename: f.file, abs_path: f.file } : {},
|
|
2004
|
+
...f.line !== void 0 ? { lineno: f.line } : {},
|
|
2005
|
+
...f.col !== void 0 ? { colno: f.col } : {},
|
|
2006
|
+
...f.inApp !== void 0 ? { in_app: f.inApp } : {}
|
|
2007
|
+
}));
|
|
2008
|
+
}
|
|
2009
|
+
var MAX_FRAMES_BYTES = 6e3;
|
|
2010
|
+
function fitPropertyFrames(frames) {
|
|
2011
|
+
const out = frames.slice(0, 30);
|
|
2012
|
+
while (out.length > 1 && JSON.stringify(out).length > MAX_FRAMES_BYTES) out.pop();
|
|
2013
|
+
return out;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// src/core/errors.ts
|
|
2017
|
+
var CONTEXT_KEYS = ["tags", "extra", "level", "fingerprint", "contexts", "user"];
|
|
2018
|
+
function splitHint(hint) {
|
|
2019
|
+
const ctx = {};
|
|
2020
|
+
const properties = {};
|
|
2021
|
+
for (const [k, v] of Object.entries(hint && typeof hint === "object" ? hint : {})) (CONTEXT_KEYS.includes(k) ? ctx : properties)[k] = v;
|
|
2022
|
+
return { ctx, properties };
|
|
2023
|
+
}
|
|
2024
|
+
var nowSeconds = () => Date.now() / 1e3;
|
|
2025
|
+
var ErrorScope = class {
|
|
2026
|
+
constructor(opts) {
|
|
2027
|
+
__publicField(this, "opts", opts);
|
|
2028
|
+
__publicField(this, "tags", {});
|
|
2029
|
+
__publicField(this, "extra", {});
|
|
2030
|
+
__publicField(this, "contexts", {});
|
|
2031
|
+
__publicField(this, "user", {});
|
|
2032
|
+
__publicField(this, "crumbs", new BreadcrumbBuffer(MAX_BREADCRUMBS));
|
|
2033
|
+
}
|
|
2034
|
+
get max() {
|
|
2035
|
+
return Math.max(0, Math.min(MAX_BREADCRUMBS, this.opts()?.maxBreadcrumbs ?? MAX_BREADCRUMBS));
|
|
2036
|
+
}
|
|
2037
|
+
addBreadcrumb(crumb, hint) {
|
|
2038
|
+
if (!crumb || typeof crumb !== "object") return;
|
|
2039
|
+
let c = { timestamp: nowSeconds(), ...crumb };
|
|
2040
|
+
const before = this.opts()?.beforeBreadcrumb;
|
|
2041
|
+
if (before) {
|
|
2042
|
+
try {
|
|
2043
|
+
c = before(c, hint);
|
|
2044
|
+
} catch {
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
const max = this.max;
|
|
2048
|
+
if (!c || !max) return;
|
|
2049
|
+
this.crumbs.add(c, max);
|
|
2050
|
+
}
|
|
2051
|
+
breadcrumbs() {
|
|
2052
|
+
return this.crumbs.values();
|
|
2053
|
+
}
|
|
2054
|
+
clear() {
|
|
2055
|
+
this.tags = {};
|
|
2056
|
+
this.extra = {};
|
|
2057
|
+
this.contexts = {};
|
|
2058
|
+
this.user = {};
|
|
2059
|
+
this.crumbs.clear();
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
var isError = (v) => v instanceof Error || !!v && typeof v === "object" && typeof v.message === "string" && typeof v.stack === "string";
|
|
2063
|
+
function describe(value) {
|
|
2064
|
+
if (typeof value === "string") return value;
|
|
2065
|
+
try {
|
|
2066
|
+
return JSON.stringify(value) ?? String(value);
|
|
2067
|
+
} catch {
|
|
2068
|
+
return String(value);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
function exceptionFromValue(error, source, handled, fallbackType = "Error") {
|
|
2072
|
+
const e = error;
|
|
2073
|
+
const ok = isError(error);
|
|
2074
|
+
return {
|
|
2075
|
+
type: (ok && e.name || fallbackType).slice(0, 200),
|
|
2076
|
+
message: (ok ? e.message : describe(error)).slice(0, 1e3),
|
|
2077
|
+
frames: ok ? fitPropertyFrames(parseReactNativeStack(e.stack)) : [],
|
|
2078
|
+
source,
|
|
2079
|
+
handled
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
function exceptionChain(error, main, mechanism) {
|
|
2083
|
+
const mainFrames = isError(error) ? parseReactNativeStack(error.stack) : main.frames;
|
|
2084
|
+
const chain = [{ type: main.type, value: main.message, mechanism: { type: mechanism, handled: main.handled !== false, exception_id: 0 }, ...mainFrames.length ? { stacktrace: { frames: toDetailFrames(mainFrames) } } : {} }];
|
|
2085
|
+
const seen = /* @__PURE__ */ new Set([error]);
|
|
2086
|
+
let cur = isError(error) ? error.cause : void 0;
|
|
2087
|
+
while (cur !== void 0 && cur !== null && !seen.has(cur) && chain.length < MAX_EXCEPTION_CHAIN) {
|
|
2088
|
+
seen.add(cur);
|
|
2089
|
+
const e = cur;
|
|
2090
|
+
const id = chain.length;
|
|
2091
|
+
const frames = isError(e) ? parseReactNativeStack(e.stack, 50) : [];
|
|
2092
|
+
chain.push({
|
|
2093
|
+
type: (isError(e) && e.name || "Error").slice(0, 200),
|
|
2094
|
+
value: (isError(e) ? e.message : describe(cur)).slice(0, 1e3),
|
|
2095
|
+
mechanism: { type: "chained", source: "cause", handled: true, exception_id: id, parent_id: id - 1 },
|
|
2096
|
+
...frames.length ? { stacktrace: { frames: toDetailFrames(frames) } } : {}
|
|
2097
|
+
});
|
|
2098
|
+
cur = isError(e) ? e.cause : void 0;
|
|
2099
|
+
}
|
|
2100
|
+
return chain.reverse();
|
|
2101
|
+
}
|
|
2102
|
+
var SENSITIVE_KEY = new RegExp(`${SENSITIVE_PROPERTY_PATTERN.source}|authorization|cookie|api[-_]?key`, "i");
|
|
2103
|
+
function normalizeValue(value, depth = 5, seen = /* @__PURE__ */ new Set()) {
|
|
2104
|
+
if (value === null || typeof value === "boolean") return value;
|
|
2105
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
|
|
2106
|
+
if (typeof value === "string") return value.slice(0, 5e3);
|
|
2107
|
+
if (typeof value === "bigint") return value.toString();
|
|
2108
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") return void 0;
|
|
2109
|
+
if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.toISOString() : String(value);
|
|
2110
|
+
if (isError(value)) return { name: value.name, message: value.message.slice(0, 1e3) };
|
|
2111
|
+
if (typeof value !== "object" || seen.has(value)) return "[Circular]";
|
|
2112
|
+
if (depth <= 0) return Array.isArray(value) ? "[Array]" : "[Object]";
|
|
2113
|
+
seen.add(value);
|
|
2114
|
+
try {
|
|
2115
|
+
if (Array.isArray(value)) return value.slice(0, MAX_DETAIL_FRAMES).map((v) => normalizeValue(v, depth - 1, seen) ?? null);
|
|
2116
|
+
const out = {};
|
|
2117
|
+
let n = 0;
|
|
2118
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2119
|
+
if (n++ >= 100) break;
|
|
2120
|
+
if (SENSITIVE_KEY.test(k)) out[k] = "[Filtered]";
|
|
2121
|
+
else {
|
|
2122
|
+
const nv = normalizeValue(v, depth - 1, seen);
|
|
2123
|
+
if (nv !== void 0) out[k] = nv;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
return out;
|
|
2127
|
+
} finally {
|
|
2128
|
+
seen.delete(value);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
var record = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
2132
|
+
var jsonSize = (v) => {
|
|
2133
|
+
try {
|
|
2134
|
+
return JSON.stringify(v)?.length ?? 0;
|
|
2135
|
+
} catch {
|
|
2136
|
+
return Infinity;
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
function finalizeDetail(detail, max = MAX_EXCEPTION_DETAIL_BYTES) {
|
|
2140
|
+
let d = normalizeValue(detail, 12);
|
|
2141
|
+
if (!d || typeof d !== "object") return void 0;
|
|
2142
|
+
if (d.tags) d.tags = normalizeTags(Object.fromEntries(Object.entries(d.tags).filter(([k]) => !SENSITIVE_KEY.test(k))));
|
|
2143
|
+
d = trimExceptionDetail(d, max);
|
|
2144
|
+
if (jsonSize(d) <= max) return d;
|
|
2145
|
+
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 } : {} };
|
|
2146
|
+
return jsonSize(slim) <= max ? slim : void 0;
|
|
2147
|
+
}
|
|
2148
|
+
function buildDetail(input) {
|
|
2149
|
+
const { scope, ctx } = input;
|
|
2150
|
+
const contexts = { ...input.contexts, ...scope.contexts };
|
|
2151
|
+
for (const [k, v] of Object.entries(record(ctx.contexts))) {
|
|
2152
|
+
if (v && typeof v === "object") contexts[k] = v;
|
|
2153
|
+
else delete contexts[k];
|
|
2154
|
+
}
|
|
2155
|
+
const user = { ...input.userId ? { id: input.userId } : {}, ...scope.user, ...record(ctx.user) };
|
|
2156
|
+
const message = input.props.type === "Message";
|
|
2157
|
+
return {
|
|
2158
|
+
level: input.level,
|
|
2159
|
+
platform: "javascript",
|
|
2160
|
+
timestamp: nowSeconds(),
|
|
2161
|
+
...input.release ? { release: input.release } : {},
|
|
2162
|
+
...input.dist ? { dist: input.dist } : {},
|
|
2163
|
+
...input.environment ? { environment: input.environment } : {},
|
|
2164
|
+
...message ? { message: { formatted: input.props.message } } : { exception: { values: exceptionChain(input.error, input.props, input.mechanism) } },
|
|
2165
|
+
breadcrumbs: { values: scope.breadcrumbs() },
|
|
2166
|
+
contexts,
|
|
2167
|
+
tags: normalizeTags({ ...scope.tags, ...record(ctx.tags) }),
|
|
2168
|
+
extra: { ...scope.extra, ...record(ctx.extra) },
|
|
2169
|
+
...Object.keys(user).length ? { user } : {},
|
|
2170
|
+
...Array.isArray(ctx.fingerprint) && ctx.fingerprint.length ? { fingerprint: ctx.fingerprint.map(String) } : {},
|
|
2171
|
+
sdk: input.sdk
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
function prepareError(props, extraProperties, detail, opts, fatal, release) {
|
|
2175
|
+
const file = props.frames.find((f) => f.file)?.file;
|
|
2176
|
+
if (opts && errorFilterDrops({ type: props.type, message: props.message, file }, opts)) return null;
|
|
2177
|
+
if (typeof opts?.sampleRate === "number" && opts.sampleRate < 1 && Math.random() >= Math.max(0, opts.sampleRate)) return null;
|
|
2178
|
+
const values = detail.exception?.values ?? [];
|
|
2179
|
+
const thrown = values[values.length - 1];
|
|
2180
|
+
const mechanism = thrown?.mechanism?.type ?? props.source;
|
|
2181
|
+
let properties = { ...props, level: detail.level ?? "error", platform: "javascript", ...mechanism ? { mechanism } : {} };
|
|
2182
|
+
if (release) properties.release = release.slice(0, 100);
|
|
2183
|
+
if (detail.fingerprint?.length) properties.fingerprintParts = detail.fingerprint.slice(0, 20);
|
|
2184
|
+
for (const [k, v] of Object.entries(extraProperties)) if (!(k in properties)) properties[k] = v;
|
|
2185
|
+
let d = detail;
|
|
2186
|
+
if (opts?.beforeSend) {
|
|
2187
|
+
try {
|
|
2188
|
+
const out = opts.beforeSend({ properties, detail: d });
|
|
2189
|
+
if (out === null) return null;
|
|
2190
|
+
if (out && typeof out === "object") {
|
|
2191
|
+
if (out.properties && typeof out.properties === "object") properties = out.properties;
|
|
2192
|
+
if (out.detail && typeof out.detail === "object") d = out.detail;
|
|
2193
|
+
}
|
|
2194
|
+
} catch {
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
const finalDetail = finalizeDetail(d);
|
|
2198
|
+
return { eventId: randomId(24), occurredAt: (/* @__PURE__ */ new Date()).toISOString(), properties, ...finalDetail ? { detail: finalDetail } : {}, fatal };
|
|
2199
|
+
}
|
|
2200
|
+
var ExceptionThrottle = class {
|
|
2201
|
+
constructor(max = 5) {
|
|
2202
|
+
__publicField(this, "max", max);
|
|
2203
|
+
__publicField(this, "seen", /* @__PURE__ */ new Map());
|
|
2204
|
+
}
|
|
2205
|
+
allow(props) {
|
|
2206
|
+
const frames = Array.isArray(props.frames) ? props.frames : [];
|
|
2207
|
+
const top = frames[0];
|
|
2208
|
+
const key = `${String(props.type)}|${String(props.message)}|${top ? `${top.file}:${top.line}:${top.col}` : ""}`;
|
|
2209
|
+
const n = this.seen.get(key) ?? 0;
|
|
2210
|
+
if (n >= this.max) return false;
|
|
2211
|
+
if (this.seen.size > 500) this.seen.clear();
|
|
2212
|
+
this.seen.set(key, n + 1);
|
|
2213
|
+
return true;
|
|
2214
|
+
}
|
|
2215
|
+
};
|
|
2216
|
+
function rnVersion(v) {
|
|
2217
|
+
if (!v) return void 0;
|
|
2218
|
+
if (typeof v === "string") return v;
|
|
2219
|
+
if (typeof v.major !== "number") return void 0;
|
|
2220
|
+
return `${v.major}.${v.minor ?? 0}.${v.patch ?? 0}${v.prerelease ? `-${v.prerelease}` : ""}`;
|
|
2221
|
+
}
|
|
2222
|
+
function reactNativeContexts(device, app, g) {
|
|
2223
|
+
const runtime = g ? runtimeContext(g) : void 0;
|
|
2224
|
+
const version = rnVersion(device.reactNativeVersion);
|
|
2225
|
+
return {
|
|
2226
|
+
os: { name: device.platform === "android" ? "Android" : "iOS", ...device.osVersion ? { version: device.osVersion } : {} },
|
|
2227
|
+
device: {
|
|
2228
|
+
family: device.deviceType === "tablet" ? "Tablet" : "Mobile",
|
|
2229
|
+
...device.model ? { model: device.model } : {},
|
|
2230
|
+
...device.manufacturer ? { manufacturer: device.manufacturer } : {},
|
|
2231
|
+
...device.brand ? { brand: device.brand } : {},
|
|
2232
|
+
...device.simulator !== void 0 ? { simulator: device.simulator } : {}
|
|
2233
|
+
},
|
|
2234
|
+
app: {
|
|
2235
|
+
...app.appVersion ? { app_version: app.appVersion } : {},
|
|
2236
|
+
...app.build ? { app_build: app.build } : {},
|
|
2237
|
+
...app.inForeground !== void 0 ? { in_foreground: app.inForeground } : {}
|
|
2238
|
+
},
|
|
2239
|
+
...runtime ? { runtime } : {},
|
|
2240
|
+
react_native_context: {
|
|
2241
|
+
...runtime ? { js_engine: runtime.name } : {},
|
|
2242
|
+
...version ? { react_native_version: version } : {},
|
|
2243
|
+
...g ? { turbo_module: !!g.__turboModuleProxy || !!g.RN$Bridgeless, fabric: !!g.nativeFabricUIManager, expo: !!g.expo } : {}
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
|
|
1549
2248
|
// src/core/targets.ts
|
|
1550
2249
|
var TargetTimeoutError = class extends Error {
|
|
1551
2250
|
constructor(ids) {
|
|
@@ -2876,38 +3575,38 @@ var SessionRecorder = class {
|
|
|
2876
3575
|
this.capturing = true;
|
|
2877
3576
|
this.captureSoon = false;
|
|
2878
3577
|
const gen = this.generation;
|
|
2879
|
-
let
|
|
3578
|
+
let frame2 = null;
|
|
2880
3579
|
try {
|
|
2881
|
-
|
|
3580
|
+
frame2 = await this.opts.capturer(await this.opts.captureOptions());
|
|
2882
3581
|
} catch (err) {
|
|
2883
3582
|
this.opts.onError?.(err);
|
|
2884
3583
|
}
|
|
2885
3584
|
if (gen !== this.generation) return;
|
|
2886
3585
|
this.capturing = false;
|
|
2887
|
-
if (
|
|
3586
|
+
if (frame2) this.onFrame(frame2, this.now());
|
|
2888
3587
|
if (this.sid !== null && this.foreground) this.schedule(this.captureSoon ? TOUCH_SETTLE_MS : this.currentIntervalMs);
|
|
2889
3588
|
}
|
|
2890
|
-
onFrame(
|
|
3589
|
+
onFrame(frame2, at) {
|
|
2891
3590
|
const id = this.sid;
|
|
2892
|
-
if (id === null || !(
|
|
2893
|
-
const sizeChanged =
|
|
3591
|
+
if (id === null || !(frame2.width > 0) || !(frame2.height > 0) || !frame2.base64) return;
|
|
3592
|
+
const sizeChanged = frame2.width !== this.viewportWidth || frame2.height !== this.viewportHeight;
|
|
2894
3593
|
const keyframe = this.needKeyframe || !this.hasKeyframe || sizeChanged;
|
|
2895
|
-
if (!keyframe && this.lastFrame ===
|
|
3594
|
+
if (!keyframe && this.lastFrame === frame2.base64) {
|
|
2896
3595
|
this.unchanged++;
|
|
2897
3596
|
return;
|
|
2898
3597
|
}
|
|
2899
3598
|
this.unchanged = 0;
|
|
2900
|
-
this.lastFrame =
|
|
2901
|
-
this.originX =
|
|
2902
|
-
this.originY =
|
|
2903
|
-
this.viewportWidth =
|
|
2904
|
-
this.viewportHeight =
|
|
2905
|
-
const url = RrwebEvents.dataUrl(
|
|
3599
|
+
this.lastFrame = frame2.base64;
|
|
3600
|
+
this.originX = frame2.originX ?? 0;
|
|
3601
|
+
this.originY = frame2.originY ?? 0;
|
|
3602
|
+
this.viewportWidth = frame2.width;
|
|
3603
|
+
this.viewportHeight = frame2.height;
|
|
3604
|
+
const url = RrwebEvents.dataUrl(frame2.base64);
|
|
2906
3605
|
const up = this.opts.uploader;
|
|
2907
3606
|
if (!keyframe) return up.push(id, RrwebEvents.frame(at, url));
|
|
2908
3607
|
this.needKeyframe = false;
|
|
2909
|
-
up.push(id, RrwebEvents.meta(at, this.opts.href(),
|
|
2910
|
-
up.push(id, RrwebEvents.fullSnapshot(at, url,
|
|
3608
|
+
up.push(id, RrwebEvents.meta(at, this.opts.href(), frame2.width, frame2.height));
|
|
3609
|
+
up.push(id, RrwebEvents.fullSnapshot(at, url, frame2.width, frame2.height));
|
|
2911
3610
|
if (!this.hasKeyframe) {
|
|
2912
3611
|
this.hasKeyframe = true;
|
|
2913
3612
|
for (const [ts, payload] of this.pendingCustom) up.push(id, RrwebEvents.custom(Math.max(ts, at), "guidely", payload));
|
|
@@ -2969,7 +3668,7 @@ var PersistentStore = class {
|
|
|
2969
3668
|
|
|
2970
3669
|
// src/core/client.ts
|
|
2971
3670
|
var SDK_NAME = "@guidelytech/react-native";
|
|
2972
|
-
var SDK_VERSION = "0.
|
|
3671
|
+
var SDK_VERSION = "0.17.0";
|
|
2973
3672
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
2974
3673
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2975
3674
|
var CACHED_START_MS = 1500;
|
|
@@ -2984,6 +3683,10 @@ function settlesWithin(promise, ms) {
|
|
|
2984
3683
|
});
|
|
2985
3684
|
}
|
|
2986
3685
|
var appVersionKey = (key) => `guidely:app:${key}`;
|
|
3686
|
+
var crashKey = (key) => `guidely:crash:${key}`;
|
|
3687
|
+
var earlyCrashKey = (key) => `guidely:crash-early:${key}`;
|
|
3688
|
+
var MAX_STORED_CRASHES = 10;
|
|
3689
|
+
var MAX_SESSION_EXCEPTIONS = 50;
|
|
2987
3690
|
var UNKNOWN_VIEW = { selector: "View", tag: "View" };
|
|
2988
3691
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2989
3692
|
var emptyState = () => ({ guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] });
|
|
@@ -3000,6 +3703,8 @@ var GuidelyClient = class {
|
|
|
3000
3703
|
/** Survey answers, guide completions/dismissals and checklist progress, kept until the server has them. */
|
|
3001
3704
|
__publicField(this, "outbox", null);
|
|
3002
3705
|
__publicField(this, "config", null);
|
|
3706
|
+
/** The `environment` option as passed (the resolved one is in `options.environment`). */
|
|
3707
|
+
__publicField(this, "envOption");
|
|
3003
3708
|
__publicField(this, "configFetchedAt", 0);
|
|
3004
3709
|
__publicField(this, "etag", null);
|
|
3005
3710
|
__publicField(this, "anonymousId", "");
|
|
@@ -3021,6 +3726,8 @@ var GuidelyClient = class {
|
|
|
3021
3726
|
__publicField(this, "expanded", /* @__PURE__ */ new Set());
|
|
3022
3727
|
__publicField(this, "surveyView", null);
|
|
3023
3728
|
__publicField(this, "resourceCenterOpen", false);
|
|
3729
|
+
/** The article a help-article link asked the resource center to show (`seq` tells repeats apart). */
|
|
3730
|
+
__publicField(this, "resourceCenterArticle", null);
|
|
3024
3731
|
__publicField(this, "cleanups", []);
|
|
3025
3732
|
__publicField(this, "timers", /* @__PURE__ */ new Set());
|
|
3026
3733
|
__publicField(this, "visibilityWatchers", /* @__PURE__ */ new Map());
|
|
@@ -3061,6 +3768,16 @@ var GuidelyClient = class {
|
|
|
3061
3768
|
__publicField(this, "localizedRc", null);
|
|
3062
3769
|
/** "What's new" last-seen time when the resource center opened (entries after it read as new). */
|
|
3063
3770
|
__publicField(this, "announcementsSeenAtOpen", null);
|
|
3771
|
+
// Error monitoring.
|
|
3772
|
+
__publicField(this, "errorScope", new ErrorScope(() => this.options.errors));
|
|
3773
|
+
__publicField(this, "exceptionThrottle", new ExceptionThrottle());
|
|
3774
|
+
/** Errors captured before storage and a config were loaded (whether error tracking is on is unknown until then). */
|
|
3775
|
+
__publicField(this, "earlyErrors", []);
|
|
3776
|
+
/** Crash reports left by earlier launches, sent once a config says error tracking is on. */
|
|
3777
|
+
__publicField(this, "storedCrashes", []);
|
|
3778
|
+
/** This launch's crash reports (normally one: the app ends right after). */
|
|
3779
|
+
__publicField(this, "launchCrashes", []);
|
|
3780
|
+
__publicField(this, "lastAppState", null);
|
|
3064
3781
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
3065
3782
|
__publicField(this, "eventBus", {
|
|
3066
3783
|
on: (name, cb) => {
|
|
@@ -3074,9 +3791,10 @@ var GuidelyClient = class {
|
|
|
3074
3791
|
}
|
|
3075
3792
|
});
|
|
3076
3793
|
if (!options || !options.projectId) throw new Error("[Guidely] `projectId` (your environment public key, e.g. pk_dev_\u2026) is required.");
|
|
3794
|
+
this.envOption = options.environment || void 0;
|
|
3077
3795
|
this.options = {
|
|
3078
3796
|
...options,
|
|
3079
|
-
environment: options.environment
|
|
3797
|
+
environment: options.environment || environmentFromKey(options.projectId),
|
|
3080
3798
|
apiUrl: options.apiUrl ?? DEFAULT_API_URL,
|
|
3081
3799
|
analytics: options.analytics ?? true,
|
|
3082
3800
|
configRefreshMs: options.configRefreshMs ?? 5 * 60 * 1e3,
|
|
@@ -3092,6 +3810,7 @@ var GuidelyClient = class {
|
|
|
3092
3810
|
if (options.user) this.user = { ...options.user };
|
|
3093
3811
|
this.locale = options.locale ? normalizeLocale(options.locale) || null : null;
|
|
3094
3812
|
this.flags = this.createFlags(options.flags);
|
|
3813
|
+
this.installErrorMonitoring();
|
|
3095
3814
|
}
|
|
3096
3815
|
createFlags(opts) {
|
|
3097
3816
|
const base = { sdkKey: this.options.projectId, apiUrl: this.options.apiUrl, fetch: this.options.fetch, sdk: { name: SDK_NAME, version: SDK_VERSION } };
|
|
@@ -3139,7 +3858,7 @@ var GuidelyClient = class {
|
|
|
3139
3858
|
async bootstrap() {
|
|
3140
3859
|
const key = this.options.projectId;
|
|
3141
3860
|
try {
|
|
3142
|
-
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]);
|
|
3861
|
+
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)]);
|
|
3143
3862
|
if (this.destroyed) return;
|
|
3144
3863
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
3145
3864
|
this.hadIdentity = !!this.anonymousId;
|
|
@@ -3152,7 +3871,9 @@ var GuidelyClient = class {
|
|
|
3152
3871
|
this.state = { ...emptyState(), ...readJson(this.store, KEYS.state(key), {}) };
|
|
3153
3872
|
this.user = this.options.user ? { ...this.options.user } : readJson(this.store, KEYS.user(key), null);
|
|
3154
3873
|
const saved = readJson(this.store, KEYS.session, null);
|
|
3155
|
-
this.
|
|
3874
|
+
this.loadStoredCrashes();
|
|
3875
|
+
const crashed = !!saved?.id && this.storedCrashes.some((c) => c.sessionId === saved.id);
|
|
3876
|
+
this.session = saved?.id && !crashed && !this.sessionExpired(Date.parse(saved.lastActiveAt)) ? saved : newSession();
|
|
3156
3877
|
this.persistSession();
|
|
3157
3878
|
this.lastAudiences = new Set(this.state.audienceIds);
|
|
3158
3879
|
this.syncFlagAttributes();
|
|
@@ -3171,9 +3892,10 @@ var GuidelyClient = class {
|
|
|
3171
3892
|
this.hydrated = true;
|
|
3172
3893
|
const cached = readJson(this.store, KEYS.config(key), null);
|
|
3173
3894
|
this.etag = this.store.get(KEYS.etag(key));
|
|
3174
|
-
if (cached && Array.isArray(cached.guides)) this.
|
|
3895
|
+
if (cached && Array.isArray(cached.guides)) this.useConfig(forPlatform(cached, this.bridge.device.platform));
|
|
3175
3896
|
this.syncMessenger();
|
|
3176
3897
|
this.pending.splice(0).forEach((fn) => fn());
|
|
3898
|
+
this.drainErrors();
|
|
3177
3899
|
const before = this.config?.configVersion;
|
|
3178
3900
|
const fresh = this.fetchConfig();
|
|
3179
3901
|
if (!this.config) await fresh;
|
|
@@ -3198,22 +3920,33 @@ var GuidelyClient = class {
|
|
|
3198
3920
|
if (this.hydrated) fn();
|
|
3199
3921
|
else this.pending.push(fn);
|
|
3200
3922
|
}
|
|
3923
|
+
/** Environment guides and flags target: the `environment` option, else the one the loaded config names, else a guess from the key. */
|
|
3924
|
+
get environment() {
|
|
3925
|
+
return this.envOption ?? this.config?.environment ?? this.options.environment;
|
|
3926
|
+
}
|
|
3927
|
+
useConfig(config) {
|
|
3928
|
+
const env = this.environment;
|
|
3929
|
+
this.config = config;
|
|
3930
|
+
if (this.environment !== env) this.syncFlagAttributes();
|
|
3931
|
+
}
|
|
3201
3932
|
async fetchConfig() {
|
|
3202
3933
|
try {
|
|
3203
3934
|
const res = await this.api.get("/config", { etag: this.etag, params: { platform: this.bridge.device.platform } });
|
|
3204
3935
|
this.configFetchedAt = Date.now();
|
|
3205
3936
|
if (res.status === 304) return true;
|
|
3206
3937
|
if (res.ok && res.data && Array.isArray(res.data.guides)) {
|
|
3207
|
-
|
|
3938
|
+
const config = forPlatform(res.data, this.bridge.device.platform);
|
|
3939
|
+
this.useConfig(config);
|
|
3208
3940
|
this.etag = res.etag;
|
|
3209
3941
|
writeJson(this.store, KEYS.config(this.options.projectId), res.data);
|
|
3210
3942
|
if (res.etag) this.store.set(KEYS.etag(this.options.projectId), res.etag);
|
|
3211
3943
|
else this.store.remove(KEYS.etag(this.options.projectId));
|
|
3212
3944
|
this.syncMessenger();
|
|
3213
3945
|
if (this.hydrated) this.checkLaunch();
|
|
3214
|
-
this.
|
|
3946
|
+
this.drainErrors();
|
|
3947
|
+
this.emitter.emit("config", config);
|
|
3215
3948
|
this.syncReplay();
|
|
3216
|
-
this.log("config loaded",
|
|
3949
|
+
this.log("config loaded", config.configVersion, `${config.guides.length} guides`);
|
|
3217
3950
|
return true;
|
|
3218
3951
|
}
|
|
3219
3952
|
this.reportError(new Error(`[Guidely] config request failed (${res.status})${this.config ? "; using cached config" : ""}`));
|
|
@@ -3254,7 +3987,12 @@ var GuidelyClient = class {
|
|
|
3254
3987
|
}
|
|
3255
3988
|
/** Called by the provider with React Native `AppState` changes. */
|
|
3256
3989
|
setAppState(state) {
|
|
3257
|
-
if (this.destroyed
|
|
3990
|
+
if (this.destroyed) return;
|
|
3991
|
+
if (state !== this.lastAppState) {
|
|
3992
|
+
this.lastAppState = state;
|
|
3993
|
+
this.autoBreadcrumb("appLifecycle", { type: "navigation", category: "app.lifecycle", message: state, data: { state } });
|
|
3994
|
+
}
|
|
3995
|
+
if (!this.hydrated) return;
|
|
3258
3996
|
if (state === "background") {
|
|
3259
3997
|
const wasForeground = this.backgroundedAt === null;
|
|
3260
3998
|
this.backgroundedAt = Date.now();
|
|
@@ -3439,8 +4177,13 @@ var GuidelyClient = class {
|
|
|
3439
4177
|
setConsent(consent) {
|
|
3440
4178
|
this.consent = consent;
|
|
3441
4179
|
this.whenHydrated(() => {
|
|
3442
|
-
if (consent === "denied")
|
|
3443
|
-
|
|
4180
|
+
if (consent === "denied") {
|
|
4181
|
+
this.queue?.clear();
|
|
4182
|
+
this.earlyErrors = [];
|
|
4183
|
+
this.storedCrashes = [];
|
|
4184
|
+
this.launchCrashes = [];
|
|
4185
|
+
void this.writeCrashes();
|
|
4186
|
+
} else if (consent === "granted") void this.queue?.flush();
|
|
3444
4187
|
this.syncReplay();
|
|
3445
4188
|
});
|
|
3446
4189
|
}
|
|
@@ -3464,6 +4207,7 @@ var GuidelyClient = class {
|
|
|
3464
4207
|
accountId: this.user?.account?.id || null,
|
|
3465
4208
|
sessionId: this.session.id,
|
|
3466
4209
|
url: this.collectUrls() && this.screenName ? this.currentUrl() : void 0,
|
|
4210
|
+
...this.appInfo().appVersion ? { appVersion: this.appInfo().appVersion } : {},
|
|
3467
4211
|
...extra
|
|
3468
4212
|
};
|
|
3469
4213
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
@@ -3503,6 +4247,7 @@ var GuidelyClient = class {
|
|
|
3503
4247
|
this.whenHydrated(() => {
|
|
3504
4248
|
const changed = name !== this.screenName;
|
|
3505
4249
|
if (changed) {
|
|
4250
|
+
this.autoBreadcrumb("navigation", { type: "navigation", category: "navigation", data: { ...this.screenName ? { from: this.screenName } : {}, to: name } });
|
|
3506
4251
|
this.leaveScreen();
|
|
3507
4252
|
this.flushHeatmap();
|
|
3508
4253
|
}
|
|
@@ -3666,7 +4411,11 @@ var GuidelyClient = class {
|
|
|
3666
4411
|
}
|
|
3667
4412
|
/** True when taps are worth observing (clicks, rage taps or heatmaps are on); the provider skips resolving views otherwise. */
|
|
3668
4413
|
isTapCaptureOn() {
|
|
3669
|
-
return this.hydrated && (this.autocaptureOn("clicks") || this.autocaptureOn("rageClicks") || this.autocaptureOn("heatmaps"));
|
|
4414
|
+
return this.hydrated && (this.autocaptureOn("clicks") || this.autocaptureOn("rageClicks") || this.autocaptureOn("heatmaps") || this.tapBreadcrumbsOn());
|
|
4415
|
+
}
|
|
4416
|
+
/** Taps become `ui.click` breadcrumbs while errors can be reported (even with click autocapture off). */
|
|
4417
|
+
tapBreadcrumbsOn() {
|
|
4418
|
+
return this.crumbsOn("taps") && this.errorTrackingAllowed();
|
|
3670
4419
|
}
|
|
3671
4420
|
/**
|
|
3672
4421
|
* Records a tap observed by the provider: `click` for an interactive element, `rage_click` for
|
|
@@ -3676,8 +4425,8 @@ var GuidelyClient = class {
|
|
|
3676
4425
|
recordTap(tap) {
|
|
3677
4426
|
try {
|
|
3678
4427
|
if (!this.hydrated || this.destroyed || !Number.isFinite(tap.x) || !Number.isFinite(tap.y)) return;
|
|
3679
|
-
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps") };
|
|
3680
|
-
if (!on.clicks && !on.rage && !on.heat) return;
|
|
4428
|
+
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps"), crumb: this.tapBreadcrumbsOn() };
|
|
4429
|
+
if (!on.clicks && !on.rage && !on.heat && !on.crumb) return;
|
|
3681
4430
|
const t = tap.t ?? Date.now();
|
|
3682
4431
|
const url = this.screenName ? this.currentUrl() : "";
|
|
3683
4432
|
this.tapChain = this.tapChain.then(() => this.processTap(tap, t, url, on)).catch((err) => this.reportError(err));
|
|
@@ -3694,6 +4443,8 @@ var GuidelyClient = class {
|
|
|
3694
4443
|
rect = target.rect;
|
|
3695
4444
|
}
|
|
3696
4445
|
if (this.destroyed) return;
|
|
4446
|
+
if (on.crumb && el) this.autoBreadcrumb("taps", { timestamp: t / 1e3, type: "user", category: "ui.click", message: el.selector, data: { selector: el.selector } });
|
|
4447
|
+
if (!on.clicks && !on.rage && !on.heat) return;
|
|
3697
4448
|
const platform = this.bridge.device.platform;
|
|
3698
4449
|
const maskAll = !!this.config?.settings?.replay?.maskAllText || !!this.options.sessionReplay?.maskAllText;
|
|
3699
4450
|
const eventUrl = this.collectUrls() && url ? url : void 0;
|
|
@@ -3792,6 +4543,277 @@ var GuidelyClient = class {
|
|
|
3792
4543
|
}
|
|
3793
4544
|
}
|
|
3794
4545
|
// ---------------------------------------------------------------------------
|
|
4546
|
+
// Error monitoring (docs/error-monitoring/sdks.md)
|
|
4547
|
+
// ---------------------------------------------------------------------------
|
|
4548
|
+
/**
|
|
4549
|
+
* Reports a caught error as an `exception` event (`handled: true`), grouped into an issue on the
|
|
4550
|
+
* Errors page. The second argument takes Sentry's capture context (`tags`, `extra`, `level`,
|
|
4551
|
+
* `fingerprint`, `contexts`, `user`); any other key is attached to the occurrence as a property.
|
|
4552
|
+
* Needs the project's error tracking on (plus analytics and consent); `errors.captureErrors: false`
|
|
4553
|
+
* does not stop it. Returns the event id (empty when nothing is sent).
|
|
4554
|
+
*/
|
|
4555
|
+
captureException(error, hint) {
|
|
4556
|
+
if (this.destroyed) return "";
|
|
4557
|
+
try {
|
|
4558
|
+
const { ctx, properties } = splitHint(hint);
|
|
4559
|
+
const props = exceptionFromValue(error, "manual", true);
|
|
4560
|
+
return this.deliverError(this.prepareCapture(error, props, properties, ctx, "generic", ctx.level ?? "error", false));
|
|
4561
|
+
} catch (err) {
|
|
4562
|
+
this.reportError(err);
|
|
4563
|
+
return "";
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
/** Reports a message (default level `info`) as its own issue, grouped by the message. */
|
|
4567
|
+
captureMessage(message, level = "info", hint) {
|
|
4568
|
+
if (this.destroyed) return "";
|
|
4569
|
+
try {
|
|
4570
|
+
const { ctx } = splitHint(hint);
|
|
4571
|
+
const props = { type: "Message", message: String(message).slice(0, 1e3), frames: [], source: "message", handled: true };
|
|
4572
|
+
return this.deliverError(this.prepareCapture(void 0, props, {}, ctx, "message", ctx.level ?? level, false));
|
|
4573
|
+
} catch (err) {
|
|
4574
|
+
this.reportError(err);
|
|
4575
|
+
return "";
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
/** Adds a breadcrumb (sent with the next errors; the last 100 are kept). */
|
|
4579
|
+
addBreadcrumb(crumb, hint) {
|
|
4580
|
+
if (!this.destroyed) this.errorScope.addBreadcrumb(crumb, hint);
|
|
4581
|
+
}
|
|
4582
|
+
/** Tags every later error (searchable on the issue page); `null` removes the tag. */
|
|
4583
|
+
setTag(key, value) {
|
|
4584
|
+
if (value === null || value === void 0) delete this.errorScope.tags[key];
|
|
4585
|
+
else this.errorScope.tags[key] = String(value);
|
|
4586
|
+
}
|
|
4587
|
+
setTags(tags) {
|
|
4588
|
+
for (const [k, v] of Object.entries(tags ?? {})) this.setTag(k, v);
|
|
4589
|
+
}
|
|
4590
|
+
/** Arbitrary data attached to every later error. */
|
|
4591
|
+
setExtra(key, value) {
|
|
4592
|
+
if (value === void 0) delete this.errorScope.extra[key];
|
|
4593
|
+
else this.errorScope.extra[key] = value;
|
|
4594
|
+
}
|
|
4595
|
+
setExtras(extras) {
|
|
4596
|
+
for (const [k, v] of Object.entries(extras ?? {})) this.setExtra(k, v);
|
|
4597
|
+
}
|
|
4598
|
+
/** A named context (`setContext("cart", { items: 3 })`) on every later error; `null` removes it. */
|
|
4599
|
+
setContext(name, context) {
|
|
4600
|
+
if (context && typeof context === "object") this.errorScope.contexts[name] = context;
|
|
4601
|
+
else delete this.errorScope.contexts[name];
|
|
4602
|
+
}
|
|
4603
|
+
/**
|
|
4604
|
+
* Sentry's `setUser`: identifies the user by `id` (like `identify`) and keeps `email` / `username`
|
|
4605
|
+
* on their errors. `null` forgets the user (`reset()`).
|
|
4606
|
+
*/
|
|
4607
|
+
setUser(user) {
|
|
4608
|
+
if (!user) {
|
|
4609
|
+
this.errorScope.user = {};
|
|
4610
|
+
return this.reset();
|
|
4611
|
+
}
|
|
4612
|
+
const { id, email, username, ...traits } = user;
|
|
4613
|
+
this.errorScope.user = { ...email ? { email: String(email) } : {}, ...username ? { username: String(username) } : {} };
|
|
4614
|
+
if (id !== void 0 && id !== null && String(id)) void this.identify(String(id), { ...traits, ...email ? { email } : {}, ...username ? { username } : {} });
|
|
4615
|
+
}
|
|
4616
|
+
crumbsOn(kind) {
|
|
4617
|
+
const b = this.options.errors?.breadcrumbs;
|
|
4618
|
+
return b !== false && !(b && b[kind] === false);
|
|
4619
|
+
}
|
|
4620
|
+
autoBreadcrumb(kind, crumb, hint) {
|
|
4621
|
+
if (!this.destroyed && this.crumbsOn(kind)) this.errorScope.addBreadcrumb(crumb, hint);
|
|
4622
|
+
}
|
|
4623
|
+
/** Errors may be captured: analytics on, consent not denied and, once a config says, the project's error tracking on. */
|
|
4624
|
+
errorsWanted() {
|
|
4625
|
+
if (this.destroyed || !this.options.analytics || this.consent === "denied") return false;
|
|
4626
|
+
const s = this.config?.settings;
|
|
4627
|
+
return !this.config || (s?.analyticsEnabled ?? true) && !!s?.errorTracking?.enabled;
|
|
4628
|
+
}
|
|
4629
|
+
/** Errors may be queued now (a config is known and allows them). */
|
|
4630
|
+
errorTrackingAllowed() {
|
|
4631
|
+
return !!this.config && this.errorsWanted();
|
|
4632
|
+
}
|
|
4633
|
+
/** Requests to the Guidely deployment never become breadcrumbs. */
|
|
4634
|
+
isGuidelyUrl(url) {
|
|
4635
|
+
const base = this.options.apiUrl.replace(/\/+$/, "");
|
|
4636
|
+
return !!base && (url === base || url.startsWith(`${base}/`));
|
|
4637
|
+
}
|
|
4638
|
+
release() {
|
|
4639
|
+
return this.options.release ?? this.appInfo().appVersion;
|
|
4640
|
+
}
|
|
4641
|
+
/** Builds and filters one error (`null` when dropped). */
|
|
4642
|
+
prepareCapture(error, props, properties, ctx, mechanism, level, fatal) {
|
|
4643
|
+
if (!this.errorsWanted()) return null;
|
|
4644
|
+
const info = this.appInfo();
|
|
4645
|
+
const release = this.release();
|
|
4646
|
+
const detail = buildDetail({
|
|
4647
|
+
error,
|
|
4648
|
+
props,
|
|
4649
|
+
mechanism,
|
|
4650
|
+
scope: this.errorScope,
|
|
4651
|
+
ctx,
|
|
4652
|
+
level,
|
|
4653
|
+
contexts: reactNativeContexts(this.bridge.device, { ...info, inForeground: this.backgroundedAt === null }, this.bridge.globals),
|
|
4654
|
+
release,
|
|
4655
|
+
dist: this.options.errors?.dist ?? info.build,
|
|
4656
|
+
environment: this.environment,
|
|
4657
|
+
userId: this.user?.id || null,
|
|
4658
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION }
|
|
4659
|
+
});
|
|
4660
|
+
return prepareError(props, properties, detail, this.options.errors, fatal, release);
|
|
4661
|
+
}
|
|
4662
|
+
/** Queues a prepared error, or holds it until storage and a config are loaded. Returns its event id ("" when dropped). */
|
|
4663
|
+
deliverError(p) {
|
|
4664
|
+
if (!p) return "";
|
|
4665
|
+
if (!this.hydrated || !this.config) {
|
|
4666
|
+
if (this.earlyErrors.length >= 10) return "";
|
|
4667
|
+
this.earlyErrors.push(p);
|
|
4668
|
+
return p.eventId;
|
|
4669
|
+
}
|
|
4670
|
+
return this.sendError(p) ? p.eventId : "";
|
|
4671
|
+
}
|
|
4672
|
+
sendError(p) {
|
|
4673
|
+
if (!this.errorTrackingAllowed()) return false;
|
|
4674
|
+
if (!p.fatal && !this.exceptionThrottle.allow(p.properties)) return false;
|
|
4675
|
+
const count = this.session.exceptions ?? 0;
|
|
4676
|
+
if (!p.fatal && count >= MAX_SESSION_EXCEPTIONS) return false;
|
|
4677
|
+
const event = this.enqueue("exception", { eventId: p.eventId, occurredAt: p.occurredAt, properties: p.properties, ...p.detail ? { detail: p.detail } : {} });
|
|
4678
|
+
if (!event) return false;
|
|
4679
|
+
this.session.exceptions = count + 1;
|
|
4680
|
+
this.persistSession();
|
|
4681
|
+
return true;
|
|
4682
|
+
}
|
|
4683
|
+
/** Sends what waited for storage and a config: earlier launches' crash reports, then early errors. */
|
|
4684
|
+
drainErrors() {
|
|
4685
|
+
if (!this.hydrated || !this.config || this.destroyed) return;
|
|
4686
|
+
const stored = this.storedCrashes.splice(0);
|
|
4687
|
+
if (stored.length) {
|
|
4688
|
+
if (this.errorTrackingAllowed()) {
|
|
4689
|
+
for (const c of stored) this.queue?.enqueue({ ...c, anonymousId: c.anonymousId || this.anonymousId, sessionId: c.sessionId || this.session.id });
|
|
4690
|
+
}
|
|
4691
|
+
void this.writeCrashes();
|
|
4692
|
+
}
|
|
4693
|
+
this.earlyErrors.splice(0).forEach((p) => this.sendError(p));
|
|
4694
|
+
}
|
|
4695
|
+
loadStoredCrashes() {
|
|
4696
|
+
const key = this.options.projectId;
|
|
4697
|
+
const valid = (c) => !!c && typeof c === "object" && typeof c.eventId === "string" && c.name === "exception";
|
|
4698
|
+
const read2 = (k) => {
|
|
4699
|
+
const v = readJson(this.store, k, []);
|
|
4700
|
+
return Array.isArray(v) ? v.filter(valid) : [];
|
|
4701
|
+
};
|
|
4702
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4703
|
+
this.storedCrashes = [...read2(crashKey(key)), ...read2(earlyCrashKey(key))].filter((c) => !seen.has(c.eventId) && !!seen.add(c.eventId)).slice(-MAX_STORED_CRASHES);
|
|
4704
|
+
this.store.remove(earlyCrashKey(key));
|
|
4705
|
+
if (this.storedCrashes.length) void this.writeCrashes();
|
|
4706
|
+
}
|
|
4707
|
+
/**
|
|
4708
|
+
* Writes the unsent crash reports to storage, directly (not behind other pending writes) so the
|
|
4709
|
+
* write starts before the app ends. Resolves when the storage has it.
|
|
4710
|
+
*/
|
|
4711
|
+
writeCrashes() {
|
|
4712
|
+
const storage = this.options.storage;
|
|
4713
|
+
const key = this.options.projectId;
|
|
4714
|
+
const direct = (op) => {
|
|
4715
|
+
try {
|
|
4716
|
+
return Promise.resolve(op()).catch((err) => this.log("crash report storage error", err));
|
|
4717
|
+
} catch (err) {
|
|
4718
|
+
this.log("crash report storage error", err);
|
|
4719
|
+
return Promise.resolve();
|
|
4720
|
+
}
|
|
4721
|
+
};
|
|
4722
|
+
if (!this.hydrated) {
|
|
4723
|
+
const json2 = JSON.stringify(this.launchCrashes.slice(-MAX_STORED_CRASHES));
|
|
4724
|
+
return storage ? direct(() => storage.setItem(earlyCrashKey(key), json2)) : Promise.resolve();
|
|
4725
|
+
}
|
|
4726
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4727
|
+
const all = [...this.storedCrashes, ...this.launchCrashes].filter((c) => !seen.has(c.eventId) && !!seen.add(c.eventId)).slice(-MAX_STORED_CRASHES);
|
|
4728
|
+
if (!all.length) {
|
|
4729
|
+
this.store.remove(crashKey(key));
|
|
4730
|
+
return storage ? direct(() => storage.removeItem(crashKey(key))) : Promise.resolve();
|
|
4731
|
+
}
|
|
4732
|
+
const json = JSON.stringify(all);
|
|
4733
|
+
this.store.set(crashKey(key), json);
|
|
4734
|
+
return storage ? direct(() => storage.setItem(crashKey(key), json)) : Promise.resolve();
|
|
4735
|
+
}
|
|
4736
|
+
/**
|
|
4737
|
+
* A fatal JS error: React Native ends a release app right after the global handler, so the report
|
|
4738
|
+
* is written to storage at once (and sent on the next launch), and sent now as well when the
|
|
4739
|
+
* network allows. The promise tells the handler when React Native's own handler may run.
|
|
4740
|
+
*/
|
|
4741
|
+
saveCrash(p) {
|
|
4742
|
+
if (!this.errorsWanted()) return void 0;
|
|
4743
|
+
const info = this.appInfo();
|
|
4744
|
+
const event = {
|
|
4745
|
+
eventId: p.eventId,
|
|
4746
|
+
name: "exception",
|
|
4747
|
+
occurredAt: p.occurredAt,
|
|
4748
|
+
anonymousId: this.anonymousId,
|
|
4749
|
+
userId: this.user?.id || null,
|
|
4750
|
+
accountId: this.user?.account?.id || null,
|
|
4751
|
+
...this.hydrated ? { sessionId: this.session.id } : {},
|
|
4752
|
+
...this.collectUrls() && this.screenName ? { url: this.currentUrl() } : {},
|
|
4753
|
+
...info.appVersion ? { appVersion: info.appVersion } : {},
|
|
4754
|
+
properties: sanitizeProperties(p.properties),
|
|
4755
|
+
...p.detail ? { detail: p.detail } : {}
|
|
4756
|
+
};
|
|
4757
|
+
this.launchCrashes.push(event);
|
|
4758
|
+
const written = this.writeCrashes();
|
|
4759
|
+
if (!this.hydrated || !this.config || !this.sendError(p)) return written;
|
|
4760
|
+
const sent = this.queue?.flush() ?? Promise.resolve();
|
|
4761
|
+
return Promise.all([written, sent]).then(async () => {
|
|
4762
|
+
if (this.queue?.peek().some((e) => e.eventId === event.eventId)) return;
|
|
4763
|
+
this.launchCrashes = this.launchCrashes.filter((c) => c.eventId !== event.eventId);
|
|
4764
|
+
await this.writeCrashes();
|
|
4765
|
+
});
|
|
4766
|
+
}
|
|
4767
|
+
/** An uncaught JS error from `ErrorUtils` (fatal ones end the app in release builds). */
|
|
4768
|
+
captureGlobalError(error, fatal) {
|
|
4769
|
+
try {
|
|
4770
|
+
const props = exceptionFromValue(error, "onerror", false);
|
|
4771
|
+
if (props.message.startsWith("[Guidely]")) return void 0;
|
|
4772
|
+
const p = this.prepareCapture(error, props, {}, {}, "onerror", fatal ? "fatal" : "error", fatal);
|
|
4773
|
+
if (!p) return void 0;
|
|
4774
|
+
if (fatal) return this.saveCrash(p);
|
|
4775
|
+
this.deliverError(p);
|
|
4776
|
+
} catch (err) {
|
|
4777
|
+
this.log("error capture failed", err);
|
|
4778
|
+
}
|
|
4779
|
+
return void 0;
|
|
4780
|
+
}
|
|
4781
|
+
captureRejection(reason) {
|
|
4782
|
+
try {
|
|
4783
|
+
const props = exceptionFromValue(reason, "unhandledrejection", false, "UnhandledRejection");
|
|
4784
|
+
if (props.message.startsWith("[Guidely]")) return;
|
|
4785
|
+
this.deliverError(this.prepareCapture(reason, props, {}, {}, "unhandledrejection", "error", false));
|
|
4786
|
+
} catch (err) {
|
|
4787
|
+
this.log("error capture failed", err);
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
/**
|
|
4791
|
+
* Guide breadcrumbs, and (with the bridge's JS globals) the global error handler, promise
|
|
4792
|
+
* rejection tracking and console / network breadcrumbs. Undone by `destroy()`.
|
|
4793
|
+
*/
|
|
4794
|
+
installErrorMonitoring() {
|
|
4795
|
+
const guideCrumb = (message, guide, stepIndex) => this.autoBreadcrumb("guides", { type: "info", category: "guide", message, data: { guideId: guide.id, ...stepIndex !== void 0 ? { stepIndex } : {} } });
|
|
4796
|
+
this.cleanups.push(
|
|
4797
|
+
this.emitter.on("guide:start", (g2) => guideCrumb("Guide shown", g2)),
|
|
4798
|
+
this.emitter.on("guide:step", (s) => guideCrumb("Guide step", s.guide, s.stepIndex)),
|
|
4799
|
+
this.emitter.on("guide:complete", (g2) => guideCrumb("Guide completed", g2)),
|
|
4800
|
+
this.emitter.on("guide:dismiss", (g2) => guideCrumb("Guide dismissed", g2)),
|
|
4801
|
+
this.emitter.on("guide:skip", (g2) => guideCrumb("Guide skipped", g2))
|
|
4802
|
+
);
|
|
4803
|
+
const g = this.bridge.globals;
|
|
4804
|
+
if (!g) return;
|
|
4805
|
+
try {
|
|
4806
|
+
if (this.crumbsOn("console")) this.cleanups.push(instrumentConsole(g.console, (c) => this.autoBreadcrumb("console", c)));
|
|
4807
|
+
if (this.crumbsOn("http")) this.cleanups.push(instrumentHttp(g, (c) => this.autoBreadcrumb("http", c), (url) => this.isGuidelyUrl(url)));
|
|
4808
|
+
if (this.options.errors?.captureErrors !== false) {
|
|
4809
|
+
this.cleanups.push(installGlobalHandler(g, (error, fatal) => this.captureGlobalError(error, fatal)));
|
|
4810
|
+
this.cleanups.push(installRejectionTracking(g, (reason) => this.captureRejection(reason), this.options.errors?.rejectionTracker));
|
|
4811
|
+
}
|
|
4812
|
+
} catch (err) {
|
|
4813
|
+
this.reportError(err);
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
// ---------------------------------------------------------------------------
|
|
3795
4817
|
// Targeting + triggers
|
|
3796
4818
|
// ---------------------------------------------------------------------------
|
|
3797
4819
|
/** Builds the targeting context for the current moment. */
|
|
@@ -3817,7 +4839,7 @@ var GuidelyClient = class {
|
|
|
3817
4839
|
completedChecklists: new Set(keysOf(Object.entries(this.state.checklists).filter(([, p]) => !!p.completedAt).map(([id]) => id))),
|
|
3818
4840
|
audienceIds: new Set(this.state.audienceIds),
|
|
3819
4841
|
now: /* @__PURE__ */ new Date(),
|
|
3820
|
-
environment: this.
|
|
4842
|
+
environment: this.environment,
|
|
3821
4843
|
locale: device.locale,
|
|
3822
4844
|
platform: device.platform,
|
|
3823
4845
|
screen: this.screenName,
|
|
@@ -4186,6 +5208,8 @@ var GuidelyClient = class {
|
|
|
4186
5208
|
*/
|
|
4187
5209
|
navigate(url) {
|
|
4188
5210
|
if (!url) return;
|
|
5211
|
+
const articleId = articleIdFromHref(url);
|
|
5212
|
+
if (articleId) return this.openArticle(articleId);
|
|
4189
5213
|
try {
|
|
4190
5214
|
if (this.options.onNavigate?.(url) === true) return;
|
|
4191
5215
|
if (/^[a-z][a-z0-9+.-]*:/i.test(url)) this.bridge.openExternal(url);
|
|
@@ -4209,7 +5233,7 @@ var GuidelyClient = class {
|
|
|
4209
5233
|
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) });
|
|
4210
5234
|
const checklists = [];
|
|
4211
5235
|
this.checklists.forEach((w, id) => checklists.push({ guide: w.guide, progress: this.getChecklistProgress(id), open: w.open }));
|
|
4212
|
-
this.ui = { steps, checklists, survey: this.surveyView, resourceCenterOpen: this.resourceCenterOpen, theme: this.themeFor(), messenger: !!this.messenger };
|
|
5236
|
+
this.ui = { steps, checklists, survey: this.surveyView, resourceCenterOpen: this.resourceCenterOpen, resourceCenterArticle: this.resourceCenterArticle, theme: this.themeFor(), messenger: !!this.messenger };
|
|
4213
5237
|
for (const cb of Array.from(this.uiListeners)) {
|
|
4214
5238
|
try {
|
|
4215
5239
|
cb();
|
|
@@ -4229,6 +5253,12 @@ var GuidelyClient = class {
|
|
|
4229
5253
|
closeStep(guideId) {
|
|
4230
5254
|
this.flowFor(guideId)?.controller.dismiss();
|
|
4231
5255
|
}
|
|
5256
|
+
/** The video on a step was tapped: records `guide_video_clicked`, then opens the video outside the app. */
|
|
5257
|
+
openVideo(guideId, url) {
|
|
5258
|
+
const v = this.flowFor(guideId)?.view;
|
|
5259
|
+
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 } });
|
|
5260
|
+
this.navigate(url);
|
|
5261
|
+
}
|
|
4232
5262
|
/** The dimmed backdrop behind a step was tapped. */
|
|
4233
5263
|
backdropPressed(guideId) {
|
|
4234
5264
|
const f = this.flowFor(guideId);
|
|
@@ -4501,10 +5531,40 @@ var GuidelyClient = class {
|
|
|
4501
5531
|
this.emitter.emit("resource-center:open");
|
|
4502
5532
|
this.notify();
|
|
4503
5533
|
}
|
|
5534
|
+
/**
|
|
5535
|
+
* Opens one help article: in Scout's Help tab when the messenger is on, otherwise in the resource
|
|
5536
|
+
* center. Help-article links in guides and articles (`#guidely-article:<id>`) call this.
|
|
5537
|
+
*/
|
|
5538
|
+
openArticle(articleId) {
|
|
5539
|
+
if (this.messenger) {
|
|
5540
|
+
this.whenHydrated(() => void this.messenger?.openArticle(articleId));
|
|
5541
|
+
return;
|
|
5542
|
+
}
|
|
5543
|
+
if (!this.getResourceCenter()) {
|
|
5544
|
+
this.log("openArticle: the resource center is not enabled or published for this environment");
|
|
5545
|
+
return;
|
|
5546
|
+
}
|
|
5547
|
+
this.resourceCenterArticle = { id: articleId, seq: (this.resourceCenterArticle?.seq ?? 0) + 1 };
|
|
5548
|
+
if (this.resourceCenterOpen) this.notify();
|
|
5549
|
+
else this.openResourceCenter();
|
|
5550
|
+
}
|
|
5551
|
+
/** A resource-center article with its body: from the config, or fetched when the config left it out. */
|
|
5552
|
+
async loadArticle(articleId) {
|
|
5553
|
+
const listed = this.getResourceCenter()?.articles.find((a) => a.id === articleId);
|
|
5554
|
+
if (listed && !listed.bodyOmitted) return listed;
|
|
5555
|
+
try {
|
|
5556
|
+
const locale = this.articleLocale();
|
|
5557
|
+
const res = await this.api.get(`/articles/${encodeURIComponent(articleId)}`, locale ? { params: { locale } } : {});
|
|
5558
|
+
return res.ok && res.data ? res.data.article : null;
|
|
5559
|
+
} catch {
|
|
5560
|
+
return null;
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
4504
5563
|
closeResourceCenter() {
|
|
4505
5564
|
if (this.messenger) return this.closeMessenger();
|
|
4506
5565
|
if (!this.resourceCenterOpen) return;
|
|
4507
5566
|
this.resourceCenterOpen = false;
|
|
5567
|
+
this.resourceCenterArticle = null;
|
|
4508
5568
|
this.notify();
|
|
4509
5569
|
}
|
|
4510
5570
|
// ---------------------------------------------------------------------------
|
|
@@ -4600,7 +5660,7 @@ var GuidelyClient = class {
|
|
|
4600
5660
|
locale: d.locale,
|
|
4601
5661
|
appVersion: this.options.appVersion ?? d.appVersion,
|
|
4602
5662
|
osVersion: d.osVersion,
|
|
4603
|
-
environment: this.
|
|
5663
|
+
environment: this.environment,
|
|
4604
5664
|
...this.screenName ? { screen: this.screenName, path: this.currentUrl() } : {},
|
|
4605
5665
|
...this.user?.traits ?? {},
|
|
4606
5666
|
id: this.user?.id || this.anonymousId || void 0,
|
|
@@ -4725,6 +5785,35 @@ function detectAppVersion(src) {
|
|
|
4725
5785
|
return {};
|
|
4726
5786
|
}
|
|
4727
5787
|
}
|
|
5788
|
+
function detectDeviceModel(src) {
|
|
5789
|
+
try {
|
|
5790
|
+
const expo = src.expoModules?.ExpoDevice ?? src.nativeModules.ExpoDevice;
|
|
5791
|
+
const info = src.nativeModules.RNDeviceInfo;
|
|
5792
|
+
const c = src.platform.constants ?? {};
|
|
5793
|
+
const android = src.platform.OS === "android";
|
|
5794
|
+
const model = text(constant(expo, "modelName")) ?? text(constant(info, "model")) ?? (android ? text(c.Model) : void 0);
|
|
5795
|
+
const manufacturer = text(constant(expo, "manufacturer")) ?? (android ? text(c.Manufacturer) : "Apple");
|
|
5796
|
+
const brand = text(constant(expo, "brand")) ?? text(constant(info, "brand")) ?? (android ? text(c.Brand) : void 0);
|
|
5797
|
+
const isDevice = constant(expo, "isDevice");
|
|
5798
|
+
return {
|
|
5799
|
+
...model ? { model } : {},
|
|
5800
|
+
...manufacturer ? { manufacturer } : {},
|
|
5801
|
+
...brand ? { brand } : {},
|
|
5802
|
+
...typeof isDevice === "boolean" ? { simulator: !isDevice } : {}
|
|
5803
|
+
};
|
|
5804
|
+
} catch {
|
|
5805
|
+
return {};
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
function detectReactNativeVersion(src) {
|
|
5809
|
+
try {
|
|
5810
|
+
const v = src.platform.constants?.reactNativeVersion;
|
|
5811
|
+
if (!v || typeof v.major !== "number") return void 0;
|
|
5812
|
+
return { major: v.major, minor: Number(v.minor) || 0, patch: Number(v.patch) || 0, ...v.prerelease != null ? { prerelease: String(v.prerelease) } : {} };
|
|
5813
|
+
} catch {
|
|
5814
|
+
return void 0;
|
|
5815
|
+
}
|
|
5816
|
+
}
|
|
4728
5817
|
function detectOsVersion(src) {
|
|
4729
5818
|
try {
|
|
4730
5819
|
const c = src.platform.constants ?? {};
|
|
@@ -4857,7 +5946,7 @@ function measureHost(host) {
|
|
|
4857
5946
|
done(null);
|
|
4858
5947
|
});
|
|
4859
5948
|
}
|
|
4860
|
-
function
|
|
5949
|
+
function describe2(f, masked, opts, withText) {
|
|
4861
5950
|
const props = f.memoizedProps && typeof f.memoizedProps === "object" ? f.memoizedProps : {};
|
|
4862
5951
|
const host = firstHost(f);
|
|
4863
5952
|
const el = { selector: idSelector(f) ?? pathSelector(f), tag: componentName(f.type) ?? "View" };
|
|
@@ -4902,8 +5991,8 @@ function resolveFiber(target, opts = {}) {
|
|
|
4902
5991
|
const element = press ?? role;
|
|
4903
5992
|
return {
|
|
4904
5993
|
ignored: false,
|
|
4905
|
-
element: element ?
|
|
4906
|
-
hit: hitFiber ?
|
|
5994
|
+
element: element ? describe2(element, masked, opts, true) : null,
|
|
5995
|
+
hit: hitFiber ? describe2(hitFiber, masked, opts, componentName(hitFiber.type) === "Text") : null
|
|
4907
5996
|
};
|
|
4908
5997
|
} catch {
|
|
4909
5998
|
return none;
|
|
@@ -5036,7 +6125,7 @@ function parseHtml(html) {
|
|
|
5036
6125
|
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;
|
|
5037
6126
|
else current.runs.push(run);
|
|
5038
6127
|
};
|
|
5039
|
-
const
|
|
6128
|
+
const size2 = (attrs, name) => {
|
|
5040
6129
|
const n = Number(attr(attrs, name));
|
|
5041
6130
|
return n > 0 ? n : void 0;
|
|
5042
6131
|
};
|
|
@@ -5098,8 +6187,8 @@ function parseHtml(html) {
|
|
|
5098
6187
|
}
|
|
5099
6188
|
} else if (tag === "img" && !closing) {
|
|
5100
6189
|
const src = attr(attrs, "src");
|
|
5101
|
-
const w =
|
|
5102
|
-
const h =
|
|
6190
|
+
const w = size2(attrs, "width");
|
|
6191
|
+
const h = size2(attrs, "height");
|
|
5103
6192
|
if (isSafeMediaUrl(src)) pdf.pages.push({ src, alt: attr(attrs, "alt") ?? "", ...w && h ? { width: w, height: h } : {} });
|
|
5104
6193
|
}
|
|
5105
6194
|
continue;
|
|
@@ -5273,10 +6362,10 @@ function BodyPdf({ block, palette: p, onLink }) {
|
|
|
5273
6362
|
] }) })
|
|
5274
6363
|
] });
|
|
5275
6364
|
}
|
|
5276
|
-
function RichText({ html, palette: p, onLink, size = 15 }) {
|
|
6365
|
+
function RichText({ html, palette: p, onLink, size: size2 = 15 }) {
|
|
5277
6366
|
const blocks = (0, import_react2.useMemo)(() => parseHtml(html), [html]);
|
|
5278
6367
|
if (!blocks.length) return null;
|
|
5279
|
-
const base = { color: p.text, fontSize:
|
|
6368
|
+
const base = { color: p.text, fontSize: size2, lineHeight: Math.round(size2 * 1.42), fontFamily: p.fontFamily };
|
|
5280
6369
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles2.wrap, children: blocks.map((b, i) => {
|
|
5281
6370
|
if (b.kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BodyImage, { block: b, palette: p }, i);
|
|
5282
6371
|
if (b.kind === "video") return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BodyVideo, { block: b, palette: p, onLink }, i);
|
|
@@ -5479,17 +6568,17 @@ function useReduceMotion() {
|
|
|
5479
6568
|
}, []);
|
|
5480
6569
|
return on;
|
|
5481
6570
|
}
|
|
5482
|
-
function MessengerLauncher({ store, client, palette: p, style, frame, defaultSide }) {
|
|
6571
|
+
function MessengerLauncher({ store, client, palette: p, style, frame: frame2, defaultSide }) {
|
|
5483
6572
|
const snap = useMessenger(store);
|
|
5484
6573
|
const reduceMotion = useReduceMotion();
|
|
5485
6574
|
const [placement, setPlacement] = (0, import_react3.useState)(() => client.getMessengerLauncherPlacement());
|
|
5486
6575
|
const pan = (0, import_react3.useRef)(new import_react_native5.Animated.ValueXY()).current;
|
|
5487
6576
|
const offset = (0, import_react3.useRef)({ x: 0, y: 0 });
|
|
5488
6577
|
const dragging = (0, import_react3.useRef)(false);
|
|
5489
|
-
const live = (0, import_react3.useRef)({ frame, defaultSide, reduceMotion });
|
|
5490
|
-
live.current = { frame, defaultSide, reduceMotion };
|
|
5491
|
-
const { width, height, size, defaultBottom } =
|
|
5492
|
-
const { top, bottom, left, right } =
|
|
6578
|
+
const live = (0, import_react3.useRef)({ frame: frame2, defaultSide, reduceMotion });
|
|
6579
|
+
live.current = { frame: frame2, defaultSide, reduceMotion };
|
|
6580
|
+
const { width, height, size: size2, defaultBottom } = frame2;
|
|
6581
|
+
const { top, bottom, left, right } = frame2.insets;
|
|
5493
6582
|
(0, import_react3.useLayoutEffect)(() => {
|
|
5494
6583
|
if (dragging.current) return;
|
|
5495
6584
|
const f = live.current.frame;
|
|
@@ -5497,7 +6586,7 @@ function MessengerLauncher({ store, client, palette: p, style, frame, defaultSid
|
|
|
5497
6586
|
const at = placement ? placementPoint(f, placement) : anchor;
|
|
5498
6587
|
offset.current = { x: at.x - anchor.x, y: at.y - anchor.y };
|
|
5499
6588
|
pan.setValue(offset.current);
|
|
5500
|
-
}, [pan, placement, defaultSide, width, height,
|
|
6589
|
+
}, [pan, placement, defaultSide, width, height, size2, defaultBottom, top, bottom, left, right]);
|
|
5501
6590
|
const responder = (0, import_react3.useMemo)(() => {
|
|
5502
6591
|
const moved = (g) => Math.hypot(g.dx, g.dy) > LAUNCHER_DRAG_THRESHOLD;
|
|
5503
6592
|
const anchor = () => placementPoint(live.current.frame, { side: live.current.defaultSide, fraction: 0 });
|
|
@@ -5850,7 +6939,7 @@ var styles4 = import_react_native5.StyleSheet.create({
|
|
|
5850
6939
|
var import_react4 = require("react");
|
|
5851
6940
|
var import_react_native6 = require("react-native");
|
|
5852
6941
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
5853
|
-
function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
6942
|
+
function ResourceCenter({ client, palette: p, open, articleRequest, topInset, bottomInset }) {
|
|
5854
6943
|
const rc = client.getResourceCenter();
|
|
5855
6944
|
const [query, setQuery] = (0, import_react4.useState)("");
|
|
5856
6945
|
const [results, setResults] = (0, import_react4.useState)(null);
|
|
@@ -5863,6 +6952,19 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
5863
6952
|
setArticle(null);
|
|
5864
6953
|
}
|
|
5865
6954
|
}, [open]);
|
|
6955
|
+
(0, import_react4.useEffect)(() => {
|
|
6956
|
+
if (!open || !articleRequest) return;
|
|
6957
|
+
let alive = true;
|
|
6958
|
+
void client.loadArticle(articleRequest.id).then((a) => {
|
|
6959
|
+
if (!alive || !a) return;
|
|
6960
|
+
if (a.externalUrl) return client.navigate(a.externalUrl);
|
|
6961
|
+
client.articleViewed(a);
|
|
6962
|
+
setArticle(a);
|
|
6963
|
+
});
|
|
6964
|
+
return () => {
|
|
6965
|
+
alive = false;
|
|
6966
|
+
};
|
|
6967
|
+
}, [client, open, articleRequest?.seq]);
|
|
5866
6968
|
(0, import_react4.useEffect)(() => {
|
|
5867
6969
|
const q = query.trim();
|
|
5868
6970
|
if (q.length < 2) {
|
|
@@ -6008,18 +7110,18 @@ var import_react_native8 = require("react-native");
|
|
|
6008
7110
|
var MARGIN = 12;
|
|
6009
7111
|
var ARROW_INSET = 18;
|
|
6010
7112
|
var opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
6011
|
-
function positionCard(target,
|
|
7113
|
+
function positionCard(target, size2, viewport, insets, placement, offset) {
|
|
6012
7114
|
const [rawSide, align] = placement.split("-");
|
|
6013
7115
|
const fits = (side2) => {
|
|
6014
7116
|
switch (side2) {
|
|
6015
7117
|
case "bottom":
|
|
6016
|
-
return target.y + target.height + offset +
|
|
7118
|
+
return target.y + target.height + offset + size2.height <= viewport.height - insets.bottom - MARGIN;
|
|
6017
7119
|
case "top":
|
|
6018
|
-
return target.y - offset -
|
|
7120
|
+
return target.y - offset - size2.height >= insets.top + MARGIN;
|
|
6019
7121
|
case "right":
|
|
6020
|
-
return target.x + target.width + offset +
|
|
7122
|
+
return target.x + target.width + offset + size2.width <= viewport.width - insets.right - MARGIN;
|
|
6021
7123
|
case "left":
|
|
6022
|
-
return target.x - offset -
|
|
7124
|
+
return target.x - offset - size2.width >= insets.left + MARGIN;
|
|
6023
7125
|
}
|
|
6024
7126
|
};
|
|
6025
7127
|
const order = rawSide === "auto" ? ["bottom", "top"] : [rawSide, opposite[rawSide], "bottom", "top"];
|
|
@@ -6028,23 +7130,23 @@ function positionCard(target, size, viewport, insets, placement, offset) {
|
|
|
6028
7130
|
const clamp3 = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
6029
7131
|
if (side === "top" || side === "bottom") {
|
|
6030
7132
|
const cx = target.x + target.width / 2;
|
|
6031
|
-
let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width -
|
|
6032
|
-
x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN -
|
|
6033
|
-
let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset -
|
|
6034
|
-
y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN -
|
|
6035
|
-
return { x: x2, y: y2, side, arrow: clamp3(cx - x2, ARROW_INSET,
|
|
7133
|
+
let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width - size2.width : cx - size2.width / 2;
|
|
7134
|
+
x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN - size2.width));
|
|
7135
|
+
let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset - size2.height;
|
|
7136
|
+
y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size2.height));
|
|
7137
|
+
return { x: x2, y: y2, side, arrow: clamp3(cx - x2, ARROW_INSET, size2.width - ARROW_INSET) };
|
|
6036
7138
|
}
|
|
6037
7139
|
const cy = target.y + target.height / 2;
|
|
6038
|
-
let y = align === "start" ? target.y : align === "end" ? target.y + target.height -
|
|
6039
|
-
y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN -
|
|
6040
|
-
const x = side === "right" ? target.x + target.width + offset : target.x - offset -
|
|
6041
|
-
return { x, y, side, arrow: clamp3(cy - y, ARROW_INSET,
|
|
7140
|
+
let y = align === "start" ? target.y : align === "end" ? target.y + target.height - size2.height : cy - size2.height / 2;
|
|
7141
|
+
y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size2.height));
|
|
7142
|
+
const x = side === "right" ? target.x + target.width + offset : target.x - offset - size2.width;
|
|
7143
|
+
return { x, y, side, arrow: clamp3(cy - y, ARROW_INSET, size2.height - ARROW_INSET) };
|
|
6042
7144
|
}
|
|
6043
7145
|
|
|
6044
7146
|
// src/ui/StepCard.tsx
|
|
6045
7147
|
var import_react_native7 = require("react-native");
|
|
6046
7148
|
var import_jsx_runtime7 = (
|
|
6047
|
-
//
|
|
7149
|
+
// The video opens outside the app (browser or the provider's app), like a new tab on the web.
|
|
6048
7150
|
require("react/jsx-runtime")
|
|
6049
7151
|
);
|
|
6050
7152
|
function StepCard({ view, client, palette: p, width, style, compact }) {
|
|
@@ -6053,16 +7155,33 @@ function StepCard({ view, client, palette: p, width, style, compact }) {
|
|
|
6053
7155
|
const body = step.type === "custom" ? step.customHtml || step.body : step.body;
|
|
6054
7156
|
const showProgress = step.showProgress && view.total > 1;
|
|
6055
7157
|
const onLink = (url) => client.navigate(url);
|
|
7158
|
+
const video = stepVideo(step);
|
|
6056
7159
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Surface, { palette: p, style: [{ width }, style], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { accessible: false, accessibilityViewIsModal: view.asModal || step.type === "modal", style: compact ? styles6.compact : styles6.inner, children: [
|
|
6057
|
-
|
|
6058
|
-
import_react_native7.
|
|
7160
|
+
video ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
7161
|
+
import_react_native7.Pressable,
|
|
6059
7162
|
{
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
7163
|
+
onPress: () => client.openVideo(guide.id, video.url),
|
|
7164
|
+
accessibilityRole: "button",
|
|
7165
|
+
accessibilityLabel: step.title ? `Play video: ${step.title}` : "Play video",
|
|
7166
|
+
accessibilityHint: "Opens the video outside the app",
|
|
7167
|
+
style: [styles6.video, { borderRadius: Math.max(0, p.radius - 4) }],
|
|
7168
|
+
children: [
|
|
7169
|
+
video.poster ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Image, { source: { uri: video.poster }, style: import_react_native7.StyleSheet.absoluteFill, resizeMode: "cover", accessibilityIgnoresInvertColors: true, accessible: false }) : null,
|
|
7170
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: styles6.play, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: styles6.playTriangle }) })
|
|
7171
|
+
]
|
|
6065
7172
|
}
|
|
7173
|
+
) : step.imageUrl ? (
|
|
7174
|
+
// An empty description marks the image as decorative: screen readers skip it.
|
|
7175
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
7176
|
+
import_react_native7.Image,
|
|
7177
|
+
{
|
|
7178
|
+
source: { uri: step.imageUrl },
|
|
7179
|
+
style: [styles6.image, { borderRadius: Math.max(0, p.radius - 4) }],
|
|
7180
|
+
resizeMode: "cover",
|
|
7181
|
+
accessibilityIgnoresInvertColors: true,
|
|
7182
|
+
...step.imageAlt?.trim() ? { accessible: true, accessibilityRole: "image", accessibilityLabel: step.imageAlt.trim() } : { accessible: false, importantForAccessibility: "no" }
|
|
7183
|
+
}
|
|
7184
|
+
)
|
|
6066
7185
|
) : null,
|
|
6067
7186
|
step.title ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { style: styles6.header, children: [
|
|
6068
7187
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Text, { accessibilityRole: "header", style: [styles6.title, { color: p.text, fontFamily: p.fontFamily }], children: step.title }),
|
|
@@ -6092,6 +7211,10 @@ var styles6 = import_react_native7.StyleSheet.create({
|
|
|
6092
7211
|
inner: { padding: 16, gap: 10 },
|
|
6093
7212
|
compact: { paddingHorizontal: 14, paddingVertical: 10, gap: 6 },
|
|
6094
7213
|
image: { width: "100%", height: 140, marginBottom: 2 },
|
|
7214
|
+
video: { width: "100%", aspectRatio: 16 / 9, maxHeight: 200, marginBottom: 2, overflow: "hidden", backgroundColor: "#0f172a", alignItems: "center", justifyContent: "center" },
|
|
7215
|
+
play: { width: 52, height: 52, borderRadius: 26, backgroundColor: "rgba(15,23,42,0.72)", alignItems: "center", justifyContent: "center", paddingLeft: 4 },
|
|
7216
|
+
// A CSS-border triangle: no icon font needed.
|
|
7217
|
+
playTriangle: { width: 0, height: 0, borderTopWidth: 10, borderBottomWidth: 10, borderLeftWidth: 16, borderTopColor: "transparent", borderBottomColor: "transparent", borderLeftColor: "#fff" },
|
|
6095
7218
|
header: { flexDirection: "row", alignItems: "flex-start", gap: 8 },
|
|
6096
7219
|
title: { flex: 1, fontSize: 17, fontWeight: "700", lineHeight: 22 },
|
|
6097
7220
|
flex: { flex: 1 },
|
|
@@ -6171,9 +7294,9 @@ function StepLayer({ view, client, palette: p, geometry }) {
|
|
|
6171
7294
|
}
|
|
6172
7295
|
if (!rect) return null;
|
|
6173
7296
|
if (step.type === "hotspot") {
|
|
6174
|
-
const
|
|
7297
|
+
const size2 = step.hotspot?.size ?? 14;
|
|
6175
7298
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { pointerEvents: "box-none", style: import_react_native8.StyleSheet.absoluteFill, children: [
|
|
6176
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Hotspot, { x: rect.x + rect.width -
|
|
7299
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(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" }),
|
|
6177
7300
|
view.expanded ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Anchored, { view, client, palette: p, geometry, rect, width: cardWidth }) : null
|
|
6178
7301
|
] });
|
|
6179
7302
|
}
|
|
@@ -6220,7 +7343,7 @@ function Arrow({ side, at, color, border }) {
|
|
|
6220
7343
|
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 };
|
|
6221
7344
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { pointerEvents: "none", style: [styles7.abs, styles7.arrow, place, { backgroundColor: color, borderColor: border }] });
|
|
6222
7345
|
}
|
|
6223
|
-
function Hotspot({ x, y, size, pulse, color, onPress, label }) {
|
|
7346
|
+
function Hotspot({ x, y, size: size2, pulse, color, onPress, label }) {
|
|
6224
7347
|
const scale = (0, import_react5.useRef)(new import_react_native8.Animated.Value(0)).current;
|
|
6225
7348
|
const [reduceMotion, setReduceMotion] = (0, import_react5.useState)(false);
|
|
6226
7349
|
(0, import_react5.useEffect)(() => {
|
|
@@ -6237,15 +7360,15 @@ function Hotspot({ x, y, size, pulse, color, onPress, label }) {
|
|
|
6237
7360
|
loop.start();
|
|
6238
7361
|
return () => loop.stop();
|
|
6239
7362
|
}, [pulse, reduceMotion, scale]);
|
|
6240
|
-
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width:
|
|
7363
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width: size2, height: size2 }], children: [
|
|
6241
7364
|
pulse && !reduceMotion ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
6242
7365
|
import_react_native8.Animated.View,
|
|
6243
7366
|
{
|
|
6244
7367
|
pointerEvents: "none",
|
|
6245
|
-
style: [styles7.abs, { width:
|
|
7368
|
+
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] }) }] }]
|
|
6246
7369
|
}
|
|
6247
7370
|
) : null,
|
|
6248
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { style: { width:
|
|
7371
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { style: { width: size2, height: size2, borderRadius: size2 / 2, backgroundColor: color, borderWidth: 2, borderColor: "#ffffff" } })
|
|
6249
7372
|
] });
|
|
6250
7373
|
}
|
|
6251
7374
|
var styles7 = import_react_native8.StyleSheet.create({
|
|
@@ -6683,7 +7806,7 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
6683
7806
|
) : null,
|
|
6684
7807
|
ui.survey ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Survey, { view: ui.survey, client, palette: palette(client.themeFor(ui.survey.guide), scheme), geometry }) : null,
|
|
6685
7808
|
ui.steps.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StepLayer, { view: s, client, palette: palette(client.themeFor(s.guide), scheme, s.guide.config.appearance), geometry }, s.key)),
|
|
6686
|
-
messenger ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessengerSheet, { store: messenger, client, palette: basePalette, topInset: insets.top, bottomInset: insets.bottom }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ResourceCenter, { client, palette: basePalette, open: ui.resourceCenterOpen, topInset: insets.top, bottomInset: insets.bottom })
|
|
7809
|
+
messenger ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessengerSheet, { store: messenger, client, palette: basePalette, topInset: insets.top, bottomInset: insets.bottom }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ResourceCenter, { client, palette: basePalette, open: ui.resourceCenterOpen, articleRequest: ui.resourceCenterArticle ?? null, topInset: insets.top, bottomInset: insets.bottom })
|
|
6687
7810
|
] });
|
|
6688
7811
|
}
|
|
6689
7812
|
var styles9 = import_react_native10.StyleSheet.create({
|
|
@@ -6856,12 +7979,16 @@ function defaultBridge(appVersion, appBuild) {
|
|
|
6856
7979
|
locale: deviceLocale(),
|
|
6857
7980
|
appVersion: appVersion ?? detected.appVersion,
|
|
6858
7981
|
appBuild: appBuild ?? detected.appBuild,
|
|
6859
|
-
osVersion: detectOsVersion(sources)
|
|
7982
|
+
osVersion: detectOsVersion(sources),
|
|
7983
|
+
...detectDeviceModel(sources),
|
|
7984
|
+
reactNativeVersion: detectReactNativeVersion(sources)
|
|
6860
7985
|
},
|
|
6861
7986
|
openExternal: (url) => void import_react_native13.Linking.openURL(url).catch(() => {
|
|
6862
7987
|
}),
|
|
6863
7988
|
windowSize: () => import_react_native13.Dimensions.get("window"),
|
|
6864
|
-
capturer: nativeCapturer()
|
|
7989
|
+
capturer: nativeCapturer(),
|
|
7990
|
+
// ErrorUtils, HermesInternal, console, fetch, XMLHttpRequest: crash capture and breadcrumbs.
|
|
7991
|
+
globals: globalThis
|
|
6865
7992
|
};
|
|
6866
7993
|
}
|
|
6867
7994
|
function nativeCapturer() {
|
|
@@ -6869,6 +7996,20 @@ function nativeCapturer() {
|
|
|
6869
7996
|
if (!mod || typeof mod.capture !== "function") return void 0;
|
|
6870
7997
|
return (options) => mod.capture(options);
|
|
6871
7998
|
}
|
|
7999
|
+
function latestErrorOptions(initial, latest) {
|
|
8000
|
+
if (!initial) return void 0;
|
|
8001
|
+
return {
|
|
8002
|
+
...initial,
|
|
8003
|
+
beforeSend: (event) => {
|
|
8004
|
+
const hook = latest()?.beforeSend;
|
|
8005
|
+
return hook ? hook(event) : event;
|
|
8006
|
+
},
|
|
8007
|
+
beforeBreadcrumb: (crumb, hint) => {
|
|
8008
|
+
const hook = latest()?.beforeBreadcrumb;
|
|
8009
|
+
return hook ? hook(crumb, hint) : crumb;
|
|
8010
|
+
}
|
|
8011
|
+
};
|
|
8012
|
+
}
|
|
6872
8013
|
function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
|
|
6873
8014
|
const [client, setClient] = (0, import_react10.useState)(null);
|
|
6874
8015
|
const optionsRef = (0, import_react10.useRef)(options);
|
|
@@ -6899,7 +8040,8 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
6899
8040
|
user: userRef.current?.id ? userRef.current : void 0,
|
|
6900
8041
|
flags: latest.flags === false ? false : { ...latest.flags ?? {}, payload: payloadRef.current ?? void 0, attributes: appAttributesRef.current },
|
|
6901
8042
|
onNavigate: (url) => optionsRef.current.onNavigate?.(url),
|
|
6902
|
-
onError: (err) => optionsRef.current.onError?.(err)
|
|
8043
|
+
onError: (err) => optionsRef.current.onError?.(err),
|
|
8044
|
+
errors: latestErrorOptions(latest.errors, () => optionsRef.current.errors)
|
|
6903
8045
|
},
|
|
6904
8046
|
defaultBridge(latest.appVersion, latest.appBuild)
|
|
6905
8047
|
);
|
|
@@ -6962,6 +8104,8 @@ var NOOP_RESULTS = {
|
|
|
6962
8104
|
isRecordingTouches: false,
|
|
6963
8105
|
getSessionId: "",
|
|
6964
8106
|
getScreen: "",
|
|
8107
|
+
captureException: "",
|
|
8108
|
+
captureMessage: "",
|
|
6965
8109
|
on: () => {
|
|
6966
8110
|
},
|
|
6967
8111
|
subscribeUi: () => () => {
|
|
@@ -7081,6 +8225,7 @@ function useActiveGuide() {
|
|
|
7081
8225
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7082
8226
|
0 && (module.exports = {
|
|
7083
8227
|
DEFAULT_API_URL,
|
|
8228
|
+
FATAL_FLUSH_MS,
|
|
7084
8229
|
Feature,
|
|
7085
8230
|
FeatureFlag,
|
|
7086
8231
|
FeatureValue,
|
|
@@ -7105,11 +8250,15 @@ function useActiveGuide() {
|
|
|
7105
8250
|
createFlagsClient,
|
|
7106
8251
|
createSseParser,
|
|
7107
8252
|
detectAppVersion,
|
|
8253
|
+
detectDeviceModel,
|
|
7108
8254
|
detectOsVersion,
|
|
8255
|
+
detectReactNativeVersion,
|
|
7109
8256
|
guidelyScreenLayout,
|
|
7110
8257
|
htmlToText,
|
|
7111
8258
|
noopClient,
|
|
8259
|
+
normalizeBundleFile,
|
|
7112
8260
|
parseHtml,
|
|
8261
|
+
parseReactNativeStack,
|
|
7113
8262
|
positionCard,
|
|
7114
8263
|
targetIdsFor,
|
|
7115
8264
|
useActiveGuide,
|