@guidelytech/react-native 0.16.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 +1137 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +448 -188
- package/dist/index.d.ts +448 -188
- package/dist/index.js +1132 -79
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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;
|
|
@@ -1101,7 +1174,22 @@ var KEYS = {
|
|
|
1101
1174
|
launcher: (key) => `guidely:launcher:${key}`
|
|
1102
1175
|
};
|
|
1103
1176
|
|
|
1104
|
-
// ../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
|
+
}
|
|
1105
1193
|
var EventQueue = class {
|
|
1106
1194
|
constructor(opts) {
|
|
1107
1195
|
__publicField2(this, "opts", opts);
|
|
@@ -1157,7 +1245,7 @@ var EventQueue = class {
|
|
|
1157
1245
|
this.schedule();
|
|
1158
1246
|
}
|
|
1159
1247
|
persist() {
|
|
1160
|
-
writeJson(this.opts.store, this.opts.storageKey, this.events);
|
|
1248
|
+
writeJson(this.opts.store, this.opts.storageKey, fitPersisted(this.events));
|
|
1161
1249
|
}
|
|
1162
1250
|
schedule() {
|
|
1163
1251
|
if (this.timer || this.retryTimer || this.destroyed) return;
|
|
@@ -1407,6 +1495,13 @@ function dismissedAfterMerge(local, server) {
|
|
|
1407
1495
|
if (server.dismissResetAt && server.dismissResetAt !== local?.dismissResetAt) return server.dismissedAt ?? null;
|
|
1408
1496
|
return local?.dismissedAt ?? server.dismissedAt ?? null;
|
|
1409
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
|
+
}
|
|
1410
1505
|
|
|
1411
1506
|
// src/core/client.ts
|
|
1412
1507
|
var import_flags2 = require("@guidelytech/flags");
|
|
@@ -1563,6 +1658,593 @@ var TapTracker = class {
|
|
|
1563
1658
|
}
|
|
1564
1659
|
};
|
|
1565
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
|
+
|
|
1566
2248
|
// src/core/targets.ts
|
|
1567
2249
|
var TargetTimeoutError = class extends Error {
|
|
1568
2250
|
constructor(ids) {
|
|
@@ -2893,38 +3575,38 @@ var SessionRecorder = class {
|
|
|
2893
3575
|
this.capturing = true;
|
|
2894
3576
|
this.captureSoon = false;
|
|
2895
3577
|
const gen = this.generation;
|
|
2896
|
-
let
|
|
3578
|
+
let frame2 = null;
|
|
2897
3579
|
try {
|
|
2898
|
-
|
|
3580
|
+
frame2 = await this.opts.capturer(await this.opts.captureOptions());
|
|
2899
3581
|
} catch (err) {
|
|
2900
3582
|
this.opts.onError?.(err);
|
|
2901
3583
|
}
|
|
2902
3584
|
if (gen !== this.generation) return;
|
|
2903
3585
|
this.capturing = false;
|
|
2904
|
-
if (
|
|
3586
|
+
if (frame2) this.onFrame(frame2, this.now());
|
|
2905
3587
|
if (this.sid !== null && this.foreground) this.schedule(this.captureSoon ? TOUCH_SETTLE_MS : this.currentIntervalMs);
|
|
2906
3588
|
}
|
|
2907
|
-
onFrame(
|
|
3589
|
+
onFrame(frame2, at) {
|
|
2908
3590
|
const id = this.sid;
|
|
2909
|
-
if (id === null || !(
|
|
2910
|
-
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;
|
|
2911
3593
|
const keyframe = this.needKeyframe || !this.hasKeyframe || sizeChanged;
|
|
2912
|
-
if (!keyframe && this.lastFrame ===
|
|
3594
|
+
if (!keyframe && this.lastFrame === frame2.base64) {
|
|
2913
3595
|
this.unchanged++;
|
|
2914
3596
|
return;
|
|
2915
3597
|
}
|
|
2916
3598
|
this.unchanged = 0;
|
|
2917
|
-
this.lastFrame =
|
|
2918
|
-
this.originX =
|
|
2919
|
-
this.originY =
|
|
2920
|
-
this.viewportWidth =
|
|
2921
|
-
this.viewportHeight =
|
|
2922
|
-
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);
|
|
2923
3605
|
const up = this.opts.uploader;
|
|
2924
3606
|
if (!keyframe) return up.push(id, RrwebEvents.frame(at, url));
|
|
2925
3607
|
this.needKeyframe = false;
|
|
2926
|
-
up.push(id, RrwebEvents.meta(at, this.opts.href(),
|
|
2927
|
-
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));
|
|
2928
3610
|
if (!this.hasKeyframe) {
|
|
2929
3611
|
this.hasKeyframe = true;
|
|
2930
3612
|
for (const [ts, payload] of this.pendingCustom) up.push(id, RrwebEvents.custom(Math.max(ts, at), "guidely", payload));
|
|
@@ -2986,7 +3668,7 @@ var PersistentStore = class {
|
|
|
2986
3668
|
|
|
2987
3669
|
// src/core/client.ts
|
|
2988
3670
|
var SDK_NAME = "@guidelytech/react-native";
|
|
2989
|
-
var SDK_VERSION = "0.
|
|
3671
|
+
var SDK_VERSION = "0.17.0";
|
|
2990
3672
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
2991
3673
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2992
3674
|
var CACHED_START_MS = 1500;
|
|
@@ -3001,6 +3683,10 @@ function settlesWithin(promise, ms) {
|
|
|
3001
3683
|
});
|
|
3002
3684
|
}
|
|
3003
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;
|
|
3004
3690
|
var UNKNOWN_VIEW = { selector: "View", tag: "View" };
|
|
3005
3691
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
3006
3692
|
var emptyState = () => ({ guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] });
|
|
@@ -3017,6 +3703,8 @@ var GuidelyClient = class {
|
|
|
3017
3703
|
/** Survey answers, guide completions/dismissals and checklist progress, kept until the server has them. */
|
|
3018
3704
|
__publicField(this, "outbox", null);
|
|
3019
3705
|
__publicField(this, "config", null);
|
|
3706
|
+
/** The `environment` option as passed (the resolved one is in `options.environment`). */
|
|
3707
|
+
__publicField(this, "envOption");
|
|
3020
3708
|
__publicField(this, "configFetchedAt", 0);
|
|
3021
3709
|
__publicField(this, "etag", null);
|
|
3022
3710
|
__publicField(this, "anonymousId", "");
|
|
@@ -3080,6 +3768,16 @@ var GuidelyClient = class {
|
|
|
3080
3768
|
__publicField(this, "localizedRc", null);
|
|
3081
3769
|
/** "What's new" last-seen time when the resource center opened (entries after it read as new). */
|
|
3082
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);
|
|
3083
3781
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
3084
3782
|
__publicField(this, "eventBus", {
|
|
3085
3783
|
on: (name, cb) => {
|
|
@@ -3093,9 +3791,10 @@ var GuidelyClient = class {
|
|
|
3093
3791
|
}
|
|
3094
3792
|
});
|
|
3095
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;
|
|
3096
3795
|
this.options = {
|
|
3097
3796
|
...options,
|
|
3098
|
-
environment: options.environment
|
|
3797
|
+
environment: options.environment || environmentFromKey(options.projectId),
|
|
3099
3798
|
apiUrl: options.apiUrl ?? DEFAULT_API_URL,
|
|
3100
3799
|
analytics: options.analytics ?? true,
|
|
3101
3800
|
configRefreshMs: options.configRefreshMs ?? 5 * 60 * 1e3,
|
|
@@ -3111,6 +3810,7 @@ var GuidelyClient = class {
|
|
|
3111
3810
|
if (options.user) this.user = { ...options.user };
|
|
3112
3811
|
this.locale = options.locale ? normalizeLocale(options.locale) || null : null;
|
|
3113
3812
|
this.flags = this.createFlags(options.flags);
|
|
3813
|
+
this.installErrorMonitoring();
|
|
3114
3814
|
}
|
|
3115
3815
|
createFlags(opts) {
|
|
3116
3816
|
const base = { sdkKey: this.options.projectId, apiUrl: this.options.apiUrl, fetch: this.options.fetch, sdk: { name: SDK_NAME, version: SDK_VERSION } };
|
|
@@ -3158,7 +3858,7 @@ var GuidelyClient = class {
|
|
|
3158
3858
|
async bootstrap() {
|
|
3159
3859
|
const key = this.options.projectId;
|
|
3160
3860
|
try {
|
|
3161
|
-
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)]);
|
|
3162
3862
|
if (this.destroyed) return;
|
|
3163
3863
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
3164
3864
|
this.hadIdentity = !!this.anonymousId;
|
|
@@ -3171,7 +3871,9 @@ var GuidelyClient = class {
|
|
|
3171
3871
|
this.state = { ...emptyState(), ...readJson(this.store, KEYS.state(key), {}) };
|
|
3172
3872
|
this.user = this.options.user ? { ...this.options.user } : readJson(this.store, KEYS.user(key), null);
|
|
3173
3873
|
const saved = readJson(this.store, KEYS.session, null);
|
|
3174
|
-
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();
|
|
3175
3877
|
this.persistSession();
|
|
3176
3878
|
this.lastAudiences = new Set(this.state.audienceIds);
|
|
3177
3879
|
this.syncFlagAttributes();
|
|
@@ -3190,9 +3892,10 @@ var GuidelyClient = class {
|
|
|
3190
3892
|
this.hydrated = true;
|
|
3191
3893
|
const cached = readJson(this.store, KEYS.config(key), null);
|
|
3192
3894
|
this.etag = this.store.get(KEYS.etag(key));
|
|
3193
|
-
if (cached && Array.isArray(cached.guides)) this.
|
|
3895
|
+
if (cached && Array.isArray(cached.guides)) this.useConfig(forPlatform(cached, this.bridge.device.platform));
|
|
3194
3896
|
this.syncMessenger();
|
|
3195
3897
|
this.pending.splice(0).forEach((fn) => fn());
|
|
3898
|
+
this.drainErrors();
|
|
3196
3899
|
const before = this.config?.configVersion;
|
|
3197
3900
|
const fresh = this.fetchConfig();
|
|
3198
3901
|
if (!this.config) await fresh;
|
|
@@ -3217,22 +3920,33 @@ var GuidelyClient = class {
|
|
|
3217
3920
|
if (this.hydrated) fn();
|
|
3218
3921
|
else this.pending.push(fn);
|
|
3219
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
|
+
}
|
|
3220
3932
|
async fetchConfig() {
|
|
3221
3933
|
try {
|
|
3222
3934
|
const res = await this.api.get("/config", { etag: this.etag, params: { platform: this.bridge.device.platform } });
|
|
3223
3935
|
this.configFetchedAt = Date.now();
|
|
3224
3936
|
if (res.status === 304) return true;
|
|
3225
3937
|
if (res.ok && res.data && Array.isArray(res.data.guides)) {
|
|
3226
|
-
|
|
3938
|
+
const config = forPlatform(res.data, this.bridge.device.platform);
|
|
3939
|
+
this.useConfig(config);
|
|
3227
3940
|
this.etag = res.etag;
|
|
3228
3941
|
writeJson(this.store, KEYS.config(this.options.projectId), res.data);
|
|
3229
3942
|
if (res.etag) this.store.set(KEYS.etag(this.options.projectId), res.etag);
|
|
3230
3943
|
else this.store.remove(KEYS.etag(this.options.projectId));
|
|
3231
3944
|
this.syncMessenger();
|
|
3232
3945
|
if (this.hydrated) this.checkLaunch();
|
|
3233
|
-
this.
|
|
3946
|
+
this.drainErrors();
|
|
3947
|
+
this.emitter.emit("config", config);
|
|
3234
3948
|
this.syncReplay();
|
|
3235
|
-
this.log("config loaded",
|
|
3949
|
+
this.log("config loaded", config.configVersion, `${config.guides.length} guides`);
|
|
3236
3950
|
return true;
|
|
3237
3951
|
}
|
|
3238
3952
|
this.reportError(new Error(`[Guidely] config request failed (${res.status})${this.config ? "; using cached config" : ""}`));
|
|
@@ -3273,7 +3987,12 @@ var GuidelyClient = class {
|
|
|
3273
3987
|
}
|
|
3274
3988
|
/** Called by the provider with React Native `AppState` changes. */
|
|
3275
3989
|
setAppState(state) {
|
|
3276
|
-
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;
|
|
3277
3996
|
if (state === "background") {
|
|
3278
3997
|
const wasForeground = this.backgroundedAt === null;
|
|
3279
3998
|
this.backgroundedAt = Date.now();
|
|
@@ -3458,8 +4177,13 @@ var GuidelyClient = class {
|
|
|
3458
4177
|
setConsent(consent) {
|
|
3459
4178
|
this.consent = consent;
|
|
3460
4179
|
this.whenHydrated(() => {
|
|
3461
|
-
if (consent === "denied")
|
|
3462
|
-
|
|
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();
|
|
3463
4187
|
this.syncReplay();
|
|
3464
4188
|
});
|
|
3465
4189
|
}
|
|
@@ -3483,6 +4207,7 @@ var GuidelyClient = class {
|
|
|
3483
4207
|
accountId: this.user?.account?.id || null,
|
|
3484
4208
|
sessionId: this.session.id,
|
|
3485
4209
|
url: this.collectUrls() && this.screenName ? this.currentUrl() : void 0,
|
|
4210
|
+
...this.appInfo().appVersion ? { appVersion: this.appInfo().appVersion } : {},
|
|
3486
4211
|
...extra
|
|
3487
4212
|
};
|
|
3488
4213
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
@@ -3522,6 +4247,7 @@ var GuidelyClient = class {
|
|
|
3522
4247
|
this.whenHydrated(() => {
|
|
3523
4248
|
const changed = name !== this.screenName;
|
|
3524
4249
|
if (changed) {
|
|
4250
|
+
this.autoBreadcrumb("navigation", { type: "navigation", category: "navigation", data: { ...this.screenName ? { from: this.screenName } : {}, to: name } });
|
|
3525
4251
|
this.leaveScreen();
|
|
3526
4252
|
this.flushHeatmap();
|
|
3527
4253
|
}
|
|
@@ -3685,7 +4411,11 @@ var GuidelyClient = class {
|
|
|
3685
4411
|
}
|
|
3686
4412
|
/** True when taps are worth observing (clicks, rage taps or heatmaps are on); the provider skips resolving views otherwise. */
|
|
3687
4413
|
isTapCaptureOn() {
|
|
3688
|
-
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();
|
|
3689
4419
|
}
|
|
3690
4420
|
/**
|
|
3691
4421
|
* Records a tap observed by the provider: `click` for an interactive element, `rage_click` for
|
|
@@ -3695,8 +4425,8 @@ var GuidelyClient = class {
|
|
|
3695
4425
|
recordTap(tap) {
|
|
3696
4426
|
try {
|
|
3697
4427
|
if (!this.hydrated || this.destroyed || !Number.isFinite(tap.x) || !Number.isFinite(tap.y)) return;
|
|
3698
|
-
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps") };
|
|
3699
|
-
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;
|
|
3700
4430
|
const t = tap.t ?? Date.now();
|
|
3701
4431
|
const url = this.screenName ? this.currentUrl() : "";
|
|
3702
4432
|
this.tapChain = this.tapChain.then(() => this.processTap(tap, t, url, on)).catch((err) => this.reportError(err));
|
|
@@ -3713,6 +4443,8 @@ var GuidelyClient = class {
|
|
|
3713
4443
|
rect = target.rect;
|
|
3714
4444
|
}
|
|
3715
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;
|
|
3716
4448
|
const platform = this.bridge.device.platform;
|
|
3717
4449
|
const maskAll = !!this.config?.settings?.replay?.maskAllText || !!this.options.sessionReplay?.maskAllText;
|
|
3718
4450
|
const eventUrl = this.collectUrls() && url ? url : void 0;
|
|
@@ -3811,6 +4543,277 @@ var GuidelyClient = class {
|
|
|
3811
4543
|
}
|
|
3812
4544
|
}
|
|
3813
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
|
+
// ---------------------------------------------------------------------------
|
|
3814
4817
|
// Targeting + triggers
|
|
3815
4818
|
// ---------------------------------------------------------------------------
|
|
3816
4819
|
/** Builds the targeting context for the current moment. */
|
|
@@ -3836,7 +4839,7 @@ var GuidelyClient = class {
|
|
|
3836
4839
|
completedChecklists: new Set(keysOf(Object.entries(this.state.checklists).filter(([, p]) => !!p.completedAt).map(([id]) => id))),
|
|
3837
4840
|
audienceIds: new Set(this.state.audienceIds),
|
|
3838
4841
|
now: /* @__PURE__ */ new Date(),
|
|
3839
|
-
environment: this.
|
|
4842
|
+
environment: this.environment,
|
|
3840
4843
|
locale: device.locale,
|
|
3841
4844
|
platform: device.platform,
|
|
3842
4845
|
screen: this.screenName,
|
|
@@ -4657,7 +5660,7 @@ var GuidelyClient = class {
|
|
|
4657
5660
|
locale: d.locale,
|
|
4658
5661
|
appVersion: this.options.appVersion ?? d.appVersion,
|
|
4659
5662
|
osVersion: d.osVersion,
|
|
4660
|
-
environment: this.
|
|
5663
|
+
environment: this.environment,
|
|
4661
5664
|
...this.screenName ? { screen: this.screenName, path: this.currentUrl() } : {},
|
|
4662
5665
|
...this.user?.traits ?? {},
|
|
4663
5666
|
id: this.user?.id || this.anonymousId || void 0,
|
|
@@ -4782,6 +5785,35 @@ function detectAppVersion(src) {
|
|
|
4782
5785
|
return {};
|
|
4783
5786
|
}
|
|
4784
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
|
+
}
|
|
4785
5817
|
function detectOsVersion(src) {
|
|
4786
5818
|
try {
|
|
4787
5819
|
const c = src.platform.constants ?? {};
|
|
@@ -4914,7 +5946,7 @@ function measureHost(host) {
|
|
|
4914
5946
|
done(null);
|
|
4915
5947
|
});
|
|
4916
5948
|
}
|
|
4917
|
-
function
|
|
5949
|
+
function describe2(f, masked, opts, withText) {
|
|
4918
5950
|
const props = f.memoizedProps && typeof f.memoizedProps === "object" ? f.memoizedProps : {};
|
|
4919
5951
|
const host = firstHost(f);
|
|
4920
5952
|
const el = { selector: idSelector(f) ?? pathSelector(f), tag: componentName(f.type) ?? "View" };
|
|
@@ -4959,8 +5991,8 @@ function resolveFiber(target, opts = {}) {
|
|
|
4959
5991
|
const element = press ?? role;
|
|
4960
5992
|
return {
|
|
4961
5993
|
ignored: false,
|
|
4962
|
-
element: element ?
|
|
4963
|
-
hit: hitFiber ?
|
|
5994
|
+
element: element ? describe2(element, masked, opts, true) : null,
|
|
5995
|
+
hit: hitFiber ? describe2(hitFiber, masked, opts, componentName(hitFiber.type) === "Text") : null
|
|
4964
5996
|
};
|
|
4965
5997
|
} catch {
|
|
4966
5998
|
return none;
|
|
@@ -5093,7 +6125,7 @@ function parseHtml(html) {
|
|
|
5093
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;
|
|
5094
6126
|
else current.runs.push(run);
|
|
5095
6127
|
};
|
|
5096
|
-
const
|
|
6128
|
+
const size2 = (attrs, name) => {
|
|
5097
6129
|
const n = Number(attr(attrs, name));
|
|
5098
6130
|
return n > 0 ? n : void 0;
|
|
5099
6131
|
};
|
|
@@ -5155,8 +6187,8 @@ function parseHtml(html) {
|
|
|
5155
6187
|
}
|
|
5156
6188
|
} else if (tag === "img" && !closing) {
|
|
5157
6189
|
const src = attr(attrs, "src");
|
|
5158
|
-
const w =
|
|
5159
|
-
const h =
|
|
6190
|
+
const w = size2(attrs, "width");
|
|
6191
|
+
const h = size2(attrs, "height");
|
|
5160
6192
|
if (isSafeMediaUrl(src)) pdf.pages.push({ src, alt: attr(attrs, "alt") ?? "", ...w && h ? { width: w, height: h } : {} });
|
|
5161
6193
|
}
|
|
5162
6194
|
continue;
|
|
@@ -5330,10 +6362,10 @@ function BodyPdf({ block, palette: p, onLink }) {
|
|
|
5330
6362
|
] }) })
|
|
5331
6363
|
] });
|
|
5332
6364
|
}
|
|
5333
|
-
function RichText({ html, palette: p, onLink, size = 15 }) {
|
|
6365
|
+
function RichText({ html, palette: p, onLink, size: size2 = 15 }) {
|
|
5334
6366
|
const blocks = (0, import_react2.useMemo)(() => parseHtml(html), [html]);
|
|
5335
6367
|
if (!blocks.length) return null;
|
|
5336
|
-
const base = { color: p.text, fontSize:
|
|
6368
|
+
const base = { color: p.text, fontSize: size2, lineHeight: Math.round(size2 * 1.42), fontFamily: p.fontFamily };
|
|
5337
6369
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles2.wrap, children: blocks.map((b, i) => {
|
|
5338
6370
|
if (b.kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BodyImage, { block: b, palette: p }, i);
|
|
5339
6371
|
if (b.kind === "video") return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BodyVideo, { block: b, palette: p, onLink }, i);
|
|
@@ -5536,17 +6568,17 @@ function useReduceMotion() {
|
|
|
5536
6568
|
}, []);
|
|
5537
6569
|
return on;
|
|
5538
6570
|
}
|
|
5539
|
-
function MessengerLauncher({ store, client, palette: p, style, frame, defaultSide }) {
|
|
6571
|
+
function MessengerLauncher({ store, client, palette: p, style, frame: frame2, defaultSide }) {
|
|
5540
6572
|
const snap = useMessenger(store);
|
|
5541
6573
|
const reduceMotion = useReduceMotion();
|
|
5542
6574
|
const [placement, setPlacement] = (0, import_react3.useState)(() => client.getMessengerLauncherPlacement());
|
|
5543
6575
|
const pan = (0, import_react3.useRef)(new import_react_native5.Animated.ValueXY()).current;
|
|
5544
6576
|
const offset = (0, import_react3.useRef)({ x: 0, y: 0 });
|
|
5545
6577
|
const dragging = (0, import_react3.useRef)(false);
|
|
5546
|
-
const live = (0, import_react3.useRef)({ frame, defaultSide, reduceMotion });
|
|
5547
|
-
live.current = { frame, defaultSide, reduceMotion };
|
|
5548
|
-
const { width, height, size, defaultBottom } =
|
|
5549
|
-
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;
|
|
5550
6582
|
(0, import_react3.useLayoutEffect)(() => {
|
|
5551
6583
|
if (dragging.current) return;
|
|
5552
6584
|
const f = live.current.frame;
|
|
@@ -5554,7 +6586,7 @@ function MessengerLauncher({ store, client, palette: p, style, frame, defaultSid
|
|
|
5554
6586
|
const at = placement ? placementPoint(f, placement) : anchor;
|
|
5555
6587
|
offset.current = { x: at.x - anchor.x, y: at.y - anchor.y };
|
|
5556
6588
|
pan.setValue(offset.current);
|
|
5557
|
-
}, [pan, placement, defaultSide, width, height,
|
|
6589
|
+
}, [pan, placement, defaultSide, width, height, size2, defaultBottom, top, bottom, left, right]);
|
|
5558
6590
|
const responder = (0, import_react3.useMemo)(() => {
|
|
5559
6591
|
const moved = (g) => Math.hypot(g.dx, g.dy) > LAUNCHER_DRAG_THRESHOLD;
|
|
5560
6592
|
const anchor = () => placementPoint(live.current.frame, { side: live.current.defaultSide, fraction: 0 });
|
|
@@ -6078,18 +7110,18 @@ var import_react_native8 = require("react-native");
|
|
|
6078
7110
|
var MARGIN = 12;
|
|
6079
7111
|
var ARROW_INSET = 18;
|
|
6080
7112
|
var opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
6081
|
-
function positionCard(target,
|
|
7113
|
+
function positionCard(target, size2, viewport, insets, placement, offset) {
|
|
6082
7114
|
const [rawSide, align] = placement.split("-");
|
|
6083
7115
|
const fits = (side2) => {
|
|
6084
7116
|
switch (side2) {
|
|
6085
7117
|
case "bottom":
|
|
6086
|
-
return target.y + target.height + offset +
|
|
7118
|
+
return target.y + target.height + offset + size2.height <= viewport.height - insets.bottom - MARGIN;
|
|
6087
7119
|
case "top":
|
|
6088
|
-
return target.y - offset -
|
|
7120
|
+
return target.y - offset - size2.height >= insets.top + MARGIN;
|
|
6089
7121
|
case "right":
|
|
6090
|
-
return target.x + target.width + offset +
|
|
7122
|
+
return target.x + target.width + offset + size2.width <= viewport.width - insets.right - MARGIN;
|
|
6091
7123
|
case "left":
|
|
6092
|
-
return target.x - offset -
|
|
7124
|
+
return target.x - offset - size2.width >= insets.left + MARGIN;
|
|
6093
7125
|
}
|
|
6094
7126
|
};
|
|
6095
7127
|
const order = rawSide === "auto" ? ["bottom", "top"] : [rawSide, opposite[rawSide], "bottom", "top"];
|
|
@@ -6098,17 +7130,17 @@ function positionCard(target, size, viewport, insets, placement, offset) {
|
|
|
6098
7130
|
const clamp3 = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
6099
7131
|
if (side === "top" || side === "bottom") {
|
|
6100
7132
|
const cx = target.x + target.width / 2;
|
|
6101
|
-
let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width -
|
|
6102
|
-
x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN -
|
|
6103
|
-
let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset -
|
|
6104
|
-
y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN -
|
|
6105
|
-
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) };
|
|
6106
7138
|
}
|
|
6107
7139
|
const cy = target.y + target.height / 2;
|
|
6108
|
-
let y = align === "start" ? target.y : align === "end" ? target.y + target.height -
|
|
6109
|
-
y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN -
|
|
6110
|
-
const x = side === "right" ? target.x + target.width + offset : target.x - offset -
|
|
6111
|
-
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) };
|
|
6112
7144
|
}
|
|
6113
7145
|
|
|
6114
7146
|
// src/ui/StepCard.tsx
|
|
@@ -6262,9 +7294,9 @@ function StepLayer({ view, client, palette: p, geometry }) {
|
|
|
6262
7294
|
}
|
|
6263
7295
|
if (!rect) return null;
|
|
6264
7296
|
if (step.type === "hotspot") {
|
|
6265
|
-
const
|
|
7297
|
+
const size2 = step.hotspot?.size ?? 14;
|
|
6266
7298
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { pointerEvents: "box-none", style: import_react_native8.StyleSheet.absoluteFill, children: [
|
|
6267
|
-
/* @__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" }),
|
|
6268
7300
|
view.expanded ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Anchored, { view, client, palette: p, geometry, rect, width: cardWidth }) : null
|
|
6269
7301
|
] });
|
|
6270
7302
|
}
|
|
@@ -6311,7 +7343,7 @@ function Arrow({ side, at, color, border }) {
|
|
|
6311
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 };
|
|
6312
7344
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { pointerEvents: "none", style: [styles7.abs, styles7.arrow, place, { backgroundColor: color, borderColor: border }] });
|
|
6313
7345
|
}
|
|
6314
|
-
function Hotspot({ x, y, size, pulse, color, onPress, label }) {
|
|
7346
|
+
function Hotspot({ x, y, size: size2, pulse, color, onPress, label }) {
|
|
6315
7347
|
const scale = (0, import_react5.useRef)(new import_react_native8.Animated.Value(0)).current;
|
|
6316
7348
|
const [reduceMotion, setReduceMotion] = (0, import_react5.useState)(false);
|
|
6317
7349
|
(0, import_react5.useEffect)(() => {
|
|
@@ -6328,15 +7360,15 @@ function Hotspot({ x, y, size, pulse, color, onPress, label }) {
|
|
|
6328
7360
|
loop.start();
|
|
6329
7361
|
return () => loop.stop();
|
|
6330
7362
|
}, [pulse, reduceMotion, scale]);
|
|
6331
|
-
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: [
|
|
6332
7364
|
pulse && !reduceMotion ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
6333
7365
|
import_react_native8.Animated.View,
|
|
6334
7366
|
{
|
|
6335
7367
|
pointerEvents: "none",
|
|
6336
|
-
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] }) }] }]
|
|
6337
7369
|
}
|
|
6338
7370
|
) : null,
|
|
6339
|
-
/* @__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" } })
|
|
6340
7372
|
] });
|
|
6341
7373
|
}
|
|
6342
7374
|
var styles7 = import_react_native8.StyleSheet.create({
|
|
@@ -6947,12 +7979,16 @@ function defaultBridge(appVersion, appBuild) {
|
|
|
6947
7979
|
locale: deviceLocale(),
|
|
6948
7980
|
appVersion: appVersion ?? detected.appVersion,
|
|
6949
7981
|
appBuild: appBuild ?? detected.appBuild,
|
|
6950
|
-
osVersion: detectOsVersion(sources)
|
|
7982
|
+
osVersion: detectOsVersion(sources),
|
|
7983
|
+
...detectDeviceModel(sources),
|
|
7984
|
+
reactNativeVersion: detectReactNativeVersion(sources)
|
|
6951
7985
|
},
|
|
6952
7986
|
openExternal: (url) => void import_react_native13.Linking.openURL(url).catch(() => {
|
|
6953
7987
|
}),
|
|
6954
7988
|
windowSize: () => import_react_native13.Dimensions.get("window"),
|
|
6955
|
-
capturer: nativeCapturer()
|
|
7989
|
+
capturer: nativeCapturer(),
|
|
7990
|
+
// ErrorUtils, HermesInternal, console, fetch, XMLHttpRequest: crash capture and breadcrumbs.
|
|
7991
|
+
globals: globalThis
|
|
6956
7992
|
};
|
|
6957
7993
|
}
|
|
6958
7994
|
function nativeCapturer() {
|
|
@@ -6960,6 +7996,20 @@ function nativeCapturer() {
|
|
|
6960
7996
|
if (!mod || typeof mod.capture !== "function") return void 0;
|
|
6961
7997
|
return (options) => mod.capture(options);
|
|
6962
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
|
+
}
|
|
6963
8013
|
function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
|
|
6964
8014
|
const [client, setClient] = (0, import_react10.useState)(null);
|
|
6965
8015
|
const optionsRef = (0, import_react10.useRef)(options);
|
|
@@ -6990,7 +8040,8 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
6990
8040
|
user: userRef.current?.id ? userRef.current : void 0,
|
|
6991
8041
|
flags: latest.flags === false ? false : { ...latest.flags ?? {}, payload: payloadRef.current ?? void 0, attributes: appAttributesRef.current },
|
|
6992
8042
|
onNavigate: (url) => optionsRef.current.onNavigate?.(url),
|
|
6993
|
-
onError: (err) => optionsRef.current.onError?.(err)
|
|
8043
|
+
onError: (err) => optionsRef.current.onError?.(err),
|
|
8044
|
+
errors: latestErrorOptions(latest.errors, () => optionsRef.current.errors)
|
|
6994
8045
|
},
|
|
6995
8046
|
defaultBridge(latest.appVersion, latest.appBuild)
|
|
6996
8047
|
);
|
|
@@ -7053,6 +8104,8 @@ var NOOP_RESULTS = {
|
|
|
7053
8104
|
isRecordingTouches: false,
|
|
7054
8105
|
getSessionId: "",
|
|
7055
8106
|
getScreen: "",
|
|
8107
|
+
captureException: "",
|
|
8108
|
+
captureMessage: "",
|
|
7056
8109
|
on: () => {
|
|
7057
8110
|
},
|
|
7058
8111
|
subscribeUi: () => () => {
|
|
@@ -7172,6 +8225,7 @@ function useActiveGuide() {
|
|
|
7172
8225
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7173
8226
|
0 && (module.exports = {
|
|
7174
8227
|
DEFAULT_API_URL,
|
|
8228
|
+
FATAL_FLUSH_MS,
|
|
7175
8229
|
Feature,
|
|
7176
8230
|
FeatureFlag,
|
|
7177
8231
|
FeatureValue,
|
|
@@ -7196,11 +8250,15 @@ function useActiveGuide() {
|
|
|
7196
8250
|
createFlagsClient,
|
|
7197
8251
|
createSseParser,
|
|
7198
8252
|
detectAppVersion,
|
|
8253
|
+
detectDeviceModel,
|
|
7199
8254
|
detectOsVersion,
|
|
8255
|
+
detectReactNativeVersion,
|
|
7200
8256
|
guidelyScreenLayout,
|
|
7201
8257
|
htmlToText,
|
|
7202
8258
|
noopClient,
|
|
8259
|
+
normalizeBundleFile,
|
|
7203
8260
|
parseHtml,
|
|
8261
|
+
parseReactNativeStack,
|
|
7204
8262
|
positionCard,
|
|
7205
8263
|
targetIdsFor,
|
|
7206
8264
|
useActiveGuide,
|