@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.js
CHANGED
|
@@ -96,7 +96,7 @@ function FlagsProvider({ children, client: external, payload, ...options }) {
|
|
|
96
96
|
return /* @__PURE__ */ jsx(FlagsContext.Provider, { value, children });
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// ../shared/dist/chunk-
|
|
99
|
+
// ../shared/dist/chunk-YL65O3FS.js
|
|
100
100
|
function fnv1a(input) {
|
|
101
101
|
let h = 2166136261;
|
|
102
102
|
for (let i = 0; i < input.length; i++) {
|
|
@@ -109,18 +109,18 @@ function rolloutBucket(subject, salt) {
|
|
|
109
109
|
return fnv1a(`${salt}:${subject}`) % 100;
|
|
110
110
|
}
|
|
111
111
|
var ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
112
|
-
function randomId(
|
|
112
|
+
function randomId(size2 = 16) {
|
|
113
113
|
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
114
114
|
let bytes;
|
|
115
115
|
if (g.crypto && typeof g.crypto.getRandomValues === "function") {
|
|
116
|
-
bytes = new Uint8Array(
|
|
116
|
+
bytes = new Uint8Array(size2);
|
|
117
117
|
g.crypto.getRandomValues(bytes);
|
|
118
118
|
} else {
|
|
119
|
-
bytes = new Uint8Array(
|
|
120
|
-
for (let i = 0; i <
|
|
119
|
+
bytes = new Uint8Array(size2);
|
|
120
|
+
for (let i = 0; i < size2; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
121
121
|
}
|
|
122
122
|
let out = "";
|
|
123
|
-
for (let i = 0; i <
|
|
123
|
+
for (let i = 0; i < size2; i++) out += ALPHABET[bytes[i] % ALPHABET.length];
|
|
124
124
|
return out;
|
|
125
125
|
}
|
|
126
126
|
function getPath(obj, path) {
|
|
@@ -390,24 +390,92 @@ function evaluateRules(group, ctx) {
|
|
|
390
390
|
const value = group.op === "and" ? results.every(Boolean) : results.some(Boolean);
|
|
391
391
|
return group.negate ? !value : value;
|
|
392
392
|
}
|
|
393
|
+
var MAX_EXCEPTION_DETAIL_BYTES = 64 * 1024;
|
|
394
|
+
var MAX_EXCEPTION_DETAIL_RAW_BYTES = 1024 * 1024;
|
|
395
|
+
var MAX_BREADCRUMBS = 100;
|
|
396
|
+
var MAX_EXCEPTION_CHAIN = 20;
|
|
397
|
+
var MAX_DETAIL_FRAMES = 250;
|
|
398
|
+
var MAX_DETAIL_THREADS = 100;
|
|
399
|
+
var MAX_DEBUG_IMAGES = 500;
|
|
400
|
+
var MAX_ERROR_TAGS = 50;
|
|
401
|
+
var size = (v) => {
|
|
402
|
+
try {
|
|
403
|
+
return JSON.stringify(v)?.length ?? 0;
|
|
404
|
+
} catch {
|
|
405
|
+
return Infinity;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
function normalizeTags(tags) {
|
|
409
|
+
const out = {};
|
|
410
|
+
for (const [k, v] of Object.entries(tags ?? {})) {
|
|
411
|
+
if (Object.keys(out).length >= MAX_ERROR_TAGS) break;
|
|
412
|
+
if (!k || v === void 0 || v === null) continue;
|
|
413
|
+
out[k.slice(0, 32)] = String(v).slice(0, 200);
|
|
414
|
+
}
|
|
415
|
+
return out;
|
|
416
|
+
}
|
|
417
|
+
function trimExceptionDetail(detail, max = MAX_EXCEPTION_DETAIL_BYTES) {
|
|
418
|
+
const d = detail;
|
|
419
|
+
if (d.breadcrumbs?.values && d.breadcrumbs.values.length > MAX_BREADCRUMBS) d.breadcrumbs.values = d.breadcrumbs.values.slice(-MAX_BREADCRUMBS);
|
|
420
|
+
if (d.exception?.values && d.exception.values.length > MAX_EXCEPTION_CHAIN) d.exception.values = d.exception.values.slice(-MAX_EXCEPTION_CHAIN);
|
|
421
|
+
if (d.threads?.values && d.threads.values.length > MAX_DETAIL_THREADS) d.threads.values = d.threads.values.slice(0, MAX_DETAIL_THREADS);
|
|
422
|
+
if (d.debug_meta?.images && d.debug_meta.images.length > MAX_DEBUG_IMAGES) d.debug_meta.images = d.debug_meta.images.slice(0, MAX_DEBUG_IMAGES);
|
|
423
|
+
const stacks = () => [...d.exception?.values ?? [], ...d.threads?.values ?? []].map((x) => x.stacktrace).filter((s) => !!s);
|
|
424
|
+
for (const s of stacks()) if (s.frames && s.frames.length > MAX_DETAIL_FRAMES) s.frames = s.frames.slice(-MAX_DETAIL_FRAMES);
|
|
425
|
+
if (size(d) <= max) return d;
|
|
426
|
+
while (d.breadcrumbs?.values.length && size(d) > max) d.breadcrumbs.values = d.breadcrumbs.values.slice(Math.ceil(d.breadcrumbs.values.length / 2));
|
|
427
|
+
if (size(d) <= max) return d;
|
|
428
|
+
for (const s of stacks())
|
|
429
|
+
for (const f of s.frames ?? []) {
|
|
430
|
+
delete f.vars;
|
|
431
|
+
delete f.pre_context;
|
|
432
|
+
delete f.post_context;
|
|
433
|
+
}
|
|
434
|
+
if (size(d) <= max) return d;
|
|
435
|
+
if (d.threads?.values) d.threads.values = d.threads.values.filter((t) => t.crashed || t.current);
|
|
436
|
+
if (size(d) <= max) return d;
|
|
437
|
+
if (d.extra) d.extra = { _trimmed: true };
|
|
438
|
+
if (d.request) delete d.request.data;
|
|
439
|
+
for (const s of stacks()) while (s.frames && s.frames.length > 20 && size(d) > max) s.frames.splice(Math.floor(s.frames.length / 2) - 5, 10);
|
|
440
|
+
if (d.debug_meta?.images && size(d) > max) d.debug_meta.images = d.debug_meta.images.slice(0, 50);
|
|
441
|
+
return d;
|
|
442
|
+
}
|
|
443
|
+
var matches = (patterns, value) => patterns.some((p) => typeof p === "string" ? p !== "" && value.includes(p) : safeTest(p, value));
|
|
444
|
+
function safeTest(re, value) {
|
|
445
|
+
re.lastIndex = 0;
|
|
446
|
+
return re.test(value);
|
|
447
|
+
}
|
|
448
|
+
function errorFilterDrops(e, opts) {
|
|
449
|
+
const message = e.message ?? "";
|
|
450
|
+
if (opts.ignoreErrors?.length && (matches(opts.ignoreErrors, `${e.type || "Error"}: ${message}`) || matches(opts.ignoreErrors, message))) return true;
|
|
451
|
+
if (!e.file) return false;
|
|
452
|
+
if (opts.denyUrls?.length && matches(opts.denyUrls, e.file)) return true;
|
|
453
|
+
if (opts.allowUrls?.length && !matches(opts.allowUrls, e.file)) return true;
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
393
456
|
var SENSITIVE_PROPERTY_PATTERN = /(password|passwd|secret|token|credit|card|cvv|ssn|iban)/i;
|
|
394
457
|
var MAX_PROPERTY_BYTES = 8 * 1024;
|
|
395
458
|
function sanitizeProperties(props) {
|
|
396
459
|
if (!props) return {};
|
|
397
460
|
const out = {};
|
|
398
|
-
let
|
|
461
|
+
let size2 = 0;
|
|
399
462
|
for (const [k, v] of Object.entries(props)) {
|
|
400
463
|
if (SENSITIVE_PROPERTY_PATTERN.test(k)) continue;
|
|
401
464
|
if (typeof v === "function" || typeof v === "symbol") continue;
|
|
402
465
|
const str2 = typeof v === "string" ? v : JSON.stringify(v ?? null);
|
|
403
|
-
|
|
404
|
-
if (
|
|
466
|
+
size2 += k.length + (str2?.length ?? 0);
|
|
467
|
+
if (size2 > MAX_PROPERTY_BYTES) break;
|
|
405
468
|
out[k.slice(0, 100)] = typeof v === "string" ? v.slice(0, 1e3) : v;
|
|
406
469
|
}
|
|
407
470
|
return out;
|
|
408
471
|
}
|
|
472
|
+
function reactNativeBundleUrl(file) {
|
|
473
|
+
const base = file.replace(/[?#].*$/, "").split("/").pop() || file;
|
|
474
|
+
return `app:///${base}`;
|
|
475
|
+
}
|
|
409
476
|
var SOURCEMAP_MAX_FILE_BYTES = 100 * 1024 * 1024;
|
|
410
477
|
var MAX_COMPLAINT_ATTACHMENT_BYTES = 2 * 1024 * 1024;
|
|
478
|
+
var MAX_BUG_REPORT_SCREENSHOT_BYTES = 4 * 1024 * 1024;
|
|
411
479
|
var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
412
480
|
var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
|
|
413
481
|
var MEDIA_FILE_MAX_BYTES = 50 * 1024 * 1024;
|
|
@@ -1032,7 +1100,22 @@ var KEYS = {
|
|
|
1032
1100
|
launcher: (key) => `guidely:launcher:${key}`
|
|
1033
1101
|
};
|
|
1034
1102
|
|
|
1035
|
-
// ../sdk-core/dist/chunk-
|
|
1103
|
+
// ../sdk-core/dist/chunk-GFLVZRTS.js
|
|
1104
|
+
var MAX_PERSISTED_QUEUE_CHARS = 1024 * 1024;
|
|
1105
|
+
function fitPersisted(events) {
|
|
1106
|
+
let size2 = JSON.stringify(events).length;
|
|
1107
|
+
if (size2 <= MAX_PERSISTED_QUEUE_CHARS) return events;
|
|
1108
|
+
const out = events.slice();
|
|
1109
|
+
for (let i = 0; i < out.length && size2 > MAX_PERSISTED_QUEUE_CHARS; i++) {
|
|
1110
|
+
const e = out[i];
|
|
1111
|
+
if (!e.detail) continue;
|
|
1112
|
+
size2 -= JSON.stringify(e.detail).length;
|
|
1113
|
+
const rest = { ...e };
|
|
1114
|
+
delete rest.detail;
|
|
1115
|
+
out[i] = rest;
|
|
1116
|
+
}
|
|
1117
|
+
return out;
|
|
1118
|
+
}
|
|
1036
1119
|
var EventQueue = class {
|
|
1037
1120
|
constructor(opts) {
|
|
1038
1121
|
__publicField2(this, "opts", opts);
|
|
@@ -1088,7 +1171,7 @@ var EventQueue = class {
|
|
|
1088
1171
|
this.schedule();
|
|
1089
1172
|
}
|
|
1090
1173
|
persist() {
|
|
1091
|
-
writeJson(this.opts.store, this.opts.storageKey, this.events);
|
|
1174
|
+
writeJson(this.opts.store, this.opts.storageKey, fitPersisted(this.events));
|
|
1092
1175
|
}
|
|
1093
1176
|
schedule() {
|
|
1094
1177
|
if (this.timer || this.retryTimer || this.destroyed) return;
|
|
@@ -1338,6 +1421,13 @@ function dismissedAfterMerge(local, server) {
|
|
|
1338
1421
|
if (server.dismissResetAt && server.dismissResetAt !== local?.dismissResetAt) return server.dismissedAt ?? null;
|
|
1339
1422
|
return local?.dismissedAt ?? server.dismissedAt ?? null;
|
|
1340
1423
|
}
|
|
1424
|
+
function environmentFromKey(key) {
|
|
1425
|
+
const prefix = /^pk_([a-z0-9]+)_/.exec(key)?.[1];
|
|
1426
|
+
if (!prefix || prefix === "dev") return "development";
|
|
1427
|
+
if (prefix === "prod") return "production";
|
|
1428
|
+
if (prefix === "stg" || prefix === "stag") return "staging";
|
|
1429
|
+
return prefix;
|
|
1430
|
+
}
|
|
1341
1431
|
|
|
1342
1432
|
// src/core/client.ts
|
|
1343
1433
|
import { createFlagsClient as createFlagsClient2 } from "@guidelytech/flags";
|
|
@@ -1494,6 +1584,593 @@ var TapTracker = class {
|
|
|
1494
1584
|
}
|
|
1495
1585
|
};
|
|
1496
1586
|
|
|
1587
|
+
// src/core/breadcrumbs.ts
|
|
1588
|
+
var BreadcrumbBuffer = class {
|
|
1589
|
+
constructor(max = MAX_BREADCRUMBS) {
|
|
1590
|
+
__publicField(this, "max", max);
|
|
1591
|
+
__publicField(this, "items", []);
|
|
1592
|
+
}
|
|
1593
|
+
/** Adds a breadcrumb; `max` (at most the buffer's own) lowers how many are kept. */
|
|
1594
|
+
add(crumb, max = this.max) {
|
|
1595
|
+
const limit = Math.min(max, this.max);
|
|
1596
|
+
this.items.push(crumb);
|
|
1597
|
+
if (this.items.length > limit) this.items.splice(0, this.items.length - limit);
|
|
1598
|
+
}
|
|
1599
|
+
/** Copies, oldest first (an event keeps the crumbs it was captured with). */
|
|
1600
|
+
values() {
|
|
1601
|
+
return this.items.map((c) => ({ ...c, ...c.data ? { data: { ...c.data } } : {} }));
|
|
1602
|
+
}
|
|
1603
|
+
get size() {
|
|
1604
|
+
return this.items.length;
|
|
1605
|
+
}
|
|
1606
|
+
clear() {
|
|
1607
|
+
this.items = [];
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
function stripUrl(url) {
|
|
1611
|
+
return String(url).replace(/[?#].*$/, "").replace(/^([a-z][\w+.-]*:\/\/)[^/@]*@/i, "$1").slice(0, 500);
|
|
1612
|
+
}
|
|
1613
|
+
function describeArg(value) {
|
|
1614
|
+
if (typeof value === "string") return value.slice(0, 500);
|
|
1615
|
+
if (value instanceof Error) return `${value.name}: ${value.message}`.slice(0, 500);
|
|
1616
|
+
try {
|
|
1617
|
+
return (JSON.stringify(value) ?? String(value)).slice(0, 500);
|
|
1618
|
+
} catch {
|
|
1619
|
+
return String(value).slice(0, 500);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
function instrumentConsole(target, emit) {
|
|
1623
|
+
if (!target) return () => {
|
|
1624
|
+
};
|
|
1625
|
+
const undo = [];
|
|
1626
|
+
let inside = false;
|
|
1627
|
+
for (const [method, level] of [
|
|
1628
|
+
["warn", "warning"],
|
|
1629
|
+
["error", "error"]
|
|
1630
|
+
]) {
|
|
1631
|
+
const original = target[method];
|
|
1632
|
+
if (typeof original !== "function") continue;
|
|
1633
|
+
const wrapped = function(...args) {
|
|
1634
|
+
const result = original.apply(this, args);
|
|
1635
|
+
if (!inside && !(typeof args[0] === "string" && args[0].startsWith("[Guidely]"))) {
|
|
1636
|
+
inside = true;
|
|
1637
|
+
try {
|
|
1638
|
+
const parts = args.slice(0, 5).map(describeArg);
|
|
1639
|
+
emit({ type: "debug", category: "console", level, message: parts.join(" ").slice(0, 1e3), data: { arguments: parts, logger: "console" } });
|
|
1640
|
+
} catch {
|
|
1641
|
+
} finally {
|
|
1642
|
+
inside = false;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
return result;
|
|
1646
|
+
};
|
|
1647
|
+
target[method] = wrapped;
|
|
1648
|
+
undo.push(() => {
|
|
1649
|
+
if (target[method] === wrapped) target[method] = original;
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
return () => undo.forEach((fn) => fn());
|
|
1653
|
+
}
|
|
1654
|
+
function httpLevel(status) {
|
|
1655
|
+
if (!status) return "error";
|
|
1656
|
+
return status >= 500 ? "error" : status >= 400 ? "warning" : "info";
|
|
1657
|
+
}
|
|
1658
|
+
var XHR_INFO = "__guidelyXhr";
|
|
1659
|
+
function instrumentHttp(g, emit, skip, now = Date.now) {
|
|
1660
|
+
if (!g) return () => {
|
|
1661
|
+
};
|
|
1662
|
+
const undo = [];
|
|
1663
|
+
let inFetch = 0;
|
|
1664
|
+
const record2 = (category, method, url, start, status) => {
|
|
1665
|
+
try {
|
|
1666
|
+
const data = { method, url: stripUrl(url), ...status ? { status_code: status } : {}, duration_ms: Math.max(0, Math.round(now() - start)) };
|
|
1667
|
+
emit({ type: "http", category, level: httpLevel(status), data });
|
|
1668
|
+
} catch {
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
const originalFetch = g.fetch;
|
|
1672
|
+
if (typeof originalFetch === "function") {
|
|
1673
|
+
const wrappedFetch = function(...args) {
|
|
1674
|
+
const [input, init] = args;
|
|
1675
|
+
let url = "";
|
|
1676
|
+
let method = "GET";
|
|
1677
|
+
try {
|
|
1678
|
+
const req = input;
|
|
1679
|
+
url = typeof req === "string" ? req : typeof req?.url === "string" ? req.url : String(input);
|
|
1680
|
+
method = String(init?.method ?? (req && typeof req === "object" ? req.method : void 0) ?? "GET").toUpperCase();
|
|
1681
|
+
} catch {
|
|
1682
|
+
}
|
|
1683
|
+
if (skip(url)) return originalFetch.apply(this, args);
|
|
1684
|
+
const start = now();
|
|
1685
|
+
let result;
|
|
1686
|
+
inFetch += 1;
|
|
1687
|
+
try {
|
|
1688
|
+
result = originalFetch.apply(this, args);
|
|
1689
|
+
} finally {
|
|
1690
|
+
inFetch -= 1;
|
|
1691
|
+
}
|
|
1692
|
+
if (!result || typeof result.then !== "function") return result;
|
|
1693
|
+
return result.then(
|
|
1694
|
+
(res) => {
|
|
1695
|
+
record2("fetch", method, url, start, typeof res?.status === "number" ? res.status : void 0);
|
|
1696
|
+
return res;
|
|
1697
|
+
},
|
|
1698
|
+
(err) => {
|
|
1699
|
+
record2("fetch", method, url, start, void 0);
|
|
1700
|
+
throw err;
|
|
1701
|
+
}
|
|
1702
|
+
);
|
|
1703
|
+
};
|
|
1704
|
+
g.fetch = wrappedFetch;
|
|
1705
|
+
undo.push(() => {
|
|
1706
|
+
if (g.fetch === wrappedFetch) g.fetch = originalFetch;
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
const proto = g.XMLHttpRequest?.prototype;
|
|
1710
|
+
const originalOpen = proto?.open;
|
|
1711
|
+
const originalSend = proto?.send;
|
|
1712
|
+
if (proto && typeof originalOpen === "function" && typeof originalSend === "function") {
|
|
1713
|
+
const open = function(...args) {
|
|
1714
|
+
try {
|
|
1715
|
+
this[XHR_INFO] = { method: String(args[0] ?? "GET").toUpperCase(), url: String(args[1] ?? ""), viaFetch: inFetch > 0 };
|
|
1716
|
+
} catch {
|
|
1717
|
+
}
|
|
1718
|
+
return originalOpen.apply(this, args);
|
|
1719
|
+
};
|
|
1720
|
+
const send = function(...args) {
|
|
1721
|
+
try {
|
|
1722
|
+
const info = this[XHR_INFO];
|
|
1723
|
+
if (info && !info.viaFetch && !skip(info.url) && typeof this.addEventListener === "function") {
|
|
1724
|
+
info.start = now();
|
|
1725
|
+
this.addEventListener("readystatechange", () => {
|
|
1726
|
+
if (this.readyState !== 4 || info.done) return;
|
|
1727
|
+
info.done = true;
|
|
1728
|
+
record2("xhr", info.method, info.url, info.start ?? now(), typeof this.status === "number" && this.status > 0 ? this.status : void 0);
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
} catch {
|
|
1732
|
+
}
|
|
1733
|
+
return originalSend.apply(this, args);
|
|
1734
|
+
};
|
|
1735
|
+
proto.open = open;
|
|
1736
|
+
proto.send = send;
|
|
1737
|
+
undo.push(() => {
|
|
1738
|
+
if (proto.open === open) proto.open = originalOpen;
|
|
1739
|
+
if (proto.send === send) proto.send = originalSend;
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
return () => undo.forEach((fn) => fn());
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/core/crashHandlers.ts
|
|
1746
|
+
var FATAL_FLUSH_MS = 2e3;
|
|
1747
|
+
var isThenable = (v) => !!v && typeof v.then === "function";
|
|
1748
|
+
function installGlobalHandler(g, report, waitMs = FATAL_FLUSH_MS) {
|
|
1749
|
+
const eu = g.ErrorUtils;
|
|
1750
|
+
if (!eu || typeof eu.setGlobalHandler !== "function") return () => {
|
|
1751
|
+
};
|
|
1752
|
+
let previous;
|
|
1753
|
+
try {
|
|
1754
|
+
previous = eu.getGlobalHandler?.();
|
|
1755
|
+
} catch {
|
|
1756
|
+
previous = void 0;
|
|
1757
|
+
}
|
|
1758
|
+
let active = true;
|
|
1759
|
+
let handlingFatal = false;
|
|
1760
|
+
const handler = (error, isFatal) => {
|
|
1761
|
+
const fatal = !!isFatal;
|
|
1762
|
+
const callPrevious = () => previous?.(error, isFatal);
|
|
1763
|
+
if (!active || fatal && handlingFatal) return callPrevious();
|
|
1764
|
+
let pending;
|
|
1765
|
+
try {
|
|
1766
|
+
pending = report(error, fatal);
|
|
1767
|
+
} catch {
|
|
1768
|
+
}
|
|
1769
|
+
if (!fatal || g.__DEV__ || !isThenable(pending)) return callPrevious();
|
|
1770
|
+
handlingFatal = true;
|
|
1771
|
+
let done = false;
|
|
1772
|
+
const finish = () => {
|
|
1773
|
+
if (done) return;
|
|
1774
|
+
done = true;
|
|
1775
|
+
clearTimeout(timer);
|
|
1776
|
+
handlingFatal = false;
|
|
1777
|
+
try {
|
|
1778
|
+
callPrevious();
|
|
1779
|
+
} catch {
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
const timer = setTimeout(finish, waitMs);
|
|
1783
|
+
pending.then(finish, finish);
|
|
1784
|
+
};
|
|
1785
|
+
try {
|
|
1786
|
+
eu.setGlobalHandler(handler);
|
|
1787
|
+
} catch {
|
|
1788
|
+
return () => {
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
return () => {
|
|
1792
|
+
active = false;
|
|
1793
|
+
try {
|
|
1794
|
+
if (previous && eu.getGlobalHandler?.() === handler) eu.setGlobalHandler(previous);
|
|
1795
|
+
} catch {
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
function describeRejection(rejection) {
|
|
1800
|
+
if (rejection instanceof Error) return rejection.stack || `${rejection.name}: ${rejection.message}`;
|
|
1801
|
+
try {
|
|
1802
|
+
return typeof rejection === "string" ? rejection : JSON.stringify(rejection) ?? String(rejection);
|
|
1803
|
+
} catch {
|
|
1804
|
+
return String(rejection);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
function hermesTracker(g) {
|
|
1808
|
+
const h = g.HermesInternal;
|
|
1809
|
+
try {
|
|
1810
|
+
if (!h || typeof h.enablePromiseRejectionTracker !== "function" || typeof h.hasPromise === "function" && !h.hasPromise()) return null;
|
|
1811
|
+
} catch {
|
|
1812
|
+
return null;
|
|
1813
|
+
}
|
|
1814
|
+
return { enable: (options) => h.enablePromiseRejectionTracker(options) };
|
|
1815
|
+
}
|
|
1816
|
+
function installRejectionTracking(g, report, tracker) {
|
|
1817
|
+
let active = true;
|
|
1818
|
+
const warn = (message) => {
|
|
1819
|
+
try {
|
|
1820
|
+
g.console?.warn?.(message);
|
|
1821
|
+
} catch {
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
const options = {
|
|
1825
|
+
allRejections: true,
|
|
1826
|
+
onUnhandled: (id, rejection) => {
|
|
1827
|
+
if (active) {
|
|
1828
|
+
try {
|
|
1829
|
+
report(rejection);
|
|
1830
|
+
} catch {
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
if (g.__DEV__) warn(`Possible unhandled promise rejection (id: ${id}):
|
|
1834
|
+
${describeRejection(rejection)}`);
|
|
1835
|
+
},
|
|
1836
|
+
onHandled: (id) => {
|
|
1837
|
+
if (g.__DEV__) warn(`Promise rejection handled (id: ${id})
|
|
1838
|
+
This means you can ignore any previous messages of the form "Possible unhandled promise rejection (id: ${id}):"`);
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
const t = tracker ?? hermesTracker(g);
|
|
1842
|
+
if (t && typeof t.enable === "function") {
|
|
1843
|
+
try {
|
|
1844
|
+
t.enable(options);
|
|
1845
|
+
return () => {
|
|
1846
|
+
active = false;
|
|
1847
|
+
};
|
|
1848
|
+
} catch {
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
if (typeof g.addEventListener === "function") {
|
|
1852
|
+
const listener = (event) => {
|
|
1853
|
+
if (active) report(event?.reason);
|
|
1854
|
+
};
|
|
1855
|
+
try {
|
|
1856
|
+
g.addEventListener("unhandledrejection", listener);
|
|
1857
|
+
return () => {
|
|
1858
|
+
active = false;
|
|
1859
|
+
g.removeEventListener?.("unhandledrejection", listener);
|
|
1860
|
+
};
|
|
1861
|
+
} catch {
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return () => {
|
|
1865
|
+
active = false;
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
function runtimeContext(g) {
|
|
1869
|
+
const h = g.HermesInternal;
|
|
1870
|
+
if (!h) return { name: "jsc" };
|
|
1871
|
+
try {
|
|
1872
|
+
const version = h.getRuntimeProperties?.()?.["OSS Release Version"];
|
|
1873
|
+
return { name: "hermes", ...typeof version === "string" && version ? { version: version.slice(0, 50) } : {} };
|
|
1874
|
+
} catch {
|
|
1875
|
+
return { name: "hermes" };
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/core/stack.ts
|
|
1880
|
+
var AT = /^\s*at (?:(.+?) \()?(?:address at )?(.+?):(\d+):(\d+)\)?\s*$/;
|
|
1881
|
+
var AT_NATIVE = /^\s*at (?:(.+?) \()?(?:native|\[native code\])\)?\s*$/;
|
|
1882
|
+
var JSC = /^\s*([^@]*)@(.+?):(\d+):(\d+)\s*$/;
|
|
1883
|
+
var JSC_BARE = /^\s*(\S+?):(\d+):(\d+)\s*$/;
|
|
1884
|
+
var JSC_NATIVE = /^\s*(?:([^@]*)@)?\[native code\]\s*$/;
|
|
1885
|
+
var BUNDLE = /\.(jsbundle|bundle|hbc)$/i;
|
|
1886
|
+
function normalizeBundleFile(file) {
|
|
1887
|
+
const f = file.trim();
|
|
1888
|
+
if (/^https?:\/\//i.test(f)) return f.replace(/[?#].*$/, "");
|
|
1889
|
+
const path = f.replace(/^file:\/\//i, "").replace(/[?#].*$/, "");
|
|
1890
|
+
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
1891
|
+
return BUNDLE.test(base) ? reactNativeBundleUrl(path) : path;
|
|
1892
|
+
}
|
|
1893
|
+
function inAppFor(file) {
|
|
1894
|
+
if (!file) return false;
|
|
1895
|
+
if (/(^|\/)node_modules\//.test(file) || /(^|\/)InternalBytecode\.js$/.test(file)) return false;
|
|
1896
|
+
if (file.startsWith("app:///") || /^https?:\/\//i.test(file)) return void 0;
|
|
1897
|
+
return true;
|
|
1898
|
+
}
|
|
1899
|
+
function frame(fn, file, line, col) {
|
|
1900
|
+
const name = (fn || "").trim();
|
|
1901
|
+
const normalized = file ? normalizeBundleFile(file).slice(0, 500) : void 0;
|
|
1902
|
+
const inApp = inAppFor(normalized);
|
|
1903
|
+
return {
|
|
1904
|
+
...name ? { fn: name.slice(0, 200) } : {},
|
|
1905
|
+
...normalized ? { file: normalized } : {},
|
|
1906
|
+
...line ? { line: Number(line) } : {},
|
|
1907
|
+
...col ? { col: Number(col) } : {},
|
|
1908
|
+
...inApp !== void 0 ? { inApp } : {}
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
function parseReactNativeStack(stack, max = MAX_DETAIL_FRAMES) {
|
|
1912
|
+
if (!stack || typeof stack !== "string") return [];
|
|
1913
|
+
const frames = [];
|
|
1914
|
+
for (const raw of stack.split("\n")) {
|
|
1915
|
+
if (frames.length >= max) break;
|
|
1916
|
+
const line = raw.slice(0, 1e3);
|
|
1917
|
+
let m;
|
|
1918
|
+
if (m = AT_NATIVE.exec(line)) frames.push(frame(m[1], void 0));
|
|
1919
|
+
else if (m = AT.exec(line)) frames.push(frame(m[1], m[2], m[3], m[4]));
|
|
1920
|
+
else if (m = JSC_NATIVE.exec(line)) frames.push(frame(m[1], void 0));
|
|
1921
|
+
else if (m = JSC.exec(line)) frames.push(frame(m[1], m[2], m[3], m[4]));
|
|
1922
|
+
else if (m = JSC_BARE.exec(line)) frames.push(frame(void 0, m[1], m[2], m[3]));
|
|
1923
|
+
}
|
|
1924
|
+
return frames;
|
|
1925
|
+
}
|
|
1926
|
+
function toDetailFrames(frames) {
|
|
1927
|
+
return frames.slice().reverse().map((f) => ({
|
|
1928
|
+
...f.fn ? { function: f.fn } : {},
|
|
1929
|
+
...f.file ? { filename: f.file, abs_path: f.file } : {},
|
|
1930
|
+
...f.line !== void 0 ? { lineno: f.line } : {},
|
|
1931
|
+
...f.col !== void 0 ? { colno: f.col } : {},
|
|
1932
|
+
...f.inApp !== void 0 ? { in_app: f.inApp } : {}
|
|
1933
|
+
}));
|
|
1934
|
+
}
|
|
1935
|
+
var MAX_FRAMES_BYTES = 6e3;
|
|
1936
|
+
function fitPropertyFrames(frames) {
|
|
1937
|
+
const out = frames.slice(0, 30);
|
|
1938
|
+
while (out.length > 1 && JSON.stringify(out).length > MAX_FRAMES_BYTES) out.pop();
|
|
1939
|
+
return out;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/core/errors.ts
|
|
1943
|
+
var CONTEXT_KEYS = ["tags", "extra", "level", "fingerprint", "contexts", "user"];
|
|
1944
|
+
function splitHint(hint) {
|
|
1945
|
+
const ctx = {};
|
|
1946
|
+
const properties = {};
|
|
1947
|
+
for (const [k, v] of Object.entries(hint && typeof hint === "object" ? hint : {})) (CONTEXT_KEYS.includes(k) ? ctx : properties)[k] = v;
|
|
1948
|
+
return { ctx, properties };
|
|
1949
|
+
}
|
|
1950
|
+
var nowSeconds = () => Date.now() / 1e3;
|
|
1951
|
+
var ErrorScope = class {
|
|
1952
|
+
constructor(opts) {
|
|
1953
|
+
__publicField(this, "opts", opts);
|
|
1954
|
+
__publicField(this, "tags", {});
|
|
1955
|
+
__publicField(this, "extra", {});
|
|
1956
|
+
__publicField(this, "contexts", {});
|
|
1957
|
+
__publicField(this, "user", {});
|
|
1958
|
+
__publicField(this, "crumbs", new BreadcrumbBuffer(MAX_BREADCRUMBS));
|
|
1959
|
+
}
|
|
1960
|
+
get max() {
|
|
1961
|
+
return Math.max(0, Math.min(MAX_BREADCRUMBS, this.opts()?.maxBreadcrumbs ?? MAX_BREADCRUMBS));
|
|
1962
|
+
}
|
|
1963
|
+
addBreadcrumb(crumb, hint) {
|
|
1964
|
+
if (!crumb || typeof crumb !== "object") return;
|
|
1965
|
+
let c = { timestamp: nowSeconds(), ...crumb };
|
|
1966
|
+
const before = this.opts()?.beforeBreadcrumb;
|
|
1967
|
+
if (before) {
|
|
1968
|
+
try {
|
|
1969
|
+
c = before(c, hint);
|
|
1970
|
+
} catch {
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
const max = this.max;
|
|
1974
|
+
if (!c || !max) return;
|
|
1975
|
+
this.crumbs.add(c, max);
|
|
1976
|
+
}
|
|
1977
|
+
breadcrumbs() {
|
|
1978
|
+
return this.crumbs.values();
|
|
1979
|
+
}
|
|
1980
|
+
clear() {
|
|
1981
|
+
this.tags = {};
|
|
1982
|
+
this.extra = {};
|
|
1983
|
+
this.contexts = {};
|
|
1984
|
+
this.user = {};
|
|
1985
|
+
this.crumbs.clear();
|
|
1986
|
+
}
|
|
1987
|
+
};
|
|
1988
|
+
var isError = (v) => v instanceof Error || !!v && typeof v === "object" && typeof v.message === "string" && typeof v.stack === "string";
|
|
1989
|
+
function describe(value) {
|
|
1990
|
+
if (typeof value === "string") return value;
|
|
1991
|
+
try {
|
|
1992
|
+
return JSON.stringify(value) ?? String(value);
|
|
1993
|
+
} catch {
|
|
1994
|
+
return String(value);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
function exceptionFromValue(error, source, handled, fallbackType = "Error") {
|
|
1998
|
+
const e = error;
|
|
1999
|
+
const ok = isError(error);
|
|
2000
|
+
return {
|
|
2001
|
+
type: (ok && e.name || fallbackType).slice(0, 200),
|
|
2002
|
+
message: (ok ? e.message : describe(error)).slice(0, 1e3),
|
|
2003
|
+
frames: ok ? fitPropertyFrames(parseReactNativeStack(e.stack)) : [],
|
|
2004
|
+
source,
|
|
2005
|
+
handled
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
function exceptionChain(error, main, mechanism) {
|
|
2009
|
+
const mainFrames = isError(error) ? parseReactNativeStack(error.stack) : main.frames;
|
|
2010
|
+
const chain = [{ type: main.type, value: main.message, mechanism: { type: mechanism, handled: main.handled !== false, exception_id: 0 }, ...mainFrames.length ? { stacktrace: { frames: toDetailFrames(mainFrames) } } : {} }];
|
|
2011
|
+
const seen = /* @__PURE__ */ new Set([error]);
|
|
2012
|
+
let cur = isError(error) ? error.cause : void 0;
|
|
2013
|
+
while (cur !== void 0 && cur !== null && !seen.has(cur) && chain.length < MAX_EXCEPTION_CHAIN) {
|
|
2014
|
+
seen.add(cur);
|
|
2015
|
+
const e = cur;
|
|
2016
|
+
const id = chain.length;
|
|
2017
|
+
const frames = isError(e) ? parseReactNativeStack(e.stack, 50) : [];
|
|
2018
|
+
chain.push({
|
|
2019
|
+
type: (isError(e) && e.name || "Error").slice(0, 200),
|
|
2020
|
+
value: (isError(e) ? e.message : describe(cur)).slice(0, 1e3),
|
|
2021
|
+
mechanism: { type: "chained", source: "cause", handled: true, exception_id: id, parent_id: id - 1 },
|
|
2022
|
+
...frames.length ? { stacktrace: { frames: toDetailFrames(frames) } } : {}
|
|
2023
|
+
});
|
|
2024
|
+
cur = isError(e) ? e.cause : void 0;
|
|
2025
|
+
}
|
|
2026
|
+
return chain.reverse();
|
|
2027
|
+
}
|
|
2028
|
+
var SENSITIVE_KEY = new RegExp(`${SENSITIVE_PROPERTY_PATTERN.source}|authorization|cookie|api[-_]?key`, "i");
|
|
2029
|
+
function normalizeValue(value, depth = 5, seen = /* @__PURE__ */ new Set()) {
|
|
2030
|
+
if (value === null || typeof value === "boolean") return value;
|
|
2031
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
|
|
2032
|
+
if (typeof value === "string") return value.slice(0, 5e3);
|
|
2033
|
+
if (typeof value === "bigint") return value.toString();
|
|
2034
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") return void 0;
|
|
2035
|
+
if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.toISOString() : String(value);
|
|
2036
|
+
if (isError(value)) return { name: value.name, message: value.message.slice(0, 1e3) };
|
|
2037
|
+
if (typeof value !== "object" || seen.has(value)) return "[Circular]";
|
|
2038
|
+
if (depth <= 0) return Array.isArray(value) ? "[Array]" : "[Object]";
|
|
2039
|
+
seen.add(value);
|
|
2040
|
+
try {
|
|
2041
|
+
if (Array.isArray(value)) return value.slice(0, MAX_DETAIL_FRAMES).map((v) => normalizeValue(v, depth - 1, seen) ?? null);
|
|
2042
|
+
const out = {};
|
|
2043
|
+
let n = 0;
|
|
2044
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2045
|
+
if (n++ >= 100) break;
|
|
2046
|
+
if (SENSITIVE_KEY.test(k)) out[k] = "[Filtered]";
|
|
2047
|
+
else {
|
|
2048
|
+
const nv = normalizeValue(v, depth - 1, seen);
|
|
2049
|
+
if (nv !== void 0) out[k] = nv;
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
return out;
|
|
2053
|
+
} finally {
|
|
2054
|
+
seen.delete(value);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
var record = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
2058
|
+
var jsonSize = (v) => {
|
|
2059
|
+
try {
|
|
2060
|
+
return JSON.stringify(v)?.length ?? 0;
|
|
2061
|
+
} catch {
|
|
2062
|
+
return Infinity;
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
2065
|
+
function finalizeDetail(detail, max = MAX_EXCEPTION_DETAIL_BYTES) {
|
|
2066
|
+
let d = normalizeValue(detail, 12);
|
|
2067
|
+
if (!d || typeof d !== "object") return void 0;
|
|
2068
|
+
if (d.tags) d.tags = normalizeTags(Object.fromEntries(Object.entries(d.tags).filter(([k]) => !SENSITIVE_KEY.test(k))));
|
|
2069
|
+
d = trimExceptionDetail(d, max);
|
|
2070
|
+
if (jsonSize(d) <= max) return d;
|
|
2071
|
+
const slim = { ...d.level ? { level: d.level } : {}, ...d.exception ? { exception: d.exception } : {}, ...d.tags ? { tags: d.tags } : {}, ...d.fingerprint ? { fingerprint: d.fingerprint } : {}, ...d.release ? { release: d.release } : {}, ...d.dist ? { dist: d.dist } : {} };
|
|
2072
|
+
return jsonSize(slim) <= max ? slim : void 0;
|
|
2073
|
+
}
|
|
2074
|
+
function buildDetail(input) {
|
|
2075
|
+
const { scope, ctx } = input;
|
|
2076
|
+
const contexts = { ...input.contexts, ...scope.contexts };
|
|
2077
|
+
for (const [k, v] of Object.entries(record(ctx.contexts))) {
|
|
2078
|
+
if (v && typeof v === "object") contexts[k] = v;
|
|
2079
|
+
else delete contexts[k];
|
|
2080
|
+
}
|
|
2081
|
+
const user = { ...input.userId ? { id: input.userId } : {}, ...scope.user, ...record(ctx.user) };
|
|
2082
|
+
const message = input.props.type === "Message";
|
|
2083
|
+
return {
|
|
2084
|
+
level: input.level,
|
|
2085
|
+
platform: "javascript",
|
|
2086
|
+
timestamp: nowSeconds(),
|
|
2087
|
+
...input.release ? { release: input.release } : {},
|
|
2088
|
+
...input.dist ? { dist: input.dist } : {},
|
|
2089
|
+
...input.environment ? { environment: input.environment } : {},
|
|
2090
|
+
...message ? { message: { formatted: input.props.message } } : { exception: { values: exceptionChain(input.error, input.props, input.mechanism) } },
|
|
2091
|
+
breadcrumbs: { values: scope.breadcrumbs() },
|
|
2092
|
+
contexts,
|
|
2093
|
+
tags: normalizeTags({ ...scope.tags, ...record(ctx.tags) }),
|
|
2094
|
+
extra: { ...scope.extra, ...record(ctx.extra) },
|
|
2095
|
+
...Object.keys(user).length ? { user } : {},
|
|
2096
|
+
...Array.isArray(ctx.fingerprint) && ctx.fingerprint.length ? { fingerprint: ctx.fingerprint.map(String) } : {},
|
|
2097
|
+
sdk: input.sdk
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
function prepareError(props, extraProperties, detail, opts, fatal, release) {
|
|
2101
|
+
const file = props.frames.find((f) => f.file)?.file;
|
|
2102
|
+
if (opts && errorFilterDrops({ type: props.type, message: props.message, file }, opts)) return null;
|
|
2103
|
+
if (typeof opts?.sampleRate === "number" && opts.sampleRate < 1 && Math.random() >= Math.max(0, opts.sampleRate)) return null;
|
|
2104
|
+
const values = detail.exception?.values ?? [];
|
|
2105
|
+
const thrown = values[values.length - 1];
|
|
2106
|
+
const mechanism = thrown?.mechanism?.type ?? props.source;
|
|
2107
|
+
let properties = { ...props, level: detail.level ?? "error", platform: "javascript", ...mechanism ? { mechanism } : {} };
|
|
2108
|
+
if (release) properties.release = release.slice(0, 100);
|
|
2109
|
+
if (detail.fingerprint?.length) properties.fingerprintParts = detail.fingerprint.slice(0, 20);
|
|
2110
|
+
for (const [k, v] of Object.entries(extraProperties)) if (!(k in properties)) properties[k] = v;
|
|
2111
|
+
let d = detail;
|
|
2112
|
+
if (opts?.beforeSend) {
|
|
2113
|
+
try {
|
|
2114
|
+
const out = opts.beforeSend({ properties, detail: d });
|
|
2115
|
+
if (out === null) return null;
|
|
2116
|
+
if (out && typeof out === "object") {
|
|
2117
|
+
if (out.properties && typeof out.properties === "object") properties = out.properties;
|
|
2118
|
+
if (out.detail && typeof out.detail === "object") d = out.detail;
|
|
2119
|
+
}
|
|
2120
|
+
} catch {
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
const finalDetail = finalizeDetail(d);
|
|
2124
|
+
return { eventId: randomId(24), occurredAt: (/* @__PURE__ */ new Date()).toISOString(), properties, ...finalDetail ? { detail: finalDetail } : {}, fatal };
|
|
2125
|
+
}
|
|
2126
|
+
var ExceptionThrottle = class {
|
|
2127
|
+
constructor(max = 5) {
|
|
2128
|
+
__publicField(this, "max", max);
|
|
2129
|
+
__publicField(this, "seen", /* @__PURE__ */ new Map());
|
|
2130
|
+
}
|
|
2131
|
+
allow(props) {
|
|
2132
|
+
const frames = Array.isArray(props.frames) ? props.frames : [];
|
|
2133
|
+
const top = frames[0];
|
|
2134
|
+
const key = `${String(props.type)}|${String(props.message)}|${top ? `${top.file}:${top.line}:${top.col}` : ""}`;
|
|
2135
|
+
const n = this.seen.get(key) ?? 0;
|
|
2136
|
+
if (n >= this.max) return false;
|
|
2137
|
+
if (this.seen.size > 500) this.seen.clear();
|
|
2138
|
+
this.seen.set(key, n + 1);
|
|
2139
|
+
return true;
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
function rnVersion(v) {
|
|
2143
|
+
if (!v) return void 0;
|
|
2144
|
+
if (typeof v === "string") return v;
|
|
2145
|
+
if (typeof v.major !== "number") return void 0;
|
|
2146
|
+
return `${v.major}.${v.minor ?? 0}.${v.patch ?? 0}${v.prerelease ? `-${v.prerelease}` : ""}`;
|
|
2147
|
+
}
|
|
2148
|
+
function reactNativeContexts(device, app, g) {
|
|
2149
|
+
const runtime = g ? runtimeContext(g) : void 0;
|
|
2150
|
+
const version = rnVersion(device.reactNativeVersion);
|
|
2151
|
+
return {
|
|
2152
|
+
os: { name: device.platform === "android" ? "Android" : "iOS", ...device.osVersion ? { version: device.osVersion } : {} },
|
|
2153
|
+
device: {
|
|
2154
|
+
family: device.deviceType === "tablet" ? "Tablet" : "Mobile",
|
|
2155
|
+
...device.model ? { model: device.model } : {},
|
|
2156
|
+
...device.manufacturer ? { manufacturer: device.manufacturer } : {},
|
|
2157
|
+
...device.brand ? { brand: device.brand } : {},
|
|
2158
|
+
...device.simulator !== void 0 ? { simulator: device.simulator } : {}
|
|
2159
|
+
},
|
|
2160
|
+
app: {
|
|
2161
|
+
...app.appVersion ? { app_version: app.appVersion } : {},
|
|
2162
|
+
...app.build ? { app_build: app.build } : {},
|
|
2163
|
+
...app.inForeground !== void 0 ? { in_foreground: app.inForeground } : {}
|
|
2164
|
+
},
|
|
2165
|
+
...runtime ? { runtime } : {},
|
|
2166
|
+
react_native_context: {
|
|
2167
|
+
...runtime ? { js_engine: runtime.name } : {},
|
|
2168
|
+
...version ? { react_native_version: version } : {},
|
|
2169
|
+
...g ? { turbo_module: !!g.__turboModuleProxy || !!g.RN$Bridgeless, fabric: !!g.nativeFabricUIManager, expo: !!g.expo } : {}
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
|
|
1497
2174
|
// src/core/targets.ts
|
|
1498
2175
|
var TargetTimeoutError = class extends Error {
|
|
1499
2176
|
constructor(ids) {
|
|
@@ -2824,38 +3501,38 @@ var SessionRecorder = class {
|
|
|
2824
3501
|
this.capturing = true;
|
|
2825
3502
|
this.captureSoon = false;
|
|
2826
3503
|
const gen = this.generation;
|
|
2827
|
-
let
|
|
3504
|
+
let frame2 = null;
|
|
2828
3505
|
try {
|
|
2829
|
-
|
|
3506
|
+
frame2 = await this.opts.capturer(await this.opts.captureOptions());
|
|
2830
3507
|
} catch (err) {
|
|
2831
3508
|
this.opts.onError?.(err);
|
|
2832
3509
|
}
|
|
2833
3510
|
if (gen !== this.generation) return;
|
|
2834
3511
|
this.capturing = false;
|
|
2835
|
-
if (
|
|
3512
|
+
if (frame2) this.onFrame(frame2, this.now());
|
|
2836
3513
|
if (this.sid !== null && this.foreground) this.schedule(this.captureSoon ? TOUCH_SETTLE_MS : this.currentIntervalMs);
|
|
2837
3514
|
}
|
|
2838
|
-
onFrame(
|
|
3515
|
+
onFrame(frame2, at) {
|
|
2839
3516
|
const id = this.sid;
|
|
2840
|
-
if (id === null || !(
|
|
2841
|
-
const sizeChanged =
|
|
3517
|
+
if (id === null || !(frame2.width > 0) || !(frame2.height > 0) || !frame2.base64) return;
|
|
3518
|
+
const sizeChanged = frame2.width !== this.viewportWidth || frame2.height !== this.viewportHeight;
|
|
2842
3519
|
const keyframe = this.needKeyframe || !this.hasKeyframe || sizeChanged;
|
|
2843
|
-
if (!keyframe && this.lastFrame ===
|
|
3520
|
+
if (!keyframe && this.lastFrame === frame2.base64) {
|
|
2844
3521
|
this.unchanged++;
|
|
2845
3522
|
return;
|
|
2846
3523
|
}
|
|
2847
3524
|
this.unchanged = 0;
|
|
2848
|
-
this.lastFrame =
|
|
2849
|
-
this.originX =
|
|
2850
|
-
this.originY =
|
|
2851
|
-
this.viewportWidth =
|
|
2852
|
-
this.viewportHeight =
|
|
2853
|
-
const url = RrwebEvents.dataUrl(
|
|
3525
|
+
this.lastFrame = frame2.base64;
|
|
3526
|
+
this.originX = frame2.originX ?? 0;
|
|
3527
|
+
this.originY = frame2.originY ?? 0;
|
|
3528
|
+
this.viewportWidth = frame2.width;
|
|
3529
|
+
this.viewportHeight = frame2.height;
|
|
3530
|
+
const url = RrwebEvents.dataUrl(frame2.base64);
|
|
2854
3531
|
const up = this.opts.uploader;
|
|
2855
3532
|
if (!keyframe) return up.push(id, RrwebEvents.frame(at, url));
|
|
2856
3533
|
this.needKeyframe = false;
|
|
2857
|
-
up.push(id, RrwebEvents.meta(at, this.opts.href(),
|
|
2858
|
-
up.push(id, RrwebEvents.fullSnapshot(at, url,
|
|
3534
|
+
up.push(id, RrwebEvents.meta(at, this.opts.href(), frame2.width, frame2.height));
|
|
3535
|
+
up.push(id, RrwebEvents.fullSnapshot(at, url, frame2.width, frame2.height));
|
|
2859
3536
|
if (!this.hasKeyframe) {
|
|
2860
3537
|
this.hasKeyframe = true;
|
|
2861
3538
|
for (const [ts, payload] of this.pendingCustom) up.push(id, RrwebEvents.custom(Math.max(ts, at), "guidely", payload));
|
|
@@ -2917,7 +3594,7 @@ var PersistentStore = class {
|
|
|
2917
3594
|
|
|
2918
3595
|
// src/core/client.ts
|
|
2919
3596
|
var SDK_NAME = "@guidelytech/react-native";
|
|
2920
|
-
var SDK_VERSION = "0.
|
|
3597
|
+
var SDK_VERSION = "0.17.0";
|
|
2921
3598
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
2922
3599
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2923
3600
|
var CACHED_START_MS = 1500;
|
|
@@ -2932,6 +3609,10 @@ function settlesWithin(promise, ms) {
|
|
|
2932
3609
|
});
|
|
2933
3610
|
}
|
|
2934
3611
|
var appVersionKey = (key) => `guidely:app:${key}`;
|
|
3612
|
+
var crashKey = (key) => `guidely:crash:${key}`;
|
|
3613
|
+
var earlyCrashKey = (key) => `guidely:crash-early:${key}`;
|
|
3614
|
+
var MAX_STORED_CRASHES = 10;
|
|
3615
|
+
var MAX_SESSION_EXCEPTIONS = 50;
|
|
2935
3616
|
var UNKNOWN_VIEW = { selector: "View", tag: "View" };
|
|
2936
3617
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2937
3618
|
var emptyState = () => ({ guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] });
|
|
@@ -2948,6 +3629,8 @@ var GuidelyClient = class {
|
|
|
2948
3629
|
/** Survey answers, guide completions/dismissals and checklist progress, kept until the server has them. */
|
|
2949
3630
|
__publicField(this, "outbox", null);
|
|
2950
3631
|
__publicField(this, "config", null);
|
|
3632
|
+
/** The `environment` option as passed (the resolved one is in `options.environment`). */
|
|
3633
|
+
__publicField(this, "envOption");
|
|
2951
3634
|
__publicField(this, "configFetchedAt", 0);
|
|
2952
3635
|
__publicField(this, "etag", null);
|
|
2953
3636
|
__publicField(this, "anonymousId", "");
|
|
@@ -3011,6 +3694,16 @@ var GuidelyClient = class {
|
|
|
3011
3694
|
__publicField(this, "localizedRc", null);
|
|
3012
3695
|
/** "What's new" last-seen time when the resource center opened (entries after it read as new). */
|
|
3013
3696
|
__publicField(this, "announcementsSeenAtOpen", null);
|
|
3697
|
+
// Error monitoring.
|
|
3698
|
+
__publicField(this, "errorScope", new ErrorScope(() => this.options.errors));
|
|
3699
|
+
__publicField(this, "exceptionThrottle", new ExceptionThrottle());
|
|
3700
|
+
/** Errors captured before storage and a config were loaded (whether error tracking is on is unknown until then). */
|
|
3701
|
+
__publicField(this, "earlyErrors", []);
|
|
3702
|
+
/** Crash reports left by earlier launches, sent once a config says error tracking is on. */
|
|
3703
|
+
__publicField(this, "storedCrashes", []);
|
|
3704
|
+
/** This launch's crash reports (normally one: the app ends right after). */
|
|
3705
|
+
__publicField(this, "launchCrashes", []);
|
|
3706
|
+
__publicField(this, "lastAppState", null);
|
|
3014
3707
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
3015
3708
|
__publicField(this, "eventBus", {
|
|
3016
3709
|
on: (name, cb) => {
|
|
@@ -3024,9 +3717,10 @@ var GuidelyClient = class {
|
|
|
3024
3717
|
}
|
|
3025
3718
|
});
|
|
3026
3719
|
if (!options || !options.projectId) throw new Error("[Guidely] `projectId` (your environment public key, e.g. pk_dev_\u2026) is required.");
|
|
3720
|
+
this.envOption = options.environment || void 0;
|
|
3027
3721
|
this.options = {
|
|
3028
3722
|
...options,
|
|
3029
|
-
environment: options.environment
|
|
3723
|
+
environment: options.environment || environmentFromKey(options.projectId),
|
|
3030
3724
|
apiUrl: options.apiUrl ?? DEFAULT_API_URL,
|
|
3031
3725
|
analytics: options.analytics ?? true,
|
|
3032
3726
|
configRefreshMs: options.configRefreshMs ?? 5 * 60 * 1e3,
|
|
@@ -3042,6 +3736,7 @@ var GuidelyClient = class {
|
|
|
3042
3736
|
if (options.user) this.user = { ...options.user };
|
|
3043
3737
|
this.locale = options.locale ? normalizeLocale(options.locale) || null : null;
|
|
3044
3738
|
this.flags = this.createFlags(options.flags);
|
|
3739
|
+
this.installErrorMonitoring();
|
|
3045
3740
|
}
|
|
3046
3741
|
createFlags(opts) {
|
|
3047
3742
|
const base = { sdkKey: this.options.projectId, apiUrl: this.options.apiUrl, fetch: this.options.fetch, sdk: { name: SDK_NAME, version: SDK_VERSION } };
|
|
@@ -3089,7 +3784,7 @@ var GuidelyClient = class {
|
|
|
3089
3784
|
async bootstrap() {
|
|
3090
3785
|
const key = this.options.projectId;
|
|
3091
3786
|
try {
|
|
3092
|
-
await this.store.hydrate([KEYS.anon, KEYS.seen, KEYS.session, KEYS.config(key), KEYS.etag(key), KEYS.state(key), KEYS.user(key), KEYS.queue(key), KEYS.outbox(key), KEYS.conversations(key), appVersionKey(key), LAUNCHER_PLACEMENT_KEY]);
|
|
3787
|
+
await this.store.hydrate([KEYS.anon, KEYS.seen, KEYS.session, KEYS.config(key), KEYS.etag(key), KEYS.state(key), KEYS.user(key), KEYS.queue(key), KEYS.outbox(key), KEYS.conversations(key), appVersionKey(key), LAUNCHER_PLACEMENT_KEY, crashKey(key), earlyCrashKey(key)]);
|
|
3093
3788
|
if (this.destroyed) return;
|
|
3094
3789
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
3095
3790
|
this.hadIdentity = !!this.anonymousId;
|
|
@@ -3102,7 +3797,9 @@ var GuidelyClient = class {
|
|
|
3102
3797
|
this.state = { ...emptyState(), ...readJson(this.store, KEYS.state(key), {}) };
|
|
3103
3798
|
this.user = this.options.user ? { ...this.options.user } : readJson(this.store, KEYS.user(key), null);
|
|
3104
3799
|
const saved = readJson(this.store, KEYS.session, null);
|
|
3105
|
-
this.
|
|
3800
|
+
this.loadStoredCrashes();
|
|
3801
|
+
const crashed = !!saved?.id && this.storedCrashes.some((c) => c.sessionId === saved.id);
|
|
3802
|
+
this.session = saved?.id && !crashed && !this.sessionExpired(Date.parse(saved.lastActiveAt)) ? saved : newSession();
|
|
3106
3803
|
this.persistSession();
|
|
3107
3804
|
this.lastAudiences = new Set(this.state.audienceIds);
|
|
3108
3805
|
this.syncFlagAttributes();
|
|
@@ -3121,9 +3818,10 @@ var GuidelyClient = class {
|
|
|
3121
3818
|
this.hydrated = true;
|
|
3122
3819
|
const cached = readJson(this.store, KEYS.config(key), null);
|
|
3123
3820
|
this.etag = this.store.get(KEYS.etag(key));
|
|
3124
|
-
if (cached && Array.isArray(cached.guides)) this.
|
|
3821
|
+
if (cached && Array.isArray(cached.guides)) this.useConfig(forPlatform(cached, this.bridge.device.platform));
|
|
3125
3822
|
this.syncMessenger();
|
|
3126
3823
|
this.pending.splice(0).forEach((fn) => fn());
|
|
3824
|
+
this.drainErrors();
|
|
3127
3825
|
const before = this.config?.configVersion;
|
|
3128
3826
|
const fresh = this.fetchConfig();
|
|
3129
3827
|
if (!this.config) await fresh;
|
|
@@ -3148,22 +3846,33 @@ var GuidelyClient = class {
|
|
|
3148
3846
|
if (this.hydrated) fn();
|
|
3149
3847
|
else this.pending.push(fn);
|
|
3150
3848
|
}
|
|
3849
|
+
/** Environment guides and flags target: the `environment` option, else the one the loaded config names, else a guess from the key. */
|
|
3850
|
+
get environment() {
|
|
3851
|
+
return this.envOption ?? this.config?.environment ?? this.options.environment;
|
|
3852
|
+
}
|
|
3853
|
+
useConfig(config) {
|
|
3854
|
+
const env = this.environment;
|
|
3855
|
+
this.config = config;
|
|
3856
|
+
if (this.environment !== env) this.syncFlagAttributes();
|
|
3857
|
+
}
|
|
3151
3858
|
async fetchConfig() {
|
|
3152
3859
|
try {
|
|
3153
3860
|
const res = await this.api.get("/config", { etag: this.etag, params: { platform: this.bridge.device.platform } });
|
|
3154
3861
|
this.configFetchedAt = Date.now();
|
|
3155
3862
|
if (res.status === 304) return true;
|
|
3156
3863
|
if (res.ok && res.data && Array.isArray(res.data.guides)) {
|
|
3157
|
-
|
|
3864
|
+
const config = forPlatform(res.data, this.bridge.device.platform);
|
|
3865
|
+
this.useConfig(config);
|
|
3158
3866
|
this.etag = res.etag;
|
|
3159
3867
|
writeJson(this.store, KEYS.config(this.options.projectId), res.data);
|
|
3160
3868
|
if (res.etag) this.store.set(KEYS.etag(this.options.projectId), res.etag);
|
|
3161
3869
|
else this.store.remove(KEYS.etag(this.options.projectId));
|
|
3162
3870
|
this.syncMessenger();
|
|
3163
3871
|
if (this.hydrated) this.checkLaunch();
|
|
3164
|
-
this.
|
|
3872
|
+
this.drainErrors();
|
|
3873
|
+
this.emitter.emit("config", config);
|
|
3165
3874
|
this.syncReplay();
|
|
3166
|
-
this.log("config loaded",
|
|
3875
|
+
this.log("config loaded", config.configVersion, `${config.guides.length} guides`);
|
|
3167
3876
|
return true;
|
|
3168
3877
|
}
|
|
3169
3878
|
this.reportError(new Error(`[Guidely] config request failed (${res.status})${this.config ? "; using cached config" : ""}`));
|
|
@@ -3204,7 +3913,12 @@ var GuidelyClient = class {
|
|
|
3204
3913
|
}
|
|
3205
3914
|
/** Called by the provider with React Native `AppState` changes. */
|
|
3206
3915
|
setAppState(state) {
|
|
3207
|
-
if (this.destroyed
|
|
3916
|
+
if (this.destroyed) return;
|
|
3917
|
+
if (state !== this.lastAppState) {
|
|
3918
|
+
this.lastAppState = state;
|
|
3919
|
+
this.autoBreadcrumb("appLifecycle", { type: "navigation", category: "app.lifecycle", message: state, data: { state } });
|
|
3920
|
+
}
|
|
3921
|
+
if (!this.hydrated) return;
|
|
3208
3922
|
if (state === "background") {
|
|
3209
3923
|
const wasForeground = this.backgroundedAt === null;
|
|
3210
3924
|
this.backgroundedAt = Date.now();
|
|
@@ -3389,8 +4103,13 @@ var GuidelyClient = class {
|
|
|
3389
4103
|
setConsent(consent) {
|
|
3390
4104
|
this.consent = consent;
|
|
3391
4105
|
this.whenHydrated(() => {
|
|
3392
|
-
if (consent === "denied")
|
|
3393
|
-
|
|
4106
|
+
if (consent === "denied") {
|
|
4107
|
+
this.queue?.clear();
|
|
4108
|
+
this.earlyErrors = [];
|
|
4109
|
+
this.storedCrashes = [];
|
|
4110
|
+
this.launchCrashes = [];
|
|
4111
|
+
void this.writeCrashes();
|
|
4112
|
+
} else if (consent === "granted") void this.queue?.flush();
|
|
3394
4113
|
this.syncReplay();
|
|
3395
4114
|
});
|
|
3396
4115
|
}
|
|
@@ -3414,6 +4133,7 @@ var GuidelyClient = class {
|
|
|
3414
4133
|
accountId: this.user?.account?.id || null,
|
|
3415
4134
|
sessionId: this.session.id,
|
|
3416
4135
|
url: this.collectUrls() && this.screenName ? this.currentUrl() : void 0,
|
|
4136
|
+
...this.appInfo().appVersion ? { appVersion: this.appInfo().appVersion } : {},
|
|
3417
4137
|
...extra
|
|
3418
4138
|
};
|
|
3419
4139
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
@@ -3453,6 +4173,7 @@ var GuidelyClient = class {
|
|
|
3453
4173
|
this.whenHydrated(() => {
|
|
3454
4174
|
const changed = name !== this.screenName;
|
|
3455
4175
|
if (changed) {
|
|
4176
|
+
this.autoBreadcrumb("navigation", { type: "navigation", category: "navigation", data: { ...this.screenName ? { from: this.screenName } : {}, to: name } });
|
|
3456
4177
|
this.leaveScreen();
|
|
3457
4178
|
this.flushHeatmap();
|
|
3458
4179
|
}
|
|
@@ -3616,7 +4337,11 @@ var GuidelyClient = class {
|
|
|
3616
4337
|
}
|
|
3617
4338
|
/** True when taps are worth observing (clicks, rage taps or heatmaps are on); the provider skips resolving views otherwise. */
|
|
3618
4339
|
isTapCaptureOn() {
|
|
3619
|
-
return this.hydrated && (this.autocaptureOn("clicks") || this.autocaptureOn("rageClicks") || this.autocaptureOn("heatmaps"));
|
|
4340
|
+
return this.hydrated && (this.autocaptureOn("clicks") || this.autocaptureOn("rageClicks") || this.autocaptureOn("heatmaps") || this.tapBreadcrumbsOn());
|
|
4341
|
+
}
|
|
4342
|
+
/** Taps become `ui.click` breadcrumbs while errors can be reported (even with click autocapture off). */
|
|
4343
|
+
tapBreadcrumbsOn() {
|
|
4344
|
+
return this.crumbsOn("taps") && this.errorTrackingAllowed();
|
|
3620
4345
|
}
|
|
3621
4346
|
/**
|
|
3622
4347
|
* Records a tap observed by the provider: `click` for an interactive element, `rage_click` for
|
|
@@ -3626,8 +4351,8 @@ var GuidelyClient = class {
|
|
|
3626
4351
|
recordTap(tap) {
|
|
3627
4352
|
try {
|
|
3628
4353
|
if (!this.hydrated || this.destroyed || !Number.isFinite(tap.x) || !Number.isFinite(tap.y)) return;
|
|
3629
|
-
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps") };
|
|
3630
|
-
if (!on.clicks && !on.rage && !on.heat) return;
|
|
4354
|
+
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps"), crumb: this.tapBreadcrumbsOn() };
|
|
4355
|
+
if (!on.clicks && !on.rage && !on.heat && !on.crumb) return;
|
|
3631
4356
|
const t = tap.t ?? Date.now();
|
|
3632
4357
|
const url = this.screenName ? this.currentUrl() : "";
|
|
3633
4358
|
this.tapChain = this.tapChain.then(() => this.processTap(tap, t, url, on)).catch((err) => this.reportError(err));
|
|
@@ -3644,6 +4369,8 @@ var GuidelyClient = class {
|
|
|
3644
4369
|
rect = target.rect;
|
|
3645
4370
|
}
|
|
3646
4371
|
if (this.destroyed) return;
|
|
4372
|
+
if (on.crumb && el) this.autoBreadcrumb("taps", { timestamp: t / 1e3, type: "user", category: "ui.click", message: el.selector, data: { selector: el.selector } });
|
|
4373
|
+
if (!on.clicks && !on.rage && !on.heat) return;
|
|
3647
4374
|
const platform = this.bridge.device.platform;
|
|
3648
4375
|
const maskAll = !!this.config?.settings?.replay?.maskAllText || !!this.options.sessionReplay?.maskAllText;
|
|
3649
4376
|
const eventUrl = this.collectUrls() && url ? url : void 0;
|
|
@@ -3742,6 +4469,277 @@ var GuidelyClient = class {
|
|
|
3742
4469
|
}
|
|
3743
4470
|
}
|
|
3744
4471
|
// ---------------------------------------------------------------------------
|
|
4472
|
+
// Error monitoring (docs/error-monitoring/sdks.md)
|
|
4473
|
+
// ---------------------------------------------------------------------------
|
|
4474
|
+
/**
|
|
4475
|
+
* Reports a caught error as an `exception` event (`handled: true`), grouped into an issue on the
|
|
4476
|
+
* Errors page. The second argument takes Sentry's capture context (`tags`, `extra`, `level`,
|
|
4477
|
+
* `fingerprint`, `contexts`, `user`); any other key is attached to the occurrence as a property.
|
|
4478
|
+
* Needs the project's error tracking on (plus analytics and consent); `errors.captureErrors: false`
|
|
4479
|
+
* does not stop it. Returns the event id (empty when nothing is sent).
|
|
4480
|
+
*/
|
|
4481
|
+
captureException(error, hint) {
|
|
4482
|
+
if (this.destroyed) return "";
|
|
4483
|
+
try {
|
|
4484
|
+
const { ctx, properties } = splitHint(hint);
|
|
4485
|
+
const props = exceptionFromValue(error, "manual", true);
|
|
4486
|
+
return this.deliverError(this.prepareCapture(error, props, properties, ctx, "generic", ctx.level ?? "error", false));
|
|
4487
|
+
} catch (err) {
|
|
4488
|
+
this.reportError(err);
|
|
4489
|
+
return "";
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
/** Reports a message (default level `info`) as its own issue, grouped by the message. */
|
|
4493
|
+
captureMessage(message, level = "info", hint) {
|
|
4494
|
+
if (this.destroyed) return "";
|
|
4495
|
+
try {
|
|
4496
|
+
const { ctx } = splitHint(hint);
|
|
4497
|
+
const props = { type: "Message", message: String(message).slice(0, 1e3), frames: [], source: "message", handled: true };
|
|
4498
|
+
return this.deliverError(this.prepareCapture(void 0, props, {}, ctx, "message", ctx.level ?? level, false));
|
|
4499
|
+
} catch (err) {
|
|
4500
|
+
this.reportError(err);
|
|
4501
|
+
return "";
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
/** Adds a breadcrumb (sent with the next errors; the last 100 are kept). */
|
|
4505
|
+
addBreadcrumb(crumb, hint) {
|
|
4506
|
+
if (!this.destroyed) this.errorScope.addBreadcrumb(crumb, hint);
|
|
4507
|
+
}
|
|
4508
|
+
/** Tags every later error (searchable on the issue page); `null` removes the tag. */
|
|
4509
|
+
setTag(key, value) {
|
|
4510
|
+
if (value === null || value === void 0) delete this.errorScope.tags[key];
|
|
4511
|
+
else this.errorScope.tags[key] = String(value);
|
|
4512
|
+
}
|
|
4513
|
+
setTags(tags) {
|
|
4514
|
+
for (const [k, v] of Object.entries(tags ?? {})) this.setTag(k, v);
|
|
4515
|
+
}
|
|
4516
|
+
/** Arbitrary data attached to every later error. */
|
|
4517
|
+
setExtra(key, value) {
|
|
4518
|
+
if (value === void 0) delete this.errorScope.extra[key];
|
|
4519
|
+
else this.errorScope.extra[key] = value;
|
|
4520
|
+
}
|
|
4521
|
+
setExtras(extras) {
|
|
4522
|
+
for (const [k, v] of Object.entries(extras ?? {})) this.setExtra(k, v);
|
|
4523
|
+
}
|
|
4524
|
+
/** A named context (`setContext("cart", { items: 3 })`) on every later error; `null` removes it. */
|
|
4525
|
+
setContext(name, context) {
|
|
4526
|
+
if (context && typeof context === "object") this.errorScope.contexts[name] = context;
|
|
4527
|
+
else delete this.errorScope.contexts[name];
|
|
4528
|
+
}
|
|
4529
|
+
/**
|
|
4530
|
+
* Sentry's `setUser`: identifies the user by `id` (like `identify`) and keeps `email` / `username`
|
|
4531
|
+
* on their errors. `null` forgets the user (`reset()`).
|
|
4532
|
+
*/
|
|
4533
|
+
setUser(user) {
|
|
4534
|
+
if (!user) {
|
|
4535
|
+
this.errorScope.user = {};
|
|
4536
|
+
return this.reset();
|
|
4537
|
+
}
|
|
4538
|
+
const { id, email, username, ...traits } = user;
|
|
4539
|
+
this.errorScope.user = { ...email ? { email: String(email) } : {}, ...username ? { username: String(username) } : {} };
|
|
4540
|
+
if (id !== void 0 && id !== null && String(id)) void this.identify(String(id), { ...traits, ...email ? { email } : {}, ...username ? { username } : {} });
|
|
4541
|
+
}
|
|
4542
|
+
crumbsOn(kind) {
|
|
4543
|
+
const b = this.options.errors?.breadcrumbs;
|
|
4544
|
+
return b !== false && !(b && b[kind] === false);
|
|
4545
|
+
}
|
|
4546
|
+
autoBreadcrumb(kind, crumb, hint) {
|
|
4547
|
+
if (!this.destroyed && this.crumbsOn(kind)) this.errorScope.addBreadcrumb(crumb, hint);
|
|
4548
|
+
}
|
|
4549
|
+
/** Errors may be captured: analytics on, consent not denied and, once a config says, the project's error tracking on. */
|
|
4550
|
+
errorsWanted() {
|
|
4551
|
+
if (this.destroyed || !this.options.analytics || this.consent === "denied") return false;
|
|
4552
|
+
const s = this.config?.settings;
|
|
4553
|
+
return !this.config || (s?.analyticsEnabled ?? true) && !!s?.errorTracking?.enabled;
|
|
4554
|
+
}
|
|
4555
|
+
/** Errors may be queued now (a config is known and allows them). */
|
|
4556
|
+
errorTrackingAllowed() {
|
|
4557
|
+
return !!this.config && this.errorsWanted();
|
|
4558
|
+
}
|
|
4559
|
+
/** Requests to the Guidely deployment never become breadcrumbs. */
|
|
4560
|
+
isGuidelyUrl(url) {
|
|
4561
|
+
const base = this.options.apiUrl.replace(/\/+$/, "");
|
|
4562
|
+
return !!base && (url === base || url.startsWith(`${base}/`));
|
|
4563
|
+
}
|
|
4564
|
+
release() {
|
|
4565
|
+
return this.options.release ?? this.appInfo().appVersion;
|
|
4566
|
+
}
|
|
4567
|
+
/** Builds and filters one error (`null` when dropped). */
|
|
4568
|
+
prepareCapture(error, props, properties, ctx, mechanism, level, fatal) {
|
|
4569
|
+
if (!this.errorsWanted()) return null;
|
|
4570
|
+
const info = this.appInfo();
|
|
4571
|
+
const release = this.release();
|
|
4572
|
+
const detail = buildDetail({
|
|
4573
|
+
error,
|
|
4574
|
+
props,
|
|
4575
|
+
mechanism,
|
|
4576
|
+
scope: this.errorScope,
|
|
4577
|
+
ctx,
|
|
4578
|
+
level,
|
|
4579
|
+
contexts: reactNativeContexts(this.bridge.device, { ...info, inForeground: this.backgroundedAt === null }, this.bridge.globals),
|
|
4580
|
+
release,
|
|
4581
|
+
dist: this.options.errors?.dist ?? info.build,
|
|
4582
|
+
environment: this.environment,
|
|
4583
|
+
userId: this.user?.id || null,
|
|
4584
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION }
|
|
4585
|
+
});
|
|
4586
|
+
return prepareError(props, properties, detail, this.options.errors, fatal, release);
|
|
4587
|
+
}
|
|
4588
|
+
/** Queues a prepared error, or holds it until storage and a config are loaded. Returns its event id ("" when dropped). */
|
|
4589
|
+
deliverError(p) {
|
|
4590
|
+
if (!p) return "";
|
|
4591
|
+
if (!this.hydrated || !this.config) {
|
|
4592
|
+
if (this.earlyErrors.length >= 10) return "";
|
|
4593
|
+
this.earlyErrors.push(p);
|
|
4594
|
+
return p.eventId;
|
|
4595
|
+
}
|
|
4596
|
+
return this.sendError(p) ? p.eventId : "";
|
|
4597
|
+
}
|
|
4598
|
+
sendError(p) {
|
|
4599
|
+
if (!this.errorTrackingAllowed()) return false;
|
|
4600
|
+
if (!p.fatal && !this.exceptionThrottle.allow(p.properties)) return false;
|
|
4601
|
+
const count = this.session.exceptions ?? 0;
|
|
4602
|
+
if (!p.fatal && count >= MAX_SESSION_EXCEPTIONS) return false;
|
|
4603
|
+
const event = this.enqueue("exception", { eventId: p.eventId, occurredAt: p.occurredAt, properties: p.properties, ...p.detail ? { detail: p.detail } : {} });
|
|
4604
|
+
if (!event) return false;
|
|
4605
|
+
this.session.exceptions = count + 1;
|
|
4606
|
+
this.persistSession();
|
|
4607
|
+
return true;
|
|
4608
|
+
}
|
|
4609
|
+
/** Sends what waited for storage and a config: earlier launches' crash reports, then early errors. */
|
|
4610
|
+
drainErrors() {
|
|
4611
|
+
if (!this.hydrated || !this.config || this.destroyed) return;
|
|
4612
|
+
const stored = this.storedCrashes.splice(0);
|
|
4613
|
+
if (stored.length) {
|
|
4614
|
+
if (this.errorTrackingAllowed()) {
|
|
4615
|
+
for (const c of stored) this.queue?.enqueue({ ...c, anonymousId: c.anonymousId || this.anonymousId, sessionId: c.sessionId || this.session.id });
|
|
4616
|
+
}
|
|
4617
|
+
void this.writeCrashes();
|
|
4618
|
+
}
|
|
4619
|
+
this.earlyErrors.splice(0).forEach((p) => this.sendError(p));
|
|
4620
|
+
}
|
|
4621
|
+
loadStoredCrashes() {
|
|
4622
|
+
const key = this.options.projectId;
|
|
4623
|
+
const valid = (c) => !!c && typeof c === "object" && typeof c.eventId === "string" && c.name === "exception";
|
|
4624
|
+
const read2 = (k) => {
|
|
4625
|
+
const v = readJson(this.store, k, []);
|
|
4626
|
+
return Array.isArray(v) ? v.filter(valid) : [];
|
|
4627
|
+
};
|
|
4628
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4629
|
+
this.storedCrashes = [...read2(crashKey(key)), ...read2(earlyCrashKey(key))].filter((c) => !seen.has(c.eventId) && !!seen.add(c.eventId)).slice(-MAX_STORED_CRASHES);
|
|
4630
|
+
this.store.remove(earlyCrashKey(key));
|
|
4631
|
+
if (this.storedCrashes.length) void this.writeCrashes();
|
|
4632
|
+
}
|
|
4633
|
+
/**
|
|
4634
|
+
* Writes the unsent crash reports to storage, directly (not behind other pending writes) so the
|
|
4635
|
+
* write starts before the app ends. Resolves when the storage has it.
|
|
4636
|
+
*/
|
|
4637
|
+
writeCrashes() {
|
|
4638
|
+
const storage = this.options.storage;
|
|
4639
|
+
const key = this.options.projectId;
|
|
4640
|
+
const direct = (op) => {
|
|
4641
|
+
try {
|
|
4642
|
+
return Promise.resolve(op()).catch((err) => this.log("crash report storage error", err));
|
|
4643
|
+
} catch (err) {
|
|
4644
|
+
this.log("crash report storage error", err);
|
|
4645
|
+
return Promise.resolve();
|
|
4646
|
+
}
|
|
4647
|
+
};
|
|
4648
|
+
if (!this.hydrated) {
|
|
4649
|
+
const json2 = JSON.stringify(this.launchCrashes.slice(-MAX_STORED_CRASHES));
|
|
4650
|
+
return storage ? direct(() => storage.setItem(earlyCrashKey(key), json2)) : Promise.resolve();
|
|
4651
|
+
}
|
|
4652
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4653
|
+
const all = [...this.storedCrashes, ...this.launchCrashes].filter((c) => !seen.has(c.eventId) && !!seen.add(c.eventId)).slice(-MAX_STORED_CRASHES);
|
|
4654
|
+
if (!all.length) {
|
|
4655
|
+
this.store.remove(crashKey(key));
|
|
4656
|
+
return storage ? direct(() => storage.removeItem(crashKey(key))) : Promise.resolve();
|
|
4657
|
+
}
|
|
4658
|
+
const json = JSON.stringify(all);
|
|
4659
|
+
this.store.set(crashKey(key), json);
|
|
4660
|
+
return storage ? direct(() => storage.setItem(crashKey(key), json)) : Promise.resolve();
|
|
4661
|
+
}
|
|
4662
|
+
/**
|
|
4663
|
+
* A fatal JS error: React Native ends a release app right after the global handler, so the report
|
|
4664
|
+
* is written to storage at once (and sent on the next launch), and sent now as well when the
|
|
4665
|
+
* network allows. The promise tells the handler when React Native's own handler may run.
|
|
4666
|
+
*/
|
|
4667
|
+
saveCrash(p) {
|
|
4668
|
+
if (!this.errorsWanted()) return void 0;
|
|
4669
|
+
const info = this.appInfo();
|
|
4670
|
+
const event = {
|
|
4671
|
+
eventId: p.eventId,
|
|
4672
|
+
name: "exception",
|
|
4673
|
+
occurredAt: p.occurredAt,
|
|
4674
|
+
anonymousId: this.anonymousId,
|
|
4675
|
+
userId: this.user?.id || null,
|
|
4676
|
+
accountId: this.user?.account?.id || null,
|
|
4677
|
+
...this.hydrated ? { sessionId: this.session.id } : {},
|
|
4678
|
+
...this.collectUrls() && this.screenName ? { url: this.currentUrl() } : {},
|
|
4679
|
+
...info.appVersion ? { appVersion: info.appVersion } : {},
|
|
4680
|
+
properties: sanitizeProperties(p.properties),
|
|
4681
|
+
...p.detail ? { detail: p.detail } : {}
|
|
4682
|
+
};
|
|
4683
|
+
this.launchCrashes.push(event);
|
|
4684
|
+
const written = this.writeCrashes();
|
|
4685
|
+
if (!this.hydrated || !this.config || !this.sendError(p)) return written;
|
|
4686
|
+
const sent = this.queue?.flush() ?? Promise.resolve();
|
|
4687
|
+
return Promise.all([written, sent]).then(async () => {
|
|
4688
|
+
if (this.queue?.peek().some((e) => e.eventId === event.eventId)) return;
|
|
4689
|
+
this.launchCrashes = this.launchCrashes.filter((c) => c.eventId !== event.eventId);
|
|
4690
|
+
await this.writeCrashes();
|
|
4691
|
+
});
|
|
4692
|
+
}
|
|
4693
|
+
/** An uncaught JS error from `ErrorUtils` (fatal ones end the app in release builds). */
|
|
4694
|
+
captureGlobalError(error, fatal) {
|
|
4695
|
+
try {
|
|
4696
|
+
const props = exceptionFromValue(error, "onerror", false);
|
|
4697
|
+
if (props.message.startsWith("[Guidely]")) return void 0;
|
|
4698
|
+
const p = this.prepareCapture(error, props, {}, {}, "onerror", fatal ? "fatal" : "error", fatal);
|
|
4699
|
+
if (!p) return void 0;
|
|
4700
|
+
if (fatal) return this.saveCrash(p);
|
|
4701
|
+
this.deliverError(p);
|
|
4702
|
+
} catch (err) {
|
|
4703
|
+
this.log("error capture failed", err);
|
|
4704
|
+
}
|
|
4705
|
+
return void 0;
|
|
4706
|
+
}
|
|
4707
|
+
captureRejection(reason) {
|
|
4708
|
+
try {
|
|
4709
|
+
const props = exceptionFromValue(reason, "unhandledrejection", false, "UnhandledRejection");
|
|
4710
|
+
if (props.message.startsWith("[Guidely]")) return;
|
|
4711
|
+
this.deliverError(this.prepareCapture(reason, props, {}, {}, "unhandledrejection", "error", false));
|
|
4712
|
+
} catch (err) {
|
|
4713
|
+
this.log("error capture failed", err);
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
/**
|
|
4717
|
+
* Guide breadcrumbs, and (with the bridge's JS globals) the global error handler, promise
|
|
4718
|
+
* rejection tracking and console / network breadcrumbs. Undone by `destroy()`.
|
|
4719
|
+
*/
|
|
4720
|
+
installErrorMonitoring() {
|
|
4721
|
+
const guideCrumb = (message, guide, stepIndex) => this.autoBreadcrumb("guides", { type: "info", category: "guide", message, data: { guideId: guide.id, ...stepIndex !== void 0 ? { stepIndex } : {} } });
|
|
4722
|
+
this.cleanups.push(
|
|
4723
|
+
this.emitter.on("guide:start", (g2) => guideCrumb("Guide shown", g2)),
|
|
4724
|
+
this.emitter.on("guide:step", (s) => guideCrumb("Guide step", s.guide, s.stepIndex)),
|
|
4725
|
+
this.emitter.on("guide:complete", (g2) => guideCrumb("Guide completed", g2)),
|
|
4726
|
+
this.emitter.on("guide:dismiss", (g2) => guideCrumb("Guide dismissed", g2)),
|
|
4727
|
+
this.emitter.on("guide:skip", (g2) => guideCrumb("Guide skipped", g2))
|
|
4728
|
+
);
|
|
4729
|
+
const g = this.bridge.globals;
|
|
4730
|
+
if (!g) return;
|
|
4731
|
+
try {
|
|
4732
|
+
if (this.crumbsOn("console")) this.cleanups.push(instrumentConsole(g.console, (c) => this.autoBreadcrumb("console", c)));
|
|
4733
|
+
if (this.crumbsOn("http")) this.cleanups.push(instrumentHttp(g, (c) => this.autoBreadcrumb("http", c), (url) => this.isGuidelyUrl(url)));
|
|
4734
|
+
if (this.options.errors?.captureErrors !== false) {
|
|
4735
|
+
this.cleanups.push(installGlobalHandler(g, (error, fatal) => this.captureGlobalError(error, fatal)));
|
|
4736
|
+
this.cleanups.push(installRejectionTracking(g, (reason) => this.captureRejection(reason), this.options.errors?.rejectionTracker));
|
|
4737
|
+
}
|
|
4738
|
+
} catch (err) {
|
|
4739
|
+
this.reportError(err);
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
// ---------------------------------------------------------------------------
|
|
3745
4743
|
// Targeting + triggers
|
|
3746
4744
|
// ---------------------------------------------------------------------------
|
|
3747
4745
|
/** Builds the targeting context for the current moment. */
|
|
@@ -3767,7 +4765,7 @@ var GuidelyClient = class {
|
|
|
3767
4765
|
completedChecklists: new Set(keysOf(Object.entries(this.state.checklists).filter(([, p]) => !!p.completedAt).map(([id]) => id))),
|
|
3768
4766
|
audienceIds: new Set(this.state.audienceIds),
|
|
3769
4767
|
now: /* @__PURE__ */ new Date(),
|
|
3770
|
-
environment: this.
|
|
4768
|
+
environment: this.environment,
|
|
3771
4769
|
locale: device.locale,
|
|
3772
4770
|
platform: device.platform,
|
|
3773
4771
|
screen: this.screenName,
|
|
@@ -4588,7 +5586,7 @@ var GuidelyClient = class {
|
|
|
4588
5586
|
locale: d.locale,
|
|
4589
5587
|
appVersion: this.options.appVersion ?? d.appVersion,
|
|
4590
5588
|
osVersion: d.osVersion,
|
|
4591
|
-
environment: this.
|
|
5589
|
+
environment: this.environment,
|
|
4592
5590
|
...this.screenName ? { screen: this.screenName, path: this.currentUrl() } : {},
|
|
4593
5591
|
...this.user?.traits ?? {},
|
|
4594
5592
|
id: this.user?.id || this.anonymousId || void 0,
|
|
@@ -4713,6 +5711,35 @@ function detectAppVersion(src) {
|
|
|
4713
5711
|
return {};
|
|
4714
5712
|
}
|
|
4715
5713
|
}
|
|
5714
|
+
function detectDeviceModel(src) {
|
|
5715
|
+
try {
|
|
5716
|
+
const expo = src.expoModules?.ExpoDevice ?? src.nativeModules.ExpoDevice;
|
|
5717
|
+
const info = src.nativeModules.RNDeviceInfo;
|
|
5718
|
+
const c = src.platform.constants ?? {};
|
|
5719
|
+
const android = src.platform.OS === "android";
|
|
5720
|
+
const model = text(constant(expo, "modelName")) ?? text(constant(info, "model")) ?? (android ? text(c.Model) : void 0);
|
|
5721
|
+
const manufacturer = text(constant(expo, "manufacturer")) ?? (android ? text(c.Manufacturer) : "Apple");
|
|
5722
|
+
const brand = text(constant(expo, "brand")) ?? text(constant(info, "brand")) ?? (android ? text(c.Brand) : void 0);
|
|
5723
|
+
const isDevice = constant(expo, "isDevice");
|
|
5724
|
+
return {
|
|
5725
|
+
...model ? { model } : {},
|
|
5726
|
+
...manufacturer ? { manufacturer } : {},
|
|
5727
|
+
...brand ? { brand } : {},
|
|
5728
|
+
...typeof isDevice === "boolean" ? { simulator: !isDevice } : {}
|
|
5729
|
+
};
|
|
5730
|
+
} catch {
|
|
5731
|
+
return {};
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
function detectReactNativeVersion(src) {
|
|
5735
|
+
try {
|
|
5736
|
+
const v = src.platform.constants?.reactNativeVersion;
|
|
5737
|
+
if (!v || typeof v.major !== "number") return void 0;
|
|
5738
|
+
return { major: v.major, minor: Number(v.minor) || 0, patch: Number(v.patch) || 0, ...v.prerelease != null ? { prerelease: String(v.prerelease) } : {} };
|
|
5739
|
+
} catch {
|
|
5740
|
+
return void 0;
|
|
5741
|
+
}
|
|
5742
|
+
}
|
|
4716
5743
|
function detectOsVersion(src) {
|
|
4717
5744
|
try {
|
|
4718
5745
|
const c = src.platform.constants ?? {};
|
|
@@ -4845,7 +5872,7 @@ function measureHost(host) {
|
|
|
4845
5872
|
done(null);
|
|
4846
5873
|
});
|
|
4847
5874
|
}
|
|
4848
|
-
function
|
|
5875
|
+
function describe2(f, masked, opts, withText) {
|
|
4849
5876
|
const props = f.memoizedProps && typeof f.memoizedProps === "object" ? f.memoizedProps : {};
|
|
4850
5877
|
const host = firstHost(f);
|
|
4851
5878
|
const el = { selector: idSelector(f) ?? pathSelector(f), tag: componentName(f.type) ?? "View" };
|
|
@@ -4890,8 +5917,8 @@ function resolveFiber(target, opts = {}) {
|
|
|
4890
5917
|
const element = press ?? role;
|
|
4891
5918
|
return {
|
|
4892
5919
|
ignored: false,
|
|
4893
|
-
element: element ?
|
|
4894
|
-
hit: hitFiber ?
|
|
5920
|
+
element: element ? describe2(element, masked, opts, true) : null,
|
|
5921
|
+
hit: hitFiber ? describe2(hitFiber, masked, opts, componentName(hitFiber.type) === "Text") : null
|
|
4895
5922
|
};
|
|
4896
5923
|
} catch {
|
|
4897
5924
|
return none;
|
|
@@ -5024,7 +6051,7 @@ function parseHtml(html) {
|
|
|
5024
6051
|
if (last && last.bold === run.bold && last.italic === run.italic && last.underline === run.underline && last.code === run.code && last.href === run.href) last.text += text2;
|
|
5025
6052
|
else current.runs.push(run);
|
|
5026
6053
|
};
|
|
5027
|
-
const
|
|
6054
|
+
const size2 = (attrs, name) => {
|
|
5028
6055
|
const n = Number(attr(attrs, name));
|
|
5029
6056
|
return n > 0 ? n : void 0;
|
|
5030
6057
|
};
|
|
@@ -5086,8 +6113,8 @@ function parseHtml(html) {
|
|
|
5086
6113
|
}
|
|
5087
6114
|
} else if (tag === "img" && !closing) {
|
|
5088
6115
|
const src = attr(attrs, "src");
|
|
5089
|
-
const w =
|
|
5090
|
-
const h =
|
|
6116
|
+
const w = size2(attrs, "width");
|
|
6117
|
+
const h = size2(attrs, "height");
|
|
5091
6118
|
if (isSafeMediaUrl(src)) pdf.pages.push({ src, alt: attr(attrs, "alt") ?? "", ...w && h ? { width: w, height: h } : {} });
|
|
5092
6119
|
}
|
|
5093
6120
|
continue;
|
|
@@ -5261,10 +6288,10 @@ function BodyPdf({ block, palette: p, onLink }) {
|
|
|
5261
6288
|
] }) })
|
|
5262
6289
|
] });
|
|
5263
6290
|
}
|
|
5264
|
-
function RichText({ html, palette: p, onLink, size = 15 }) {
|
|
6291
|
+
function RichText({ html, palette: p, onLink, size: size2 = 15 }) {
|
|
5265
6292
|
const blocks = useMemo2(() => parseHtml(html), [html]);
|
|
5266
6293
|
if (!blocks.length) return null;
|
|
5267
|
-
const base = { color: p.text, fontSize:
|
|
6294
|
+
const base = { color: p.text, fontSize: size2, lineHeight: Math.round(size2 * 1.42), fontFamily: p.fontFamily };
|
|
5268
6295
|
return /* @__PURE__ */ jsx3(View2, { style: styles2.wrap, children: blocks.map((b, i) => {
|
|
5269
6296
|
if (b.kind === "image") return /* @__PURE__ */ jsx3(BodyImage, { block: b, palette: p }, i);
|
|
5270
6297
|
if (b.kind === "video") return /* @__PURE__ */ jsx3(BodyVideo, { block: b, palette: p, onLink }, i);
|
|
@@ -5467,17 +6494,17 @@ function useReduceMotion() {
|
|
|
5467
6494
|
}, []);
|
|
5468
6495
|
return on;
|
|
5469
6496
|
}
|
|
5470
|
-
function MessengerLauncher({ store, client, palette: p, style, frame, defaultSide }) {
|
|
6497
|
+
function MessengerLauncher({ store, client, palette: p, style, frame: frame2, defaultSide }) {
|
|
5471
6498
|
const snap = useMessenger(store);
|
|
5472
6499
|
const reduceMotion = useReduceMotion();
|
|
5473
6500
|
const [placement, setPlacement] = useState3(() => client.getMessengerLauncherPlacement());
|
|
5474
6501
|
const pan = useRef2(new Animated.ValueXY()).current;
|
|
5475
6502
|
const offset = useRef2({ x: 0, y: 0 });
|
|
5476
6503
|
const dragging = useRef2(false);
|
|
5477
|
-
const live = useRef2({ frame, defaultSide, reduceMotion });
|
|
5478
|
-
live.current = { frame, defaultSide, reduceMotion };
|
|
5479
|
-
const { width, height, size, defaultBottom } =
|
|
5480
|
-
const { top, bottom, left, right } =
|
|
6504
|
+
const live = useRef2({ frame: frame2, defaultSide, reduceMotion });
|
|
6505
|
+
live.current = { frame: frame2, defaultSide, reduceMotion };
|
|
6506
|
+
const { width, height, size: size2, defaultBottom } = frame2;
|
|
6507
|
+
const { top, bottom, left, right } = frame2.insets;
|
|
5481
6508
|
useLayoutEffect(() => {
|
|
5482
6509
|
if (dragging.current) return;
|
|
5483
6510
|
const f = live.current.frame;
|
|
@@ -5485,7 +6512,7 @@ function MessengerLauncher({ store, client, palette: p, style, frame, defaultSid
|
|
|
5485
6512
|
const at = placement ? placementPoint(f, placement) : anchor;
|
|
5486
6513
|
offset.current = { x: at.x - anchor.x, y: at.y - anchor.y };
|
|
5487
6514
|
pan.setValue(offset.current);
|
|
5488
|
-
}, [pan, placement, defaultSide, width, height,
|
|
6515
|
+
}, [pan, placement, defaultSide, width, height, size2, defaultBottom, top, bottom, left, right]);
|
|
5489
6516
|
const responder = useMemo3(() => {
|
|
5490
6517
|
const moved = (g) => Math.hypot(g.dx, g.dy) > LAUNCHER_DRAG_THRESHOLD;
|
|
5491
6518
|
const anchor = () => placementPoint(live.current.frame, { side: live.current.defaultSide, fraction: 0 });
|
|
@@ -6009,18 +7036,18 @@ import { AccessibilityInfo as AccessibilityInfo2, Animated as Animated2, Easing
|
|
|
6009
7036
|
var MARGIN = 12;
|
|
6010
7037
|
var ARROW_INSET = 18;
|
|
6011
7038
|
var opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
6012
|
-
function positionCard(target,
|
|
7039
|
+
function positionCard(target, size2, viewport, insets, placement, offset) {
|
|
6013
7040
|
const [rawSide, align] = placement.split("-");
|
|
6014
7041
|
const fits = (side2) => {
|
|
6015
7042
|
switch (side2) {
|
|
6016
7043
|
case "bottom":
|
|
6017
|
-
return target.y + target.height + offset +
|
|
7044
|
+
return target.y + target.height + offset + size2.height <= viewport.height - insets.bottom - MARGIN;
|
|
6018
7045
|
case "top":
|
|
6019
|
-
return target.y - offset -
|
|
7046
|
+
return target.y - offset - size2.height >= insets.top + MARGIN;
|
|
6020
7047
|
case "right":
|
|
6021
|
-
return target.x + target.width + offset +
|
|
7048
|
+
return target.x + target.width + offset + size2.width <= viewport.width - insets.right - MARGIN;
|
|
6022
7049
|
case "left":
|
|
6023
|
-
return target.x - offset -
|
|
7050
|
+
return target.x - offset - size2.width >= insets.left + MARGIN;
|
|
6024
7051
|
}
|
|
6025
7052
|
};
|
|
6026
7053
|
const order = rawSide === "auto" ? ["bottom", "top"] : [rawSide, opposite[rawSide], "bottom", "top"];
|
|
@@ -6029,17 +7056,17 @@ function positionCard(target, size, viewport, insets, placement, offset) {
|
|
|
6029
7056
|
const clamp3 = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
6030
7057
|
if (side === "top" || side === "bottom") {
|
|
6031
7058
|
const cx = target.x + target.width / 2;
|
|
6032
|
-
let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width -
|
|
6033
|
-
x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN -
|
|
6034
|
-
let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset -
|
|
6035
|
-
y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN -
|
|
6036
|
-
return { x: x2, y: y2, side, arrow: clamp3(cx - x2, ARROW_INSET,
|
|
7059
|
+
let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width - size2.width : cx - size2.width / 2;
|
|
7060
|
+
x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN - size2.width));
|
|
7061
|
+
let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset - size2.height;
|
|
7062
|
+
y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size2.height));
|
|
7063
|
+
return { x: x2, y: y2, side, arrow: clamp3(cx - x2, ARROW_INSET, size2.width - ARROW_INSET) };
|
|
6037
7064
|
}
|
|
6038
7065
|
const cy = target.y + target.height / 2;
|
|
6039
|
-
let y = align === "start" ? target.y : align === "end" ? target.y + target.height -
|
|
6040
|
-
y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN -
|
|
6041
|
-
const x = side === "right" ? target.x + target.width + offset : target.x - offset -
|
|
6042
|
-
return { x, y, side, arrow: clamp3(cy - y, ARROW_INSET,
|
|
7066
|
+
let y = align === "start" ? target.y : align === "end" ? target.y + target.height - size2.height : cy - size2.height / 2;
|
|
7067
|
+
y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size2.height));
|
|
7068
|
+
const x = side === "right" ? target.x + target.width + offset : target.x - offset - size2.width;
|
|
7069
|
+
return { x, y, side, arrow: clamp3(cy - y, ARROW_INSET, size2.height - ARROW_INSET) };
|
|
6043
7070
|
}
|
|
6044
7071
|
|
|
6045
7072
|
// src/ui/StepCard.tsx
|
|
@@ -6193,9 +7220,9 @@ function StepLayer({ view, client, palette: p, geometry }) {
|
|
|
6193
7220
|
}
|
|
6194
7221
|
if (!rect) return null;
|
|
6195
7222
|
if (step.type === "hotspot") {
|
|
6196
|
-
const
|
|
7223
|
+
const size2 = step.hotspot?.size ?? 14;
|
|
6197
7224
|
return /* @__PURE__ */ jsxs6(View7, { pointerEvents: "box-none", style: StyleSheet7.absoluteFill, children: [
|
|
6198
|
-
/* @__PURE__ */ jsx8(Hotspot, { x: rect.x + rect.width -
|
|
7225
|
+
/* @__PURE__ */ jsx8(Hotspot, { x: rect.x + rect.width - size2 / 2, y: rect.y - size2 / 2, size: size2, pulse: step.hotspot?.pulse ?? true, color: p.primary, onPress: () => client.toggleHotspot(view.guide.id), label: step.title || "Show tip" }),
|
|
6199
7226
|
view.expanded ? /* @__PURE__ */ jsx8(Anchored, { view, client, palette: p, geometry, rect, width: cardWidth }) : null
|
|
6200
7227
|
] });
|
|
6201
7228
|
}
|
|
@@ -6242,7 +7269,7 @@ function Arrow({ side, at, color, border }) {
|
|
|
6242
7269
|
const place = side === "bottom" ? { top: -half, left: at - half } : side === "top" ? { bottom: -half, left: at - half } : side === "right" ? { left: -half, top: at - half } : { right: -half, top: at - half };
|
|
6243
7270
|
return /* @__PURE__ */ jsx8(View7, { pointerEvents: "none", style: [styles7.abs, styles7.arrow, place, { backgroundColor: color, borderColor: border }] });
|
|
6244
7271
|
}
|
|
6245
|
-
function Hotspot({ x, y, size, pulse, color, onPress, label }) {
|
|
7272
|
+
function Hotspot({ x, y, size: size2, pulse, color, onPress, label }) {
|
|
6246
7273
|
const scale = useRef3(new Animated2.Value(0)).current;
|
|
6247
7274
|
const [reduceMotion, setReduceMotion] = useState5(false);
|
|
6248
7275
|
useEffect5(() => {
|
|
@@ -6259,15 +7286,15 @@ function Hotspot({ x, y, size, pulse, color, onPress, label }) {
|
|
|
6259
7286
|
loop.start();
|
|
6260
7287
|
return () => loop.stop();
|
|
6261
7288
|
}, [pulse, reduceMotion, scale]);
|
|
6262
|
-
return /* @__PURE__ */ jsxs6(Pressable7, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width:
|
|
7289
|
+
return /* @__PURE__ */ jsxs6(Pressable7, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width: size2, height: size2 }], children: [
|
|
6263
7290
|
pulse && !reduceMotion ? /* @__PURE__ */ jsx8(
|
|
6264
7291
|
Animated2.View,
|
|
6265
7292
|
{
|
|
6266
7293
|
pointerEvents: "none",
|
|
6267
|
-
style: [styles7.abs, { width:
|
|
7294
|
+
style: [styles7.abs, { width: size2, height: size2, borderRadius: size2 / 2, backgroundColor: color, opacity: scale.interpolate({ inputRange: [0, 1], outputRange: [0.55, 0] }), transform: [{ scale: scale.interpolate({ inputRange: [0, 1], outputRange: [1, 2.6] }) }] }]
|
|
6268
7295
|
}
|
|
6269
7296
|
) : null,
|
|
6270
|
-
/* @__PURE__ */ jsx8(View7, { style: { width:
|
|
7297
|
+
/* @__PURE__ */ jsx8(View7, { style: { width: size2, height: size2, borderRadius: size2 / 2, backgroundColor: color, borderWidth: 2, borderColor: "#ffffff" } })
|
|
6271
7298
|
] });
|
|
6272
7299
|
}
|
|
6273
7300
|
var styles7 = StyleSheet7.create({
|
|
@@ -6888,12 +7915,16 @@ function defaultBridge(appVersion, appBuild) {
|
|
|
6888
7915
|
locale: deviceLocale(),
|
|
6889
7916
|
appVersion: appVersion ?? detected.appVersion,
|
|
6890
7917
|
appBuild: appBuild ?? detected.appBuild,
|
|
6891
|
-
osVersion: detectOsVersion(sources)
|
|
7918
|
+
osVersion: detectOsVersion(sources),
|
|
7919
|
+
...detectDeviceModel(sources),
|
|
7920
|
+
reactNativeVersion: detectReactNativeVersion(sources)
|
|
6892
7921
|
},
|
|
6893
7922
|
openExternal: (url) => void Linking.openURL(url).catch(() => {
|
|
6894
7923
|
}),
|
|
6895
7924
|
windowSize: () => Dimensions.get("window"),
|
|
6896
|
-
capturer: nativeCapturer()
|
|
7925
|
+
capturer: nativeCapturer(),
|
|
7926
|
+
// ErrorUtils, HermesInternal, console, fetch, XMLHttpRequest: crash capture and breadcrumbs.
|
|
7927
|
+
globals: globalThis
|
|
6897
7928
|
};
|
|
6898
7929
|
}
|
|
6899
7930
|
function nativeCapturer() {
|
|
@@ -6901,6 +7932,20 @@ function nativeCapturer() {
|
|
|
6901
7932
|
if (!mod || typeof mod.capture !== "function") return void 0;
|
|
6902
7933
|
return (options) => mod.capture(options);
|
|
6903
7934
|
}
|
|
7935
|
+
function latestErrorOptions(initial, latest) {
|
|
7936
|
+
if (!initial) return void 0;
|
|
7937
|
+
return {
|
|
7938
|
+
...initial,
|
|
7939
|
+
beforeSend: (event) => {
|
|
7940
|
+
const hook = latest()?.beforeSend;
|
|
7941
|
+
return hook ? hook(event) : event;
|
|
7942
|
+
},
|
|
7943
|
+
beforeBreadcrumb: (crumb, hint) => {
|
|
7944
|
+
const hook = latest()?.beforeBreadcrumb;
|
|
7945
|
+
return hook ? hook(crumb, hint) : crumb;
|
|
7946
|
+
}
|
|
7947
|
+
};
|
|
7948
|
+
}
|
|
6904
7949
|
function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
|
|
6905
7950
|
const [client, setClient] = useState8(null);
|
|
6906
7951
|
const optionsRef = useRef7(options);
|
|
@@ -6931,7 +7976,8 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
6931
7976
|
user: userRef.current?.id ? userRef.current : void 0,
|
|
6932
7977
|
flags: latest.flags === false ? false : { ...latest.flags ?? {}, payload: payloadRef.current ?? void 0, attributes: appAttributesRef.current },
|
|
6933
7978
|
onNavigate: (url) => optionsRef.current.onNavigate?.(url),
|
|
6934
|
-
onError: (err) => optionsRef.current.onError?.(err)
|
|
7979
|
+
onError: (err) => optionsRef.current.onError?.(err),
|
|
7980
|
+
errors: latestErrorOptions(latest.errors, () => optionsRef.current.errors)
|
|
6935
7981
|
},
|
|
6936
7982
|
defaultBridge(latest.appVersion, latest.appBuild)
|
|
6937
7983
|
);
|
|
@@ -6994,6 +8040,8 @@ var NOOP_RESULTS = {
|
|
|
6994
8040
|
isRecordingTouches: false,
|
|
6995
8041
|
getSessionId: "",
|
|
6996
8042
|
getScreen: "",
|
|
8043
|
+
captureException: "",
|
|
8044
|
+
captureMessage: "",
|
|
6997
8045
|
on: () => {
|
|
6998
8046
|
},
|
|
6999
8047
|
subscribeUi: () => () => {
|
|
@@ -7112,6 +8160,7 @@ function useActiveGuide() {
|
|
|
7112
8160
|
}
|
|
7113
8161
|
export {
|
|
7114
8162
|
DEFAULT_API_URL,
|
|
8163
|
+
FATAL_FLUSH_MS,
|
|
7115
8164
|
Feature,
|
|
7116
8165
|
FeatureFlag,
|
|
7117
8166
|
FeatureValue,
|
|
@@ -7136,11 +8185,15 @@ export {
|
|
|
7136
8185
|
createFlagsClient3 as createFlagsClient,
|
|
7137
8186
|
createSseParser,
|
|
7138
8187
|
detectAppVersion,
|
|
8188
|
+
detectDeviceModel,
|
|
7139
8189
|
detectOsVersion,
|
|
8190
|
+
detectReactNativeVersion,
|
|
7140
8191
|
guidelyScreenLayout,
|
|
7141
8192
|
htmlToText,
|
|
7142
8193
|
noopClient,
|
|
8194
|
+
normalizeBundleFile,
|
|
7143
8195
|
parseHtml,
|
|
8196
|
+
parseReactNativeStack,
|
|
7144
8197
|
positionCard,
|
|
7145
8198
|
targetIdsFor,
|
|
7146
8199
|
useActiveGuide,
|