@guidelytech/sdk-core 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/{chunk-2E2YUQAG.js → chunk-GFLVZRTS.js} +28 -3
- package/dist/chunk-GFLVZRTS.js.map +1 -0
- package/dist/{types-Bkw8ErmO.d.cts → environment-D63OzdWf.d.cts} +172 -5
- package/dist/{types-Dy_uPu6S.d.ts → environment-DAT4iYvZ.d.ts} +172 -5
- package/dist/headless.cjs +27 -1
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +3 -1
- package/dist/index.cjs +1155 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -10
- package/dist/index.d.ts +71 -10
- package/dist/index.js +1089 -72
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/dist/capture-CgN69Lxq.d.cts +0 -100
- package/dist/capture-Csz721-A.d.ts +0 -100
- package/dist/chunk-2E2YUQAG.js.map +0 -1
- package/dist/chunk-JUSM7J5N.js +0 -376
- package/dist/chunk-JUSM7J5N.js.map +0 -1
- package/dist/chunk-ZAF2KRJA.js +0 -334
- package/dist/chunk-ZAF2KRJA.js.map +0 -1
- package/dist/health-sbP9JwTE.d.cts +0 -90
- package/dist/health-sbP9JwTE.d.ts +0 -90
- package/dist/http-Cb6KYYFv.d.cts +0 -61
- package/dist/http-D047Ngxk.d.ts +0 -61
- package/dist/types-BDlebXrm.d.ts +0 -314
- package/dist/types-Mtlmlj5C.d.cts +0 -314
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
|
51
51
|
createStorage: () => createStorage,
|
|
52
52
|
default: () => index_default,
|
|
53
53
|
detectDevice: () => detectDevice,
|
|
54
|
+
environmentFromKey: () => environmentFromKey,
|
|
54
55
|
isAppUrl: () => isAppUrl,
|
|
55
56
|
isExclusiveGuide: () => isExclusiveGuide,
|
|
56
57
|
isFrequencyEligible: () => isFrequencyEligible,
|
|
@@ -64,7 +65,7 @@ __export(index_exports, {
|
|
|
64
65
|
module.exports = __toCommonJS(index_exports);
|
|
65
66
|
|
|
66
67
|
// src/client.ts
|
|
67
|
-
var
|
|
68
|
+
var import_runtime23 = require("@guidelytech/shared/runtime");
|
|
68
69
|
var import_guide_runtime5 = require("@guidelytech/guide-runtime");
|
|
69
70
|
var import_guide_runtime6 = require("@guidelytech/guide-runtime");
|
|
70
71
|
var import_flags = require("@guidelytech/flags");
|
|
@@ -103,14 +104,14 @@ var ServerHealth = class {
|
|
|
103
104
|
/** The request failed or the server is overloaded. `retryAfterMs` comes from a `Retry-After` header. */
|
|
104
105
|
failure(retryAfterMs) {
|
|
105
106
|
this.failures += 1;
|
|
106
|
-
const
|
|
107
|
+
const now3 = this.now();
|
|
107
108
|
if (retryAfterMs != null && retryAfterMs > 0) {
|
|
108
|
-
this.openUntil = Math.max(this.openUntil,
|
|
109
|
+
this.openUntil = Math.max(this.openUntil, now3 + Math.min(retryAfterMs, MAX_RETRY_AFTER_MS));
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
112
|
if (this.failures < this.threshold) return;
|
|
112
113
|
const cooldown = Math.min(this.maxMs, this.baseMs * 2 ** (this.failures - this.threshold));
|
|
113
|
-
this.openUntil = Math.max(this.openUntil,
|
|
114
|
+
this.openUntil = Math.max(this.openUntil, now3 + cooldown * (0.8 + this.random() * 0.4));
|
|
114
115
|
}
|
|
115
116
|
/** Milliseconds background traffic should wait before trying again (0: send now). */
|
|
116
117
|
waitMs() {
|
|
@@ -146,12 +147,12 @@ var ServerHealth = class {
|
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
};
|
|
149
|
-
function parseRetryAfter(value,
|
|
150
|
+
function parseRetryAfter(value, now3 = Date.now()) {
|
|
150
151
|
if (!value) return null;
|
|
151
152
|
const trimmed = value.trim();
|
|
152
153
|
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
153
154
|
const at = Date.parse(trimmed);
|
|
154
|
-
return Number.isNaN(at) ? null : Math.max(0, at -
|
|
155
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now3);
|
|
155
156
|
}
|
|
156
157
|
function isServerFailure(status) {
|
|
157
158
|
return status === 429 || status >= 500;
|
|
@@ -243,10 +244,10 @@ var Api = class {
|
|
|
243
244
|
if (!f) throw new Error("fetch is not available");
|
|
244
245
|
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
245
246
|
let timer = null;
|
|
246
|
-
const arm = (
|
|
247
|
+
const arm = (ms2) => {
|
|
247
248
|
if (!ac) return;
|
|
248
249
|
if (timer) clearTimeout(timer);
|
|
249
|
-
timer = setTimeout(() => ac.abort(),
|
|
250
|
+
timer = setTimeout(() => ac.abort(), ms2);
|
|
250
251
|
};
|
|
251
252
|
arm(STREAM_RESPONSE_TIMEOUT_MS);
|
|
252
253
|
try {
|
|
@@ -293,7 +294,7 @@ var Api = class {
|
|
|
293
294
|
}
|
|
294
295
|
}
|
|
295
296
|
};
|
|
296
|
-
var sleep = (
|
|
297
|
+
var sleep = (ms2) => new Promise((r) => setTimeout(r, ms2));
|
|
297
298
|
function backoffMs(attempt, baseMs = 1e3, maxMs = 3e4) {
|
|
298
299
|
return Math.min(maxMs, baseMs * 2 ** Math.max(0, attempt));
|
|
299
300
|
}
|
|
@@ -467,13 +468,13 @@ var Outbox = class {
|
|
|
467
468
|
persist() {
|
|
468
469
|
writeJson(this.opts.store, this.opts.storageKey, this.items);
|
|
469
470
|
}
|
|
470
|
-
retryIn(
|
|
471
|
+
retryIn(ms2) {
|
|
471
472
|
if (this.destroyed) return;
|
|
472
473
|
if (this.timer) clearTimeout(this.timer);
|
|
473
474
|
this.timer = setTimeout(() => {
|
|
474
475
|
this.timer = null;
|
|
475
476
|
void this.flush();
|
|
476
|
-
},
|
|
477
|
+
}, ms2);
|
|
477
478
|
}
|
|
478
479
|
/** Sends queued requests in order, one at a time. */
|
|
479
480
|
async flush() {
|
|
@@ -1046,10 +1047,10 @@ var AuthoringBridge = class {
|
|
|
1046
1047
|
this.observer.observe(document.documentElement, { subtree: true, childList: true, attributes: true, characterData: true });
|
|
1047
1048
|
}
|
|
1048
1049
|
scheduleSnapshot() {
|
|
1049
|
-
const
|
|
1050
|
-
if (!this.snapshotTimer) this.snapshotDue =
|
|
1050
|
+
const now3 = Date.now();
|
|
1051
|
+
if (!this.snapshotTimer) this.snapshotDue = now3 + SNAPSHOT_MAX_WAIT_MS;
|
|
1051
1052
|
else clearTimeout(this.snapshotTimer);
|
|
1052
|
-
this.snapshotTimer = setTimeout(() => this.sendSnapshot(), Math.max(0, Math.min(SNAPSHOT_DEBOUNCE_MS, this.snapshotDue -
|
|
1053
|
+
this.snapshotTimer = setTimeout(() => this.sendSnapshot(), Math.max(0, Math.min(SNAPSHOT_DEBOUNCE_MS, this.snapshotDue - now3)));
|
|
1053
1054
|
}
|
|
1054
1055
|
sendSnapshot() {
|
|
1055
1056
|
if (this.snapshotTimer) clearTimeout(this.snapshotTimer);
|
|
@@ -1354,12 +1355,12 @@ function describe(value) {
|
|
|
1354
1355
|
}
|
|
1355
1356
|
}
|
|
1356
1357
|
function exceptionFromError(error, source, handled, fallbackType = "Error") {
|
|
1357
|
-
const
|
|
1358
|
+
const isError2 = error instanceof Error || !!error && typeof error === "object" && typeof error.message === "string" && typeof error.stack === "string";
|
|
1358
1359
|
const e = error;
|
|
1359
1360
|
return {
|
|
1360
|
-
type:
|
|
1361
|
-
message: (
|
|
1362
|
-
frames:
|
|
1361
|
+
type: isError2 && e.name || fallbackType,
|
|
1362
|
+
message: (isError2 ? e.message : describe(error)).slice(0, 1e3),
|
|
1363
|
+
frames: isError2 ? fitFrames((0, import_runtime11.parseStack)(e.stack)) : [],
|
|
1363
1364
|
source,
|
|
1364
1365
|
handled
|
|
1365
1366
|
};
|
|
@@ -1409,6 +1410,222 @@ var ExceptionThrottle = class {
|
|
|
1409
1410
|
}
|
|
1410
1411
|
};
|
|
1411
1412
|
|
|
1413
|
+
// src/error-monitoring.ts
|
|
1414
|
+
var import_runtime13 = require("@guidelytech/shared/runtime");
|
|
1415
|
+
var CONTEXT_KEYS = ["tags", "extra", "level", "fingerprint", "contexts", "user", "mechanism"];
|
|
1416
|
+
function splitHint(hint) {
|
|
1417
|
+
const ctx = {};
|
|
1418
|
+
const properties = {};
|
|
1419
|
+
for (const [k, v] of Object.entries(hint ?? {})) (CONTEXT_KEYS.includes(k) ? ctx : properties)[k] = v;
|
|
1420
|
+
return { ctx, properties };
|
|
1421
|
+
}
|
|
1422
|
+
var now = () => Date.now() / 1e3;
|
|
1423
|
+
var ErrorScope = class {
|
|
1424
|
+
constructor(opts) {
|
|
1425
|
+
__publicField(this, "opts", opts);
|
|
1426
|
+
__publicField(this, "tags", {});
|
|
1427
|
+
__publicField(this, "extra", {});
|
|
1428
|
+
__publicField(this, "contexts", {});
|
|
1429
|
+
__publicField(this, "user", {});
|
|
1430
|
+
__publicField(this, "crumbs", []);
|
|
1431
|
+
}
|
|
1432
|
+
get max() {
|
|
1433
|
+
return Math.max(0, Math.min(import_runtime13.MAX_BREADCRUMBS, this.opts()?.maxBreadcrumbs ?? import_runtime13.MAX_BREADCRUMBS));
|
|
1434
|
+
}
|
|
1435
|
+
addBreadcrumb(crumb, hint) {
|
|
1436
|
+
let c = { timestamp: now(), ...crumb };
|
|
1437
|
+
const before = this.opts()?.beforeBreadcrumb;
|
|
1438
|
+
if (before) {
|
|
1439
|
+
try {
|
|
1440
|
+
c = before(c, hint);
|
|
1441
|
+
} catch {
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (!c || !this.max) return;
|
|
1445
|
+
this.crumbs.push(c);
|
|
1446
|
+
if (this.crumbs.length > this.max) this.crumbs.splice(0, this.crumbs.length - this.max);
|
|
1447
|
+
}
|
|
1448
|
+
breadcrumbs() {
|
|
1449
|
+
return this.crumbs.slice();
|
|
1450
|
+
}
|
|
1451
|
+
clear() {
|
|
1452
|
+
this.tags = {};
|
|
1453
|
+
this.extra = {};
|
|
1454
|
+
this.contexts = {};
|
|
1455
|
+
this.user = {};
|
|
1456
|
+
this.crumbs = [];
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
function toDetailFrames(frames) {
|
|
1460
|
+
const out = [];
|
|
1461
|
+
for (let i = frames.length - 1; i >= 0; i--) {
|
|
1462
|
+
const f = frames[i];
|
|
1463
|
+
out.push({ ...f.fn ? { function: f.fn } : {}, ...f.file ? { abs_path: f.file, filename: f.file } : {}, ...f.line ? { lineno: f.line } : {}, ...f.col ? { colno: f.col } : {}, ...typeof f.inApp === "boolean" ? { in_app: f.inApp } : {} });
|
|
1464
|
+
}
|
|
1465
|
+
return out;
|
|
1466
|
+
}
|
|
1467
|
+
var isError = (v) => v instanceof Error || !!v && typeof v === "object" && typeof v.message === "string" && typeof v.stack === "string";
|
|
1468
|
+
function exceptionChain(error, main, mechanism) {
|
|
1469
|
+
const chain = [{ type: main.type, value: main.message, mechanism: { type: mechanism, handled: main.handled !== false, exception_id: 0 }, stacktrace: { frames: toDetailFrames(main.frames) } }];
|
|
1470
|
+
const seen = /* @__PURE__ */ new Set([error]);
|
|
1471
|
+
let cur = isError(error) ? error.cause : void 0;
|
|
1472
|
+
while (cur !== void 0 && cur !== null && !seen.has(cur) && chain.length < import_runtime13.MAX_EXCEPTION_CHAIN) {
|
|
1473
|
+
seen.add(cur);
|
|
1474
|
+
const e = cur;
|
|
1475
|
+
const id = chain.length;
|
|
1476
|
+
chain.push({
|
|
1477
|
+
type: isError(e) && e.name || "Error",
|
|
1478
|
+
value: (isError(e) ? e.message : String(cur)).slice(0, 1e3),
|
|
1479
|
+
mechanism: { type: "chained", source: "cause", handled: true, exception_id: id, parent_id: id - 1 },
|
|
1480
|
+
...isError(e) ? { stacktrace: { frames: toDetailFrames((0, import_runtime13.parseStack)(e.stack, 50)) } } : {}
|
|
1481
|
+
});
|
|
1482
|
+
cur = isError(e) ? e.cause : void 0;
|
|
1483
|
+
}
|
|
1484
|
+
return chain.reverse();
|
|
1485
|
+
}
|
|
1486
|
+
var VERSION_TOKEN = { chrome: "(?:Chrome|CriOS)", edge: "Edg", firefox: "(?:Firefox|FxiOS)", safari: "Version", opera: "OPR", samsung: "SamsungBrowser", electron: "Electron" };
|
|
1487
|
+
var OS_VERSION = /(?:Mac OS X|Windows NT|Android|CPU (?:iPhone )?OS) ([\d._]+)/;
|
|
1488
|
+
var NAMES = { chrome: "Chrome", edge: "Edge", firefox: "Firefox", safari: "Safari", opera: "Opera", samsung: "Samsung Internet", electron: "Electron", ie: "Internet Explorer", macos: "macOS", windows: "Windows", ios: "iOS", android: "Android", linux: "Linux", chromeos: "Chrome OS" };
|
|
1489
|
+
function browserContexts(ua, device) {
|
|
1490
|
+
const out = {};
|
|
1491
|
+
const token = device.browser ? VERSION_TOKEN[device.browser] : void 0;
|
|
1492
|
+
const v = token ? new RegExp(`${token}/([\\d.]+)`).exec(ua)?.[1] : void 0;
|
|
1493
|
+
if (device.browser) out.browser = { name: NAMES[device.browser] ?? device.browser, ...v ? { version: v } : {} };
|
|
1494
|
+
const ov = OS_VERSION.exec(ua)?.[1]?.replace(/_/g, ".");
|
|
1495
|
+
if (device.os) out.os = { name: NAMES[device.os] ?? device.os, ...ov ? { version: ov } : {} };
|
|
1496
|
+
out.device = { family: device.type === "desktop" ? "Desktop" : device.type === "tablet" ? "Tablet" : "Mobile" };
|
|
1497
|
+
return out;
|
|
1498
|
+
}
|
|
1499
|
+
function buildDetail(input) {
|
|
1500
|
+
const { scope, ctx } = input;
|
|
1501
|
+
const contexts = { ...input.contexts, ...scope.contexts };
|
|
1502
|
+
for (const [k, v] of Object.entries(ctx.contexts ?? {})) if (v) contexts[k] = v;
|
|
1503
|
+
const user = { ...scope.user, ...ctx.user ?? {} };
|
|
1504
|
+
const detail = {
|
|
1505
|
+
level: input.level,
|
|
1506
|
+
exception: { values: input.props.type === "Message" ? [] : exceptionChain(input.error, input.props, input.mechanism) },
|
|
1507
|
+
breadcrumbs: { values: scope.breadcrumbs() },
|
|
1508
|
+
contexts,
|
|
1509
|
+
tags: (0, import_runtime13.normalizeTags)({ ...scope.tags, ...ctx.tags ?? {} }),
|
|
1510
|
+
extra: { ...scope.extra, ...ctx.extra ?? {} },
|
|
1511
|
+
...Object.keys(user).length ? { user } : {},
|
|
1512
|
+
...ctx.fingerprint?.length ? { fingerprint: ctx.fingerprint.map(String) } : {}
|
|
1513
|
+
};
|
|
1514
|
+
return (0, import_runtime13.trimExceptionDetail)(detail);
|
|
1515
|
+
}
|
|
1516
|
+
var isOwnRequest = (url, apiUrl) => !!apiUrl && url.startsWith(apiUrl) && /\/api\/v1\/sdk\//.test(url);
|
|
1517
|
+
function breadcrumbUrl(raw) {
|
|
1518
|
+
try {
|
|
1519
|
+
const u = new URL(raw, typeof location !== "undefined" ? location.href : "http://localhost/");
|
|
1520
|
+
return (u.origin === "null" ? u.protocol : u.origin) + u.pathname;
|
|
1521
|
+
} catch {
|
|
1522
|
+
return raw.replace(/[?#].*$/, "");
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
function describeArg(a) {
|
|
1526
|
+
if (typeof a === "string") return a;
|
|
1527
|
+
if (a instanceof Error) return `${a.name}: ${a.message}`;
|
|
1528
|
+
try {
|
|
1529
|
+
return JSON.stringify(a) ?? String(a);
|
|
1530
|
+
} catch {
|
|
1531
|
+
return String(a);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
var XHR_INFO = "__guidelyCrumb";
|
|
1535
|
+
function instrumentBreadcrumbs(add, opts) {
|
|
1536
|
+
let on = true;
|
|
1537
|
+
const undo = [];
|
|
1538
|
+
const g = globalThis;
|
|
1539
|
+
if (opts.console !== false && g.console) {
|
|
1540
|
+
const levels = opts.console === "all" ? ["log", "info", "warn", "error", "debug"] : ["warn", "error"];
|
|
1541
|
+
for (const level of levels) {
|
|
1542
|
+
const original = g.console[level];
|
|
1543
|
+
if (typeof original !== "function") continue;
|
|
1544
|
+
const wrapped = function(...args) {
|
|
1545
|
+
if (on) {
|
|
1546
|
+
try {
|
|
1547
|
+
const message = args.map(describeArg).join(" ").slice(0, 1e3);
|
|
1548
|
+
if (!message.startsWith("[Guidely]")) add({ category: "console", level: level === "warn" ? "warning" : level === "log" ? "info" : level, message, data: { logger: "console" } }, { event: args });
|
|
1549
|
+
} catch {
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
return original.apply(this, args);
|
|
1553
|
+
};
|
|
1554
|
+
g.console[level] = wrapped;
|
|
1555
|
+
undo.push(() => {
|
|
1556
|
+
if (g.console[level] === wrapped) g.console[level] = original;
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
if (opts.http !== false && typeof g.fetch === "function") {
|
|
1561
|
+
const original = g.fetch;
|
|
1562
|
+
const wrapped = function(input, init) {
|
|
1563
|
+
const started = Date.now();
|
|
1564
|
+
let url = "";
|
|
1565
|
+
let method = "GET";
|
|
1566
|
+
try {
|
|
1567
|
+
url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1568
|
+
method = (init?.method ?? (typeof input === "object" && "method" in input ? input.method : "GET")).toUpperCase();
|
|
1569
|
+
} catch {
|
|
1570
|
+
}
|
|
1571
|
+
const p = original.call(this, input, init);
|
|
1572
|
+
if (on && url && !isOwnRequest(url, opts.apiUrl)) {
|
|
1573
|
+
const crumb = (status) => {
|
|
1574
|
+
try {
|
|
1575
|
+
add({ type: "http", category: "fetch", level: status === void 0 || status >= 500 ? "error" : status >= 400 ? "warning" : "info", data: { method, url: breadcrumbUrl(url), ...status !== void 0 ? { status_code: status } : {}, duration_ms: Date.now() - started } });
|
|
1576
|
+
} catch {
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
p.then(
|
|
1580
|
+
(res) => crumb(res.status),
|
|
1581
|
+
() => crumb(void 0)
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
return p;
|
|
1585
|
+
};
|
|
1586
|
+
g.fetch = wrapped;
|
|
1587
|
+
undo.push(() => {
|
|
1588
|
+
if (g.fetch === wrapped) g.fetch = original;
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
const proto = opts.http !== false ? g.XMLHttpRequest?.prototype : void 0;
|
|
1592
|
+
if (proto) {
|
|
1593
|
+
const open = proto.open;
|
|
1594
|
+
const send = proto.send;
|
|
1595
|
+
const wrappedOpen = function(method, url, ...rest) {
|
|
1596
|
+
try {
|
|
1597
|
+
this[XHR_INFO] = { method: String(method).toUpperCase(), url: String(url) };
|
|
1598
|
+
} catch {
|
|
1599
|
+
}
|
|
1600
|
+
return open.call(this, method, url, ...rest);
|
|
1601
|
+
};
|
|
1602
|
+
const wrappedSend = function(body) {
|
|
1603
|
+
const info = this[XHR_INFO];
|
|
1604
|
+
if (on && info && !isOwnRequest(info.url, opts.apiUrl)) {
|
|
1605
|
+
const started = Date.now();
|
|
1606
|
+
this.addEventListener("loadend", () => {
|
|
1607
|
+
try {
|
|
1608
|
+
const status = this.status || void 0;
|
|
1609
|
+
add({ type: "http", category: "xhr", level: !status || status >= 500 ? "error" : status >= 400 ? "warning" : "info", data: { method: info.method, url: breadcrumbUrl(info.url), ...status ? { status_code: status } : {}, duration_ms: Date.now() - started } }, { event: this });
|
|
1610
|
+
} catch {
|
|
1611
|
+
}
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
return send.call(this, body);
|
|
1615
|
+
};
|
|
1616
|
+
proto.open = wrappedOpen;
|
|
1617
|
+
proto.send = wrappedSend;
|
|
1618
|
+
undo.push(() => {
|
|
1619
|
+
if (proto.open === wrappedOpen) proto.open = open;
|
|
1620
|
+
if (proto.send === wrappedSend) proto.send = send;
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
return () => {
|
|
1624
|
+
on = false;
|
|
1625
|
+
for (const u of undo) u();
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1412
1629
|
// src/emitter.ts
|
|
1413
1630
|
var Emitter = class {
|
|
1414
1631
|
constructor(onError) {
|
|
@@ -1445,13 +1662,13 @@ var Emitter = class {
|
|
|
1445
1662
|
|
|
1446
1663
|
// src/frequency.ts
|
|
1447
1664
|
var HOUR = 3600 * 1e3;
|
|
1448
|
-
function isFrequencyEligible(freq, state, shownThisSession,
|
|
1665
|
+
function isFrequencyEligible(freq, state, shownThisSession, now3 = Date.now()) {
|
|
1449
1666
|
const s = state ?? { impressions: 0 };
|
|
1450
1667
|
if (freq.maxImpressions != null && s.impressions >= freq.maxImpressions) return false;
|
|
1451
1668
|
if (s.completedAt && freq.mode !== "every_time") return false;
|
|
1452
1669
|
if (s.dismissedAt) {
|
|
1453
1670
|
if (!freq.reshowAfterDismiss) return false;
|
|
1454
|
-
if (freq.cooldownHours > 0 &&
|
|
1671
|
+
if (freq.cooldownHours > 0 && now3 - Date.parse(s.dismissedAt) < freq.cooldownHours * HOUR) return false;
|
|
1455
1672
|
}
|
|
1456
1673
|
switch (freq.mode) {
|
|
1457
1674
|
case "once":
|
|
@@ -1461,7 +1678,7 @@ function isFrequencyEligible(freq, state, shownThisSession, now = Date.now()) {
|
|
|
1461
1678
|
case "interval": {
|
|
1462
1679
|
if (!s.lastShownAt) return true;
|
|
1463
1680
|
const hours = freq.intervalHours ?? 24;
|
|
1464
|
-
return
|
|
1681
|
+
return now3 - Date.parse(s.lastShownAt) >= hours * HOUR;
|
|
1465
1682
|
}
|
|
1466
1683
|
case "every_time":
|
|
1467
1684
|
default:
|
|
@@ -1479,11 +1696,11 @@ function areMutuallyExclusive(a, b) {
|
|
|
1479
1696
|
const lb = b.config.frequency.mutuallyExclusiveWith;
|
|
1480
1697
|
return la.includes(b.id) || la.includes(b.key) || lb.includes(a.id) || lb.includes(a.key);
|
|
1481
1698
|
}
|
|
1482
|
-
function isWithinSchedule(guide,
|
|
1699
|
+
function isWithinSchedule(guide, now3 = Date.now()) {
|
|
1483
1700
|
for (const w of [guide.config.schedule, guide.config.survey]) {
|
|
1484
1701
|
if (!w) continue;
|
|
1485
|
-
if (w.startAt &&
|
|
1486
|
-
if (w.endAt &&
|
|
1702
|
+
if (w.startAt && now3 < Date.parse(w.startAt)) return false;
|
|
1703
|
+
if (w.endAt && now3 > Date.parse(w.endAt)) return false;
|
|
1487
1704
|
}
|
|
1488
1705
|
return true;
|
|
1489
1706
|
}
|
|
@@ -1491,19 +1708,19 @@ var MINUTE = 60 * 1e3;
|
|
|
1491
1708
|
function countsTowardThrottle(guide) {
|
|
1492
1709
|
return isExclusiveGuide(guide) && !guide.config.frequency.ignoreThrottling;
|
|
1493
1710
|
}
|
|
1494
|
-
function isThrottleEligible(settings, launches,
|
|
1711
|
+
function isThrottleEligible(settings, launches, now3 = Date.now()) {
|
|
1495
1712
|
if (!settings || !settings.enabled) return true;
|
|
1496
1713
|
const times = launches.map((l) => Date.parse(l)).filter((t) => !Number.isNaN(t));
|
|
1497
1714
|
const windowMs = Math.max(1, settings.windowHours) * HOUR;
|
|
1498
|
-
if (times.filter((t) =>
|
|
1499
|
-
if (settings.minGapMinutes > 0 && times.some((t) =>
|
|
1715
|
+
if (times.filter((t) => now3 - t < windowMs).length >= Math.max(1, settings.maxGuides)) return false;
|
|
1716
|
+
if (settings.minGapMinutes > 0 && times.some((t) => now3 - t < settings.minGapMinutes * MINUTE)) return false;
|
|
1500
1717
|
return true;
|
|
1501
1718
|
}
|
|
1502
|
-
function pruneLaunches(launches, settings,
|
|
1719
|
+
function pruneLaunches(launches, settings, now3 = Date.now()) {
|
|
1503
1720
|
const keepMs = Math.max(settings?.windowHours ?? 24, 1) * HOUR + (settings?.minGapMinutes ?? 0) * MINUTE;
|
|
1504
1721
|
return launches.filter((l) => {
|
|
1505
1722
|
const t = Date.parse(l);
|
|
1506
|
-
return !Number.isNaN(t) &&
|
|
1723
|
+
return !Number.isNaN(t) && now3 - t < keepMs;
|
|
1507
1724
|
}).slice(-100);
|
|
1508
1725
|
}
|
|
1509
1726
|
function dismissedAfterMerge(local, server) {
|
|
@@ -1674,7 +1891,7 @@ function createMessengerTransport(opts) {
|
|
|
1674
1891
|
}
|
|
1675
1892
|
|
|
1676
1893
|
// src/messenger-visitor.ts
|
|
1677
|
-
var
|
|
1894
|
+
var import_runtime17 = require("@guidelytech/shared/runtime");
|
|
1678
1895
|
var import_guide_runtime4 = require("@guidelytech/guide-runtime");
|
|
1679
1896
|
var PAGE_GUIDE_KINDS = /* @__PURE__ */ new Set(["flow", "tooltip", "modal"]);
|
|
1680
1897
|
var CATCH_ALL = /* @__PURE__ */ new Set(["", "*", "**", "/*", "/**", "/"]);
|
|
@@ -1699,7 +1916,7 @@ function plainText(html, max = 280) {
|
|
|
1699
1916
|
return text.length > max ? `${text.slice(0, max - 1).trimEnd()}\u2026` : text;
|
|
1700
1917
|
}
|
|
1701
1918
|
function buildMessengerVisitor(input) {
|
|
1702
|
-
const
|
|
1919
|
+
const now3 = input.now ?? Date.now();
|
|
1703
1920
|
const { guides, state } = input;
|
|
1704
1921
|
const done = (g) => !!state.guides[g.id]?.completedAt;
|
|
1705
1922
|
const pageGuides = guides.filter((g) => PAGE_GUIDE_KINDS.has(g.kind) && g.config.steps.length > 0 && !done(g) && targetsPage(g, input.url) && input.isTargeted(g)).slice(0, 3).map((g) => ({ id: g.id, key: g.key, name: g.name, kind: g.kind }));
|
|
@@ -1709,7 +1926,7 @@ function buildMessengerVisitor(input) {
|
|
|
1709
1926
|
const s = state.guides[g.id];
|
|
1710
1927
|
if (g.kind !== "flow" || !s?.lastStepId || s.completedAt || g.config.steps.length < 2) continue;
|
|
1711
1928
|
const at = Date.parse(s.lastShownAt ?? "");
|
|
1712
|
-
if (!Number.isFinite(at) ||
|
|
1929
|
+
if (!Number.isFinite(at) || now3 - at > RESUME_WITHIN_MS || at <= resumeAt) continue;
|
|
1713
1930
|
const stepIndex = g.config.steps.findIndex((st) => st.id === s.lastStepId);
|
|
1714
1931
|
if (stepIndex < 1 || !isWithinSchedule(g)) continue;
|
|
1715
1932
|
resumeGuide = { id: g.id, name: g.name, stepId: s.lastStepId, stepIndex, stepCount: g.config.steps.length };
|
|
@@ -1728,20 +1945,20 @@ function buildMessengerVisitor(input) {
|
|
|
1728
1945
|
break;
|
|
1729
1946
|
}
|
|
1730
1947
|
const seen = new Set(input.newsSeen);
|
|
1731
|
-
const news = guides.filter((g) => g.kind === "announcement" && g.config.steps.length > 0 &&
|
|
1948
|
+
const news = guides.filter((g) => g.kind === "announcement" && g.config.steps.length > 0 && now3 - Date.parse(g.publishedAt) <= NEWS_WITHIN_MS && input.isAudienceTargeted(g)).sort((a, b) => b.publishedAt.localeCompare(a.publishedAt)).slice(0, MAX_NEWS).map((g) => {
|
|
1732
1949
|
const step = g.config.steps[0];
|
|
1733
1950
|
return {
|
|
1734
1951
|
id: g.id,
|
|
1735
1952
|
key: g.key,
|
|
1736
1953
|
title: step.title || g.name,
|
|
1737
1954
|
body: plainText(step.body ?? ""),
|
|
1738
|
-
imageUrl: step.imageUrl || null,
|
|
1955
|
+
imageUrl: step.imageUrl || (0, import_runtime17.videoThumbnailUrl)(step.videoUrl) || null,
|
|
1739
1956
|
publishedAt: g.publishedAt,
|
|
1740
1957
|
// Read once opened in the News tab, or once the announcement itself was shown.
|
|
1741
1958
|
unread: !seen.has(g.id) && !(state.guides[g.id]?.impressions ?? 0)
|
|
1742
1959
|
};
|
|
1743
1960
|
});
|
|
1744
|
-
return { firstName: (0,
|
|
1961
|
+
return { firstName: (0, import_runtime17.firstNameFromTraits)(input.traits), path: pathOf(input.url), pageGuides, resumeGuide, checklist, news };
|
|
1745
1962
|
}
|
|
1746
1963
|
|
|
1747
1964
|
// src/navigation.ts
|
|
@@ -1858,19 +2075,19 @@ var PageTracker = class {
|
|
|
1858
2075
|
__publicField(this, "maxScroll", 0);
|
|
1859
2076
|
}
|
|
1860
2077
|
/** Starts measuring a new page. */
|
|
1861
|
-
start(url,
|
|
2078
|
+
start(url, now3 = Date.now()) {
|
|
1862
2079
|
this.url = url;
|
|
1863
2080
|
this.visibleMs = 0;
|
|
1864
|
-
this.visibleSince = typeof document !== "undefined" && document.visibilityState === "hidden" ? null :
|
|
2081
|
+
this.visibleSince = typeof document !== "undefined" && document.visibilityState === "hidden" ? null : now3;
|
|
1865
2082
|
this.maxScroll = 0;
|
|
1866
2083
|
this.measureScroll();
|
|
1867
2084
|
}
|
|
1868
2085
|
/** Pauses / resumes the visible-time clock. */
|
|
1869
|
-
setHidden(hidden,
|
|
2086
|
+
setHidden(hidden, now3 = Date.now()) {
|
|
1870
2087
|
if (hidden && this.visibleSince !== null) {
|
|
1871
|
-
this.visibleMs += Math.max(0,
|
|
2088
|
+
this.visibleMs += Math.max(0, now3 - this.visibleSince);
|
|
1872
2089
|
this.visibleSince = null;
|
|
1873
|
-
} else if (!hidden && this.visibleSince === null) this.visibleSince =
|
|
2090
|
+
} else if (!hidden && this.visibleSince === null) this.visibleSince = now3;
|
|
1874
2091
|
}
|
|
1875
2092
|
measureScroll() {
|
|
1876
2093
|
try {
|
|
@@ -1879,9 +2096,9 @@ var PageTracker = class {
|
|
|
1879
2096
|
}
|
|
1880
2097
|
}
|
|
1881
2098
|
/** Final numbers for the current page. */
|
|
1882
|
-
leave(
|
|
2099
|
+
leave(now3 = Date.now()) {
|
|
1883
2100
|
this.measureScroll();
|
|
1884
|
-
const running = this.visibleSince !== null ? Math.max(0,
|
|
2101
|
+
const running = this.visibleSince !== null ? Math.max(0, now3 - this.visibleSince) : 0;
|
|
1885
2102
|
return { url: this.url, durationMs: Math.round(this.visibleMs + running), scrollDepth: this.maxScroll };
|
|
1886
2103
|
}
|
|
1887
2104
|
};
|
|
@@ -1987,6 +2204,21 @@ function openPrototypeOverlay(testUrl, opts) {
|
|
|
1987
2204
|
}
|
|
1988
2205
|
|
|
1989
2206
|
// src/queue.ts
|
|
2207
|
+
var MAX_PERSISTED_QUEUE_CHARS = 1024 * 1024;
|
|
2208
|
+
function fitPersisted(events) {
|
|
2209
|
+
let size = JSON.stringify(events).length;
|
|
2210
|
+
if (size <= MAX_PERSISTED_QUEUE_CHARS) return events;
|
|
2211
|
+
const out = events.slice();
|
|
2212
|
+
for (let i = 0; i < out.length && size > MAX_PERSISTED_QUEUE_CHARS; i++) {
|
|
2213
|
+
const e = out[i];
|
|
2214
|
+
if (!e.detail) continue;
|
|
2215
|
+
size -= JSON.stringify(e.detail).length;
|
|
2216
|
+
const rest = { ...e };
|
|
2217
|
+
delete rest.detail;
|
|
2218
|
+
out[i] = rest;
|
|
2219
|
+
}
|
|
2220
|
+
return out;
|
|
2221
|
+
}
|
|
1990
2222
|
var EventQueue = class {
|
|
1991
2223
|
constructor(opts) {
|
|
1992
2224
|
__publicField(this, "opts", opts);
|
|
@@ -2019,12 +2251,12 @@ var EventQueue = class {
|
|
|
2019
2251
|
this.attempt = 0;
|
|
2020
2252
|
void this.flush();
|
|
2021
2253
|
}
|
|
2022
|
-
retryIn(
|
|
2254
|
+
retryIn(ms2) {
|
|
2023
2255
|
if (this.retryTimer || this.destroyed) return;
|
|
2024
2256
|
this.retryTimer = setTimeout(() => {
|
|
2025
2257
|
this.retryTimer = null;
|
|
2026
2258
|
void this.flush();
|
|
2027
|
-
},
|
|
2259
|
+
}, ms2);
|
|
2028
2260
|
}
|
|
2029
2261
|
get size() {
|
|
2030
2262
|
return this.events.length;
|
|
@@ -2042,7 +2274,7 @@ var EventQueue = class {
|
|
|
2042
2274
|
this.schedule();
|
|
2043
2275
|
}
|
|
2044
2276
|
persist() {
|
|
2045
|
-
writeJson(this.opts.store, this.opts.storageKey, this.events);
|
|
2277
|
+
writeJson(this.opts.store, this.opts.storageKey, fitPersisted(this.events));
|
|
2046
2278
|
}
|
|
2047
2279
|
schedule() {
|
|
2048
2280
|
if (this.timer || this.retryTimer || this.destroyed) return;
|
|
@@ -2116,17 +2348,631 @@ var EventQueue = class {
|
|
|
2116
2348
|
// src/load-recorder.ts
|
|
2117
2349
|
var loadRecorder = async () => (await import("./recorder.cjs")).recorder;
|
|
2118
2350
|
|
|
2351
|
+
// src/environment.ts
|
|
2352
|
+
function environmentFromKey(key) {
|
|
2353
|
+
const prefix = /^pk_([a-z0-9]+)_/.exec(key)?.[1];
|
|
2354
|
+
if (!prefix || prefix === "dev") return "development";
|
|
2355
|
+
if (prefix === "prod") return "production";
|
|
2356
|
+
if (prefix === "stg" || prefix === "stag") return "staging";
|
|
2357
|
+
return prefix;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// src/trace-propagation.ts
|
|
2361
|
+
function hex(bytes) {
|
|
2362
|
+
const a = new Uint8Array(bytes);
|
|
2363
|
+
try {
|
|
2364
|
+
crypto.getRandomValues(a);
|
|
2365
|
+
} catch {
|
|
2366
|
+
for (let i = 0; i < bytes; i++) a[i] = Math.floor(Math.random() * 256);
|
|
2367
|
+
}
|
|
2368
|
+
let out = "";
|
|
2369
|
+
for (const b of a) out += (b < 16 ? "0" : "") + b.toString(16);
|
|
2370
|
+
return out;
|
|
2371
|
+
}
|
|
2372
|
+
function stripUrl(url) {
|
|
2373
|
+
return url.split(/[?#]/)[0];
|
|
2374
|
+
}
|
|
2375
|
+
function resolveUrl(url) {
|
|
2376
|
+
try {
|
|
2377
|
+
return new URL(url, location.href).href;
|
|
2378
|
+
} catch {
|
|
2379
|
+
return url;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
function shouldPropagate(url, targets) {
|
|
2383
|
+
const abs = resolveUrl(url);
|
|
2384
|
+
if (!targets) {
|
|
2385
|
+
try {
|
|
2386
|
+
return new URL(abs).origin === location.origin;
|
|
2387
|
+
} catch {
|
|
2388
|
+
return false;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
return targets.some((t) => {
|
|
2392
|
+
if (typeof t === "string") return url.includes(t) || abs.includes(t);
|
|
2393
|
+
t.lastIndex = 0;
|
|
2394
|
+
if (t.test(url)) return true;
|
|
2395
|
+
t.lastIndex = 0;
|
|
2396
|
+
return t.test(abs);
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
function mergeBaggage(existing, ours) {
|
|
2400
|
+
const kept = (existing || "").split(",").map((s) => s.trim()).filter((s) => s && !s.startsWith("sentry-"));
|
|
2401
|
+
return [...kept, ours].join(",");
|
|
2402
|
+
}
|
|
2403
|
+
function traceHeaders(dsc, spanId) {
|
|
2404
|
+
const flag = dsc.sampled ? "1" : "0";
|
|
2405
|
+
const e = encodeURIComponent;
|
|
2406
|
+
const baggage = [`sentry-trace_id=${dsc.traceId}`, `sentry-public_key=${e(dsc.publicKey)}`, dsc.release ? `sentry-release=${e(dsc.release)}` : "", dsc.environment ? `sentry-environment=${e(dsc.environment)}` : "", `sentry-sampled=${dsc.sampled}`, `sentry-sample_rate=${dsc.sampleRate}`].filter(Boolean).join(",");
|
|
2407
|
+
return { "sentry-trace": `${dsc.traceId}-${spanId}-${flag}`, traceparent: `00-${dsc.traceId}-${spanId}-0${flag}`, baggage };
|
|
2408
|
+
}
|
|
2409
|
+
function httpSpanStatus(code) {
|
|
2410
|
+
if (code >= 100 && code < 400) return "ok";
|
|
2411
|
+
if (code >= 400 && code < 500) return { 401: "unauthenticated", 403: "permission_denied", 404: "not_found", 409: "already_exists", 413: "failed_precondition", 429: "resource_exhausted", 499: "cancelled" }[code] ?? "invalid_argument";
|
|
2412
|
+
if (code >= 500 && code < 600) return { 501: "unimplemented", 503: "unavailable", 504: "deadline_exceeded" }[code] ?? "internal_error";
|
|
2413
|
+
return "unknown_error";
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// src/web-vitals.ts
|
|
2417
|
+
function observe(type, cb, extra = {}) {
|
|
2418
|
+
try {
|
|
2419
|
+
if (typeof PerformanceObserver === "undefined" || !PerformanceObserver.supportedEntryTypes?.includes(type)) return null;
|
|
2420
|
+
const po = new PerformanceObserver((list) => cb(list.getEntries()));
|
|
2421
|
+
po.observe({ type, buffered: true, ...extra });
|
|
2422
|
+
return { take: () => cb(po.takeRecords()), stop: () => po.disconnect() };
|
|
2423
|
+
} catch {
|
|
2424
|
+
return null;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
var newCls = () => ({ value: 0, session: 0, first: 0, last: 0 });
|
|
2428
|
+
function addLayoutShift(s, e) {
|
|
2429
|
+
if (e.hadRecentInput) return;
|
|
2430
|
+
if (s.session && e.startTime - s.last < 1e3 && e.startTime - s.first < 5e3) s.session += e.value;
|
|
2431
|
+
else {
|
|
2432
|
+
s.session = e.value;
|
|
2433
|
+
s.first = e.startTime;
|
|
2434
|
+
}
|
|
2435
|
+
s.last = e.startTime;
|
|
2436
|
+
if (s.session > s.value) s.value = s.session;
|
|
2437
|
+
}
|
|
2438
|
+
var newInp = () => ({ longest: [], ids: /* @__PURE__ */ new Set() });
|
|
2439
|
+
function addInteraction(s, e) {
|
|
2440
|
+
const id = e.interactionId;
|
|
2441
|
+
if (!id) return;
|
|
2442
|
+
s.ids.add(id);
|
|
2443
|
+
const known = s.longest.find((i) => i.id === id);
|
|
2444
|
+
if (known) known.latency = Math.max(known.latency, e.duration);
|
|
2445
|
+
else if (s.longest.length < 10 || e.duration > s.longest[s.longest.length - 1].latency) s.longest.push({ id, latency: e.duration });
|
|
2446
|
+
else return;
|
|
2447
|
+
s.longest.sort((a, b) => b.latency - a.latency);
|
|
2448
|
+
s.longest.splice(10);
|
|
2449
|
+
}
|
|
2450
|
+
function inpValue(s, count = s.ids.size) {
|
|
2451
|
+
if (!s.longest.length) return void 0;
|
|
2452
|
+
return s.longest[Math.min(s.longest.length - 1, Math.floor(count / 50))].latency;
|
|
2453
|
+
}
|
|
2454
|
+
var ms = (value) => ({ value, unit: "millisecond" });
|
|
2455
|
+
var WebVitals = class {
|
|
2456
|
+
constructor(nav) {
|
|
2457
|
+
__publicField(this, "lcp");
|
|
2458
|
+
__publicField(this, "fcp");
|
|
2459
|
+
__publicField(this, "ttfb");
|
|
2460
|
+
__publicField(this, "cls", newCls());
|
|
2461
|
+
__publicField(this, "inp", newInp());
|
|
2462
|
+
__publicField(this, "observed", []);
|
|
2463
|
+
__publicField(this, "lcpFinal", false);
|
|
2464
|
+
__publicField(this, "hiddenAt");
|
|
2465
|
+
__publicField(this, "offs", []);
|
|
2466
|
+
const activation = nav?.activationStart || 0;
|
|
2467
|
+
const rel = (t) => Math.max(0, t - activation);
|
|
2468
|
+
this.hiddenAt = document.visibilityState === "hidden" ? 0 : Infinity;
|
|
2469
|
+
if (nav && nav.responseStart > 0) this.ttfb = rel(nav.responseStart);
|
|
2470
|
+
const add = (o) => o && this.observed.push(o);
|
|
2471
|
+
add(
|
|
2472
|
+
observe("paint", (es) => {
|
|
2473
|
+
for (const e of es) if (e.name === "first-contentful-paint" && e.startTime < this.hiddenAt) this.fcp = rel(e.startTime);
|
|
2474
|
+
})
|
|
2475
|
+
);
|
|
2476
|
+
add(
|
|
2477
|
+
observe("largest-contentful-paint", (es) => {
|
|
2478
|
+
const e = es[es.length - 1];
|
|
2479
|
+
if (e && !this.lcpFinal && e.startTime < this.hiddenAt) this.lcp = rel(e.startTime);
|
|
2480
|
+
})
|
|
2481
|
+
);
|
|
2482
|
+
add(observe("layout-shift", (es) => es.forEach((e) => addLayoutShift(this.cls, e))));
|
|
2483
|
+
const onEvent = (es) => es.forEach((e) => addInteraction(this.inp, e));
|
|
2484
|
+
add(observe("event", onEvent, { durationThreshold: 40 }));
|
|
2485
|
+
add(observe("first-input", onEvent));
|
|
2486
|
+
const freeze = () => {
|
|
2487
|
+
this.lcpFinal = true;
|
|
2488
|
+
this.offs.splice(0).forEach((f) => f());
|
|
2489
|
+
};
|
|
2490
|
+
for (const type of ["keydown", "pointerdown"]) {
|
|
2491
|
+
addEventListener(type, freeze, { once: true, capture: true });
|
|
2492
|
+
this.offs.push(() => removeEventListener(type, freeze, { capture: true }));
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
/** The page was hidden: deliver pending entries and freeze LCP. */
|
|
2496
|
+
hidden() {
|
|
2497
|
+
this.observed.forEach((o) => o.take());
|
|
2498
|
+
this.lcpFinal = true;
|
|
2499
|
+
if (this.hiddenAt === Infinity) this.hiddenAt = performance.now();
|
|
2500
|
+
}
|
|
2501
|
+
/** Known at load: LCP (so far), FCP and TTFB. */
|
|
2502
|
+
pageload() {
|
|
2503
|
+
const m = {};
|
|
2504
|
+
if (this.lcp !== void 0) m.lcp = ms(this.lcp);
|
|
2505
|
+
if (this.fcp !== void 0) m.fcp = ms(this.fcp);
|
|
2506
|
+
if (this.ttfb !== void 0) m.ttfb = ms(this.ttfb);
|
|
2507
|
+
return m;
|
|
2508
|
+
}
|
|
2509
|
+
/** Known only when the page is hidden: CLS, and INP once there was an interaction. */
|
|
2510
|
+
late() {
|
|
2511
|
+
const count = performance.interactionCount;
|
|
2512
|
+
const inp = inpValue(this.inp, count || void 0);
|
|
2513
|
+
return { cls: { value: this.cls.value, unit: "" }, ...inp !== void 0 ? { inp: ms(inp) } : {} };
|
|
2514
|
+
}
|
|
2515
|
+
stop() {
|
|
2516
|
+
this.observed.splice(0).forEach((o) => o.stop());
|
|
2517
|
+
this.offs.splice(0).forEach((f) => f());
|
|
2518
|
+
}
|
|
2519
|
+
};
|
|
2520
|
+
|
|
2521
|
+
// src/tracing.ts
|
|
2522
|
+
var TRACES_SDK_NAME = "guidely.javascript";
|
|
2523
|
+
var MAX_SPANS = 1e3;
|
|
2524
|
+
var MAX_QUEUE = 20;
|
|
2525
|
+
var BATCH = 10;
|
|
2526
|
+
var now2 = () => performance.now();
|
|
2527
|
+
function entries(type) {
|
|
2528
|
+
try {
|
|
2529
|
+
return performance.getEntriesByType(type);
|
|
2530
|
+
} catch {
|
|
2531
|
+
return [];
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
function pathOf2(href) {
|
|
2535
|
+
try {
|
|
2536
|
+
return new URL(href).pathname || "/";
|
|
2537
|
+
} catch {
|
|
2538
|
+
return stripUrl(href) || "/";
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
function browserContext() {
|
|
2542
|
+
const ua = navigator.userAgent || "";
|
|
2543
|
+
const name = detectDevice(ua, 1024).browser;
|
|
2544
|
+
if (!name) return void 0;
|
|
2545
|
+
const token = { edge: "Edg", opera: "OPR", samsung: "SamsungBrowser", chrome: "(?:Chrome|CriOS)", firefox: "(?:Firefox|FxiOS)", safari: "Version", electron: "Electron" }[name];
|
|
2546
|
+
const version = token ? new RegExp(`${token}/([\\d.]+)`).exec(ua)?.[1] : void 0;
|
|
2547
|
+
return { name: name[0].toUpperCase() + name.slice(1), ...version ? { version } : {} };
|
|
2548
|
+
}
|
|
2549
|
+
function startTracer(config) {
|
|
2550
|
+
if (typeof performance === "undefined" || typeof performance.now !== "function" || typeof window === "undefined") return null;
|
|
2551
|
+
return new Tracer(config);
|
|
2552
|
+
}
|
|
2553
|
+
var Tracer = class {
|
|
2554
|
+
constructor(o) {
|
|
2555
|
+
__publicField(this, "o", o);
|
|
2556
|
+
/** null until the project setting is known; then whether tracing is on for this page load. */
|
|
2557
|
+
__publicField(this, "enabled", null);
|
|
2558
|
+
__publicField(this, "sampled", false);
|
|
2559
|
+
__publicField(this, "rate", 0);
|
|
2560
|
+
__publicField(this, "txn", null);
|
|
2561
|
+
__publicField(this, "pageload");
|
|
2562
|
+
__publicField(this, "origin");
|
|
2563
|
+
__publicField(this, "vitals");
|
|
2564
|
+
__publicField(this, "longTasks", []);
|
|
2565
|
+
/** Finished before sampling was decided. */
|
|
2566
|
+
__publicField(this, "held", []);
|
|
2567
|
+
__publicField(this, "out", []);
|
|
2568
|
+
__publicField(this, "flushTimer", null);
|
|
2569
|
+
__publicField(this, "cleanups", []);
|
|
2570
|
+
__publicField(this, "stopped", false);
|
|
2571
|
+
__publicField(this, "vitalsSent", false);
|
|
2572
|
+
__publicField(this, "apiPrefix");
|
|
2573
|
+
__publicField(this, "idleMs");
|
|
2574
|
+
__publicField(this, "finalMs");
|
|
2575
|
+
this.idleMs = o.idleMs ?? 1e3;
|
|
2576
|
+
this.finalMs = o.finalMs ?? 3e4;
|
|
2577
|
+
this.origin = performance.timeOrigin || Date.now() - now2();
|
|
2578
|
+
this.apiPrefix = `${resolveUrl(o.apiUrl).replace(/\/+$/, "")}/api/v1/sdk/`;
|
|
2579
|
+
this.vitals = new WebVitals(entries("navigation")[0]);
|
|
2580
|
+
const lt = observe("longtask", (es) => {
|
|
2581
|
+
for (const e of es) if (this.longTasks.length < 500) this.longTasks.push(e);
|
|
2582
|
+
});
|
|
2583
|
+
if (lt) this.cleanups.push(lt.stop);
|
|
2584
|
+
this.instrumentFetch();
|
|
2585
|
+
this.instrumentXhr();
|
|
2586
|
+
this.cleanups.push(watchNavigation((href) => this.navigate(href), 0));
|
|
2587
|
+
const onVisibility = () => document.visibilityState === "hidden" && this.onHide();
|
|
2588
|
+
const onPageHide = () => this.onHide();
|
|
2589
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
2590
|
+
addEventListener("pagehide", onPageHide);
|
|
2591
|
+
this.cleanups.push(() => {
|
|
2592
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
2593
|
+
removeEventListener("pagehide", onPageHide);
|
|
2594
|
+
});
|
|
2595
|
+
const t = this.pageload = this.begin("pageload", 0);
|
|
2596
|
+
const onLoad = () => setTimeout(() => {
|
|
2597
|
+
t.loaded = true;
|
|
2598
|
+
this.scheduleIdle(t);
|
|
2599
|
+
}, 0);
|
|
2600
|
+
if (document.readyState === "complete") onLoad();
|
|
2601
|
+
else {
|
|
2602
|
+
addEventListener("load", onLoad, { once: true });
|
|
2603
|
+
this.cleanups.push(() => removeEventListener("load", onLoad));
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
/** Applies the project's `performance` setting; sampling is decided the first time it is on. */
|
|
2607
|
+
configure(settings) {
|
|
2608
|
+
if (this.stopped) return;
|
|
2609
|
+
if (!settings?.enabled) {
|
|
2610
|
+
this.enabled = false;
|
|
2611
|
+
this.held = [];
|
|
2612
|
+
this.out = [];
|
|
2613
|
+
return this.stop();
|
|
2614
|
+
}
|
|
2615
|
+
if (this.enabled) return;
|
|
2616
|
+
this.enabled = true;
|
|
2617
|
+
const sdkRate = this.o.tracesSampleRate ?? 1;
|
|
2618
|
+
this.rate = Math.max(0, Math.min((settings.sampleRate ?? 100) / 100, Number.isFinite(sdkRate) ? sdkRate : 1, 1));
|
|
2619
|
+
this.sampled = Math.random() < this.rate;
|
|
2620
|
+
if (!this.sampled) this.vitals.stop();
|
|
2621
|
+
for (const e of this.held.splice(0)) if (this.sampled) this.enqueue(e);
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* The trace an error happening now belongs to (the open navigation, else the page load), so error
|
|
2625
|
+
* events link to it. Null when this page load isn't traced.
|
|
2626
|
+
*/
|
|
2627
|
+
traceContext() {
|
|
2628
|
+
if (!this.enabled || !this.sampled || this.stopped) return null;
|
|
2629
|
+
const t = this.txn ?? this.pageload;
|
|
2630
|
+
return { traceId: t.traceId, spanId: t.spanId };
|
|
2631
|
+
}
|
|
2632
|
+
/** Stops tracing and sends what is queued. */
|
|
2633
|
+
destroy() {
|
|
2634
|
+
this.flush(true);
|
|
2635
|
+
this.stop();
|
|
2636
|
+
}
|
|
2637
|
+
stop() {
|
|
2638
|
+
if (this.stopped) return;
|
|
2639
|
+
this.stopped = true;
|
|
2640
|
+
if (this.flushTimer) clearTimeout(this.flushTimer);
|
|
2641
|
+
for (const t of [this.txn, this.pageload]) {
|
|
2642
|
+
clearTimeout(t?.idle);
|
|
2643
|
+
clearTimeout(t?.final);
|
|
2644
|
+
}
|
|
2645
|
+
this.vitals.stop();
|
|
2646
|
+
this.cleanups.splice(0).forEach((f) => f());
|
|
2647
|
+
}
|
|
2648
|
+
ts(rel) {
|
|
2649
|
+
return (this.origin + rel) / 1e3;
|
|
2650
|
+
}
|
|
2651
|
+
begin(op, start, href) {
|
|
2652
|
+
const url = this.o.pageHref(href);
|
|
2653
|
+
const t = { op, name: pathOf2(url), url, traceId: hex(16), spanId: hex(8), start, last: start, inflight: 0, spans: [], loaded: op !== "pageload" };
|
|
2654
|
+
t.final = setTimeout(() => this.finish(t), Math.max(0, this.finalMs - (now2() - start)));
|
|
2655
|
+
this.txn = t;
|
|
2656
|
+
return t;
|
|
2657
|
+
}
|
|
2658
|
+
scheduleIdle(t) {
|
|
2659
|
+
clearTimeout(t.idle);
|
|
2660
|
+
if (t.done || t.inflight > 0 || !t.loaded) return;
|
|
2661
|
+
t.idle = setTimeout(() => this.finish(t), this.idleMs);
|
|
2662
|
+
}
|
|
2663
|
+
navigate(href) {
|
|
2664
|
+
if (this.stopped || pathOf2(this.o.pageHref(href)) === this.txn?.name) return;
|
|
2665
|
+
if (this.txn && !this.txn.done) this.finish(this.txn);
|
|
2666
|
+
this.scheduleIdle(this.begin("navigation", now2(), href));
|
|
2667
|
+
}
|
|
2668
|
+
onHide() {
|
|
2669
|
+
if (this.stopped) return;
|
|
2670
|
+
this.vitals.hidden();
|
|
2671
|
+
if (this.txn && !this.txn.done) this.finish(this.txn, now2());
|
|
2672
|
+
if (this.enabled && this.sampled && !this.vitalsSent) {
|
|
2673
|
+
this.vitalsSent = true;
|
|
2674
|
+
const at = this.ts(now2());
|
|
2675
|
+
const p = this.pageload;
|
|
2676
|
+
const e = this.event(p, "ui.webvitals", at, at, [], this.vitals.late());
|
|
2677
|
+
e.contexts.trace.span_id = hex(8);
|
|
2678
|
+
e.contexts.trace.parent_span_id = p.spanId;
|
|
2679
|
+
this.enqueue(e);
|
|
2680
|
+
}
|
|
2681
|
+
this.flush(true);
|
|
2682
|
+
}
|
|
2683
|
+
/** Ends a transaction: adds the spans read from performance entries and queues (or holds) it. */
|
|
2684
|
+
finish(t, endAt) {
|
|
2685
|
+
if (t.done) return;
|
|
2686
|
+
t.done = true;
|
|
2687
|
+
clearTimeout(t.idle);
|
|
2688
|
+
clearTimeout(t.final);
|
|
2689
|
+
if (this.stopped || this.enabled === false || this.enabled && !this.sampled) return;
|
|
2690
|
+
const until = now2();
|
|
2691
|
+
const spans = t.spans;
|
|
2692
|
+
const add = (op, description, start, end2, origin, data) => {
|
|
2693
|
+
if (spans.length >= MAX_SPANS || !(end2 >= start) || start < t.start || start > until) return;
|
|
2694
|
+
spans.push({ span_id: hex(8), parent_span_id: t.spanId, trace_id: t.traceId, op, description, start_timestamp: this.ts(start), timestamp: this.ts(end2), origin, ...data ? { data } : {} });
|
|
2695
|
+
};
|
|
2696
|
+
if (t.op === "pageload") {
|
|
2697
|
+
const n = entries("navigation")[0];
|
|
2698
|
+
if (n) {
|
|
2699
|
+
const timing = (name, s, e) => s > 0 && e > 0 && add("browser", name, s, e, "auto.ui.browser.metrics");
|
|
2700
|
+
timing("redirect", n.redirectStart, n.redirectEnd);
|
|
2701
|
+
timing("cache", n.fetchStart, n.domainLookupStart);
|
|
2702
|
+
timing("DNS", n.domainLookupStart, n.domainLookupEnd);
|
|
2703
|
+
timing("connect", n.connectStart, n.connectEnd);
|
|
2704
|
+
timing("TLS/SSL", n.secureConnectionStart, n.connectEnd);
|
|
2705
|
+
timing("request", n.requestStart, n.responseEnd);
|
|
2706
|
+
timing("response", n.responseStart, n.responseEnd);
|
|
2707
|
+
timing("domContentLoadedEvent", n.domContentLoadedEventStart, n.domContentLoadedEventEnd);
|
|
2708
|
+
timing("loadEvent", n.loadEventStart, n.loadEventEnd);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
for (const e of entries("resource")) {
|
|
2712
|
+
const it = e.initiatorType;
|
|
2713
|
+
if (it === "fetch" || it === "xmlhttprequest" || this.own(e.name)) continue;
|
|
2714
|
+
const kind = it === "script" || it === "css" || it === "link" ? it : it === "img" || it === "image" ? "img" : "other";
|
|
2715
|
+
const data = { "http.response_content_length": e.encodedBodySize, "http.decoded_response_content_length": e.decodedBodySize, "http.response_transfer_size": e.transferSize };
|
|
2716
|
+
if (e.renderBlockingStatus) data["resource.render_blocking_status"] = e.renderBlockingStatus;
|
|
2717
|
+
add(`resource.${kind}`, stripUrl(e.name), e.startTime, e.responseEnd || e.startTime + e.duration, "auto.resource.browser.metrics", data);
|
|
2718
|
+
}
|
|
2719
|
+
for (const e of this.longTasks) add("ui.long-task", "Main UI thread blocked", e.startTime, e.startTime + e.duration, "auto.ui.browser.metrics");
|
|
2720
|
+
for (const e of entries("mark")) add("mark", e.name, e.startTime, e.startTime, "auto.ui.browser.metrics");
|
|
2721
|
+
for (const e of entries("measure")) add("measure", e.name, e.startTime, e.startTime + e.duration, "auto.ui.browser.metrics");
|
|
2722
|
+
const end = endAt !== void 0 ? this.ts(endAt) : spans.reduce((m2, s) => Math.max(m2, s.timestamp), this.ts(t.last));
|
|
2723
|
+
const m = t.op === "pageload" ? this.vitals.pageload() : {};
|
|
2724
|
+
const event = this.event(t, t.op, this.ts(t.start), end, spans, Object.keys(m).length ? m : void 0);
|
|
2725
|
+
if (this.enabled) this.enqueue(event);
|
|
2726
|
+
else if (this.held.length < MAX_QUEUE) this.held.push(event);
|
|
2727
|
+
}
|
|
2728
|
+
event(t, op, start, end, spans, measurements) {
|
|
2729
|
+
const c = this.o.context();
|
|
2730
|
+
const url = c.collectUrls ? stripUrl(t.url) : void 0;
|
|
2731
|
+
const browser = browserContext();
|
|
2732
|
+
return {
|
|
2733
|
+
type: "transaction",
|
|
2734
|
+
event_id: hex(16),
|
|
2735
|
+
platform: "javascript",
|
|
2736
|
+
transaction: t.name,
|
|
2737
|
+
transaction_info: { source: "url" },
|
|
2738
|
+
start_timestamp: start,
|
|
2739
|
+
timestamp: end,
|
|
2740
|
+
...c.release ? { release: c.release } : {},
|
|
2741
|
+
environment: c.environment,
|
|
2742
|
+
contexts: {
|
|
2743
|
+
trace: { trace_id: t.traceId, span_id: t.spanId, op, status: "ok", origin: `auto.${op}.guidely` },
|
|
2744
|
+
...browser ? { browser } : {},
|
|
2745
|
+
guidely: { anonymousId: c.anonymousId, sessionId: c.sessionId, ...url ? { url } : {} }
|
|
2746
|
+
},
|
|
2747
|
+
spans,
|
|
2748
|
+
...measurements ? { measurements } : {},
|
|
2749
|
+
...url ? { request: { url } } : {},
|
|
2750
|
+
...c.userId ? { user: { id: c.userId } } : {}
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
// --- requests ---------------------------------------------------------------------------------
|
|
2754
|
+
/** A Guidely API request (never traced, never given headers). */
|
|
2755
|
+
own(url) {
|
|
2756
|
+
return resolveUrl(url).startsWith(this.apiPrefix);
|
|
2757
|
+
}
|
|
2758
|
+
startHttp(method, url, type) {
|
|
2759
|
+
if (this.stopped || this.enabled === false || this.own(url)) return null;
|
|
2760
|
+
const abs = resolveUrl(url);
|
|
2761
|
+
const t = this.txn && !this.txn.done ? this.txn : null;
|
|
2762
|
+
const spanId = hex(8);
|
|
2763
|
+
let span = null;
|
|
2764
|
+
if (t && (this.enabled === null || this.sampled)) {
|
|
2765
|
+
const u = stripUrl(abs);
|
|
2766
|
+
span = { span_id: spanId, parent_span_id: t.spanId, trace_id: t.traceId, op: "http.client", description: `${method} ${u}`, start_timestamp: this.ts(now2()), timestamp: 0, origin: "auto.http.browser", data: { "http.method": method, url: u, type } };
|
|
2767
|
+
t.inflight += 1;
|
|
2768
|
+
clearTimeout(t.idle);
|
|
2769
|
+
}
|
|
2770
|
+
return { txn: t, span, spanId, url };
|
|
2771
|
+
}
|
|
2772
|
+
endHttp(live, status, length) {
|
|
2773
|
+
const { txn: t, span } = live;
|
|
2774
|
+
if (!t || !span) return;
|
|
2775
|
+
t.inflight = Math.max(0, t.inflight - 1);
|
|
2776
|
+
t.last = now2();
|
|
2777
|
+
span.timestamp = this.ts(t.last);
|
|
2778
|
+
span.status = status ? httpSpanStatus(status) : "unknown_error";
|
|
2779
|
+
if (status) span.data["http.response.status_code"] = status;
|
|
2780
|
+
if (length && Number(length) >= 0) span.data["http.response_content_length"] = Number(length);
|
|
2781
|
+
if (!t.done && t.spans.length < MAX_SPANS) t.spans.push(span);
|
|
2782
|
+
this.scheduleIdle(t);
|
|
2783
|
+
}
|
|
2784
|
+
/** Trace headers for a request, or null when it must not get any (tracing undecided / off, or not a target). */
|
|
2785
|
+
headers(live, baggage) {
|
|
2786
|
+
if (!this.enabled || this.stopped || !shouldPropagate(live.url, this.o.tracePropagationTargets)) return null;
|
|
2787
|
+
const c = this.o.context();
|
|
2788
|
+
const h = traceHeaders({ traceId: (live.txn ?? this.txn ?? this.pageload).traceId, publicKey: this.o.publicKey, release: c.release, environment: c.environment, sampled: this.sampled, sampleRate: this.rate }, live.spanId);
|
|
2789
|
+
return { ...h, baggage: mergeBaggage(baggage, h.baggage) };
|
|
2790
|
+
}
|
|
2791
|
+
instrumentFetch() {
|
|
2792
|
+
const win = window;
|
|
2793
|
+
const orig = win.fetch;
|
|
2794
|
+
if (typeof orig !== "function") return;
|
|
2795
|
+
const patched = (input, init) => {
|
|
2796
|
+
let live = null;
|
|
2797
|
+
try {
|
|
2798
|
+
const isReq = typeof Request !== "undefined" && input instanceof Request;
|
|
2799
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
2800
|
+
live = this.startHttp(String(init?.method || (isReq ? input.method : "GET")).toUpperCase(), url, "fetch");
|
|
2801
|
+
if (live && typeof Headers !== "undefined") {
|
|
2802
|
+
const headers = new Headers(init?.headers ?? (isReq ? input.headers : void 0));
|
|
2803
|
+
const add = headers.has("sentry-trace") ? null : this.headers(live, headers.get("baggage"));
|
|
2804
|
+
if (add) {
|
|
2805
|
+
for (const [k, v] of Object.entries(add)) if (k !== "traceparent" || !headers.has(k)) headers.set(k, v);
|
|
2806
|
+
init = { ...init, headers };
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
} catch {
|
|
2810
|
+
}
|
|
2811
|
+
const p = orig.call(win, input, init);
|
|
2812
|
+
if (live?.span) {
|
|
2813
|
+
const l = live;
|
|
2814
|
+
p.then(
|
|
2815
|
+
(res) => this.endHttp(l, res.status, res.headers?.get?.("content-length")),
|
|
2816
|
+
() => this.endHttp(l, 0)
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
return p;
|
|
2820
|
+
};
|
|
2821
|
+
win.fetch = patched;
|
|
2822
|
+
this.cleanups.push(() => {
|
|
2823
|
+
if (win.fetch === patched) win.fetch = orig;
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
instrumentXhr() {
|
|
2827
|
+
const proto = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest.prototype : null;
|
|
2828
|
+
if (!proto) return;
|
|
2829
|
+
const { open, send, setRequestHeader } = proto;
|
|
2830
|
+
const self = { startHttp: this.startHttp.bind(this), headers: this.headers.bind(this), endHttp: this.endHttp.bind(this) };
|
|
2831
|
+
const meta = /* @__PURE__ */ new WeakMap();
|
|
2832
|
+
const pOpen = function(method, url, ...rest) {
|
|
2833
|
+
meta.set(this, { method: String(method).toUpperCase(), url: String(url), headers: {} });
|
|
2834
|
+
return open.call(this, method, url, ...rest);
|
|
2835
|
+
};
|
|
2836
|
+
const pSetHeader = function(name, value) {
|
|
2837
|
+
const m = meta.get(this);
|
|
2838
|
+
if (m) m.headers[String(name).toLowerCase()] = String(value);
|
|
2839
|
+
return setRequestHeader.call(this, name, value);
|
|
2840
|
+
};
|
|
2841
|
+
const pSend = function(body) {
|
|
2842
|
+
try {
|
|
2843
|
+
const m = meta.get(this);
|
|
2844
|
+
const live = m ? self.startHttp(m.method, m.url, "xhr") : null;
|
|
2845
|
+
if (live && m && !m.headers["sentry-trace"]) {
|
|
2846
|
+
const existing = m.headers.baggage ?? null;
|
|
2847
|
+
const add = self.headers(live, null);
|
|
2848
|
+
if (add) {
|
|
2849
|
+
setRequestHeader.call(this, "sentry-trace", add["sentry-trace"]);
|
|
2850
|
+
if (!m.headers.traceparent) setRequestHeader.call(this, "traceparent", add.traceparent);
|
|
2851
|
+
if (!existing?.includes("sentry-")) setRequestHeader.call(this, "baggage", add.baggage);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
if (live?.span) this.addEventListener("loadend", () => self.endHttp(live, this.status, safeHeader(this, "content-length")), { once: true });
|
|
2855
|
+
} catch {
|
|
2856
|
+
}
|
|
2857
|
+
return send.call(this, body);
|
|
2858
|
+
};
|
|
2859
|
+
proto.open = pOpen;
|
|
2860
|
+
proto.setRequestHeader = pSetHeader;
|
|
2861
|
+
proto.send = pSend;
|
|
2862
|
+
this.cleanups.push(() => {
|
|
2863
|
+
if (proto.open === pOpen) proto.open = open;
|
|
2864
|
+
if (proto.setRequestHeader === pSetHeader) proto.setRequestHeader = setRequestHeader;
|
|
2865
|
+
if (proto.send === pSend) proto.send = send;
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2868
|
+
// --- sending ----------------------------------------------------------------------------------
|
|
2869
|
+
enqueue(e) {
|
|
2870
|
+
this.out.push(e);
|
|
2871
|
+
if (this.out.length > MAX_QUEUE) this.out.shift();
|
|
2872
|
+
this.flushTimer ?? (this.flushTimer = setTimeout(() => this.flush(), this.o.flushMs ?? 1e3));
|
|
2873
|
+
}
|
|
2874
|
+
/** Sends queued transactions (`hide`: with `sendBeacon` / keepalive, the page is going away). */
|
|
2875
|
+
flush(hide = false) {
|
|
2876
|
+
if (this.flushTimer) clearTimeout(this.flushTimer);
|
|
2877
|
+
this.flushTimer = null;
|
|
2878
|
+
if (!this.out.length) return;
|
|
2879
|
+
const allowed = this.o.allowed();
|
|
2880
|
+
if (allowed === false) {
|
|
2881
|
+
this.out = [];
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
if (allowed === null) return;
|
|
2885
|
+
const { api } = this.o;
|
|
2886
|
+
if (!hide && !api.health.canSend()) {
|
|
2887
|
+
this.flushTimer = setTimeout(() => this.flush(), api.health.waitMs());
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2890
|
+
while (this.out.length) {
|
|
2891
|
+
const batch = this.out.splice(0, BATCH);
|
|
2892
|
+
const body = { sdk: { name: TRACES_SDK_NAME, version: this.o.sdkVersion }, transactions: batch };
|
|
2893
|
+
if (hide && api.beacon("/traces", body)) continue;
|
|
2894
|
+
api.post("/traces", body, { keepalive: hide }).then(
|
|
2895
|
+
(res) => isServerFailure(res.status) && this.retry(batch),
|
|
2896
|
+
() => this.retry(batch)
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
retry(batch) {
|
|
2901
|
+
if (this.stopped) return;
|
|
2902
|
+
this.out.unshift(...batch);
|
|
2903
|
+
this.out.splice(MAX_QUEUE);
|
|
2904
|
+
this.flushTimer ?? (this.flushTimer = setTimeout(() => this.flush(), Math.max(5e3, this.o.api.health.waitMs())));
|
|
2905
|
+
}
|
|
2906
|
+
};
|
|
2907
|
+
function safeHeader(xhr, name) {
|
|
2908
|
+
try {
|
|
2909
|
+
return xhr.getResponseHeader(name);
|
|
2910
|
+
} catch {
|
|
2911
|
+
return null;
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// src/bug-report.ts
|
|
2916
|
+
var import_runtime22 = require("@guidelytech/shared/runtime");
|
|
2917
|
+
async function encodeScreenshot(s) {
|
|
2918
|
+
if (!s) return null;
|
|
2919
|
+
if (!(typeof Blob !== "undefined" && s instanceof Blob)) {
|
|
2920
|
+
const encoded = s;
|
|
2921
|
+
const data = encoded.data.replace(/^data:[^,]*,/, "");
|
|
2922
|
+
if (data.length * 0.75 > import_runtime22.MAX_BUG_REPORT_SCREENSHOT_BYTES) throw new Error("[Guidely] captureFeedback: screenshots can be up to 4 MB");
|
|
2923
|
+
return { fileName: encoded.fileName || "screenshot.png", data };
|
|
2924
|
+
}
|
|
2925
|
+
if (s.size > import_runtime22.MAX_BUG_REPORT_SCREENSHOT_BYTES) throw new Error("[Guidely] captureFeedback: screenshots can be up to 4 MB");
|
|
2926
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
2927
|
+
const r = new FileReader();
|
|
2928
|
+
r.onload = () => resolve(String(r.result));
|
|
2929
|
+
r.onerror = () => reject(new Error("[Guidely] captureFeedback: the screenshot couldn't be read"));
|
|
2930
|
+
r.readAsDataURL(s);
|
|
2931
|
+
});
|
|
2932
|
+
const name = s.name || (s.type === "image/jpeg" ? "screenshot.jpg" : "screenshot.png");
|
|
2933
|
+
return { fileName: name, data: dataUrl.slice(dataUrl.indexOf(",") + 1) };
|
|
2934
|
+
}
|
|
2935
|
+
function bugReportBody(input, ctx, source, screenshot) {
|
|
2936
|
+
const tags = {};
|
|
2937
|
+
for (const [k, v] of Object.entries(input.tags ?? {}).slice(0, 20)) if (k && v !== void 0 && v !== null) tags[k.slice(0, 64)] = String(v).slice(0, 200);
|
|
2938
|
+
const email = input.email?.trim();
|
|
2939
|
+
const name = input.name?.trim();
|
|
2940
|
+
const associated = input.associatedEventId ?? ctx.lastExceptionId;
|
|
2941
|
+
return {
|
|
2942
|
+
anonymousId: ctx.anonymousId,
|
|
2943
|
+
userId: ctx.userId,
|
|
2944
|
+
sessionId: ctx.sessionId,
|
|
2945
|
+
message: String(input.message ?? "").trim().slice(0, import_runtime22.MAX_BUG_REPORT_LENGTH),
|
|
2946
|
+
...email ? { email } : {},
|
|
2947
|
+
...name ? { name } : {},
|
|
2948
|
+
...associated ? { associatedEventId: associated } : {},
|
|
2949
|
+
...ctx.url ? { url: ctx.url } : {},
|
|
2950
|
+
...ctx.appVersion ? { appVersion: ctx.appVersion } : {},
|
|
2951
|
+
source,
|
|
2952
|
+
...Object.keys(tags).length ? { tags } : {},
|
|
2953
|
+
sdk: ctx.sdk,
|
|
2954
|
+
...screenshot ? { screenshot } : {}
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
async function sendBugReport(api, body) {
|
|
2958
|
+
const res = await api.request("POST", "/bug-reports", { body, timeoutMs: body.screenshot ? 6e4 : 2e4 });
|
|
2959
|
+
if (res.ok && res.data && "id" in res.data) return res.data;
|
|
2960
|
+
if (res.status === 429) throw new Error("You've sent several reports just now. Please try again later.");
|
|
2961
|
+
const message = res.data && "error" in res.data ? res.data.error?.message : null;
|
|
2962
|
+
throw new Error(res.status === 400 && message ? message : "Your report couldn't be sent. Please try again.");
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2119
2965
|
// src/client.ts
|
|
2120
2966
|
var SDK_NAME = "@guidelytech/sdk-core";
|
|
2121
|
-
var SDK_VERSION = "0.
|
|
2967
|
+
var SDK_VERSION = "0.17.0";
|
|
2122
2968
|
var DEFAULT_API_URL = "https://api.guidely.app";
|
|
2123
2969
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2124
2970
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2125
2971
|
var MAX_SESSION_EXCEPTIONS = 100;
|
|
2126
2972
|
var CACHED_START_MS = 1500;
|
|
2127
|
-
function settlesWithin(promise,
|
|
2973
|
+
function settlesWithin(promise, ms2) {
|
|
2128
2974
|
return new Promise((resolve) => {
|
|
2129
|
-
const timer = setTimeout(() => resolve(false),
|
|
2975
|
+
const timer = setTimeout(() => resolve(false), ms2);
|
|
2130
2976
|
const done = () => {
|
|
2131
2977
|
clearTimeout(timer);
|
|
2132
2978
|
resolve(true);
|
|
@@ -2143,8 +2989,8 @@ function nowIso() {
|
|
|
2143
2989
|
function emptyState() {
|
|
2144
2990
|
return { guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] };
|
|
2145
2991
|
}
|
|
2146
|
-
function newSession(
|
|
2147
|
-
return { id: `s_${(0,
|
|
2992
|
+
function newSession(now3 = Date.now()) {
|
|
2993
|
+
return { id: `s_${(0, import_runtime23.randomId)(16)}`, startedAt: new Date(now3).toISOString(), shownGuides: [], events: [], lastActivityAt: now3 };
|
|
2148
2994
|
}
|
|
2149
2995
|
var GuidelyClient = class _GuidelyClient {
|
|
2150
2996
|
constructor(options) {
|
|
@@ -2159,6 +3005,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2159
3005
|
/** Agent Analytics turns (agentConversation), in their own outbox. */
|
|
2160
3006
|
__publicField(this, "agentTurns");
|
|
2161
3007
|
__publicField(this, "config", null);
|
|
3008
|
+
/** The `environment` option as passed (the resolved one is in `options.environment`). */
|
|
3009
|
+
__publicField(this, "envOption");
|
|
2162
3010
|
__publicField(this, "etag", null);
|
|
2163
3011
|
__publicField(this, "anonymousId");
|
|
2164
3012
|
__publicField(this, "user", null);
|
|
@@ -2200,6 +3048,11 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2200
3048
|
__publicField(this, "exceptionThrottle", new ExceptionThrottle());
|
|
2201
3049
|
/** Exceptions seen before the config was loaded (reported once it is known whether the server accepts them). */
|
|
2202
3050
|
__publicField(this, "earlyExceptions", []);
|
|
3051
|
+
/** The last exception this page reported, which bug reports link to by default. */
|
|
3052
|
+
__publicField(this, "lastExceptionId", null);
|
|
3053
|
+
__publicField(this, "bugReportDialog", null);
|
|
3054
|
+
/** Tags, extra, contexts and breadcrumbs sent with every error (`setTag`, `addBreadcrumb`…). */
|
|
3055
|
+
__publicField(this, "errorScope", new ErrorScope(() => this.options.errors));
|
|
2203
3056
|
__publicField(this, "initialPageViewSent", false);
|
|
2204
3057
|
__publicField(this, "pageViewUrl", null);
|
|
2205
3058
|
__publicField(this, "pageLeft", false);
|
|
@@ -2211,6 +3064,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2211
3064
|
__publicField(this, "locale", null);
|
|
2212
3065
|
/** Language each shown guide was rendered in; added to its events as `locale`. */
|
|
2213
3066
|
__publicField(this, "guideLocales", /* @__PURE__ */ new Map());
|
|
3067
|
+
/** Performance tracing (`tracing` option + the project's `performance` setting); see tracing.ts. */
|
|
3068
|
+
__publicField(this, "tracer");
|
|
2214
3069
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
2215
3070
|
__publicField(this, "eventBus", {
|
|
2216
3071
|
on: (name, cb) => {
|
|
@@ -2227,9 +3082,10 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2227
3082
|
});
|
|
2228
3083
|
if (!isBrowser()) throw new Error("[Guidely] createGuidely() must be called in the browser. Create the client inside useEffect / onMount, or use the framework adapters.");
|
|
2229
3084
|
if (!options || !options.projectId) throw new Error("[Guidely] `projectId` (your environment public key, e.g. pk_dev_\u2026) is required.");
|
|
3085
|
+
this.envOption = options.environment || void 0;
|
|
2230
3086
|
this.options = {
|
|
2231
3087
|
...options,
|
|
2232
|
-
environment: options.environment
|
|
3088
|
+
environment: options.environment || environmentFromKey(options.projectId),
|
|
2233
3089
|
apiUrl: options.apiUrl ?? DEFAULT_API_URL,
|
|
2234
3090
|
analytics: options.analytics ?? true,
|
|
2235
3091
|
consent: options.consent ?? "granted",
|
|
@@ -2245,7 +3101,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2245
3101
|
this.sessionStore = this.options.storage === "memory" ? this.store : createStorage("session");
|
|
2246
3102
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
2247
3103
|
if (!this.anonymousId) {
|
|
2248
|
-
this.anonymousId = `anon_${(0,
|
|
3104
|
+
this.anonymousId = `anon_${(0, import_runtime23.randomId)(20)}`;
|
|
2249
3105
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
2250
3106
|
}
|
|
2251
3107
|
this.firstVisit = !this.store.get(KEYS.seen);
|
|
@@ -2273,6 +3129,16 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2273
3129
|
this.agentTurns = new AgentTurns({ api: this.api, store: this.store, storageKey: KEYS.agentOutbox(this.options.projectId), health: this.api.health, sdk: { name: SDK_NAME, version: SDK_VERSION } });
|
|
2274
3130
|
if (options.user) this.user = { ...options.user };
|
|
2275
3131
|
this.flags = this.createFlags(options.flags);
|
|
3132
|
+
this.tracer = options.tracing === false ? null : startTracer({
|
|
3133
|
+
...typeof options.tracing === "object" ? options.tracing : {},
|
|
3134
|
+
api: this.api,
|
|
3135
|
+
publicKey: this.options.projectId,
|
|
3136
|
+
apiUrl: this.options.apiUrl,
|
|
3137
|
+
sdkVersion: SDK_VERSION,
|
|
3138
|
+
pageHref: (href) => this.pageHref(href),
|
|
3139
|
+
context: () => ({ anonymousId: this.anonymousId, sessionId: this.session.id, userId: this.user?.id || null, release: this.options.release || this.appVersion(), environment: this.environment, collectUrls: this.collectUrls() }),
|
|
3140
|
+
allowed: () => this.consent === "pending" ? null : this.analyticsEnabled() && !this.editing()
|
|
3141
|
+
});
|
|
2276
3142
|
}
|
|
2277
3143
|
createFlags(opts) {
|
|
2278
3144
|
const base = { sdkKey: this.options.projectId, apiUrl: this.options.apiUrl, fetch: this.options.fetch, sdk: { name: SDK_NAME, version: SDK_VERSION } };
|
|
@@ -2327,7 +3193,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2327
3193
|
try {
|
|
2328
3194
|
const cached = readJson(this.store, KEYS.config(this.options.projectId), null);
|
|
2329
3195
|
this.etag = this.store.get(KEYS.etag(this.options.projectId));
|
|
2330
|
-
if (cached && cached.guides) this.
|
|
3196
|
+
if (cached && cached.guides) this.useConfig(webOnly(cached));
|
|
2331
3197
|
const before = this.config?.configVersion;
|
|
2332
3198
|
const fresh = this.fetchConfig();
|
|
2333
3199
|
if (!this.config) await fresh;
|
|
@@ -2356,12 +3222,22 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2356
3222
|
this.resolveReady?.();
|
|
2357
3223
|
}
|
|
2358
3224
|
}
|
|
3225
|
+
/** Environment guides and flags target: the `environment` option, else the one the loaded config names, else a guess from the key. */
|
|
3226
|
+
get environment() {
|
|
3227
|
+
return this.envOption ?? this.config?.environment ?? this.options.environment;
|
|
3228
|
+
}
|
|
3229
|
+
useConfig(config) {
|
|
3230
|
+
const env = this.environment;
|
|
3231
|
+
this.config = config;
|
|
3232
|
+
if (this.environment !== env) this.syncFlagAttributes();
|
|
3233
|
+
this.tracer?.configure(config.settings?.performance);
|
|
3234
|
+
}
|
|
2359
3235
|
async fetchConfig() {
|
|
2360
3236
|
try {
|
|
2361
3237
|
const res = await this.api.get("/config", { etag: this.etag, params: { platform: "web" } });
|
|
2362
3238
|
if (res.status === 304) return true;
|
|
2363
3239
|
if (res.ok && res.data && Array.isArray(res.data.guides)) {
|
|
2364
|
-
this.
|
|
3240
|
+
this.useConfig(webOnly(res.data));
|
|
2365
3241
|
this.etag = res.etag;
|
|
2366
3242
|
writeJson(this.store, KEYS.config(this.options.projectId), res.data);
|
|
2367
3243
|
if (res.etag) this.store.set(KEYS.etag(this.options.projectId), res.etag);
|
|
@@ -2384,6 +3260,15 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2384
3260
|
const onClick = (e) => this.handleDocumentClick(e);
|
|
2385
3261
|
doc.addEventListener("click", onClick, true);
|
|
2386
3262
|
this.cleanups.push(() => doc.removeEventListener("click", onClick, true));
|
|
3263
|
+
const onArticleLink = (e) => {
|
|
3264
|
+
if (e.defaultPrevented) return;
|
|
3265
|
+
const articleId = (0, import_runtime23.articleIdFromHref)((0, import_guide_runtime5.composedTarget)(e)?.closest?.("a[href]")?.getAttribute("href"));
|
|
3266
|
+
if (!articleId) return;
|
|
3267
|
+
e.preventDefault();
|
|
3268
|
+
this.openArticle(articleId);
|
|
3269
|
+
};
|
|
3270
|
+
doc.addEventListener("click", onArticleLink);
|
|
3271
|
+
this.cleanups.push(() => doc.removeEventListener("click", onArticleLink));
|
|
2387
3272
|
const onVisibility = () => {
|
|
2388
3273
|
const hidden = doc.visibilityState === "hidden";
|
|
2389
3274
|
this.pageTracker.setHidden(hidden);
|
|
@@ -2405,9 +3290,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2405
3290
|
};
|
|
2406
3291
|
let lastScroll = 0;
|
|
2407
3292
|
const onScroll = () => {
|
|
2408
|
-
const
|
|
2409
|
-
if (
|
|
2410
|
-
lastScroll =
|
|
3293
|
+
const now3 = Date.now();
|
|
3294
|
+
if (now3 - lastScroll < 250) return;
|
|
3295
|
+
lastScroll = now3;
|
|
2411
3296
|
this.pageTracker.measureScroll();
|
|
2412
3297
|
this.touchSession();
|
|
2413
3298
|
};
|
|
@@ -2465,14 +3350,22 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2465
3350
|
/** Uncaught errors + unhandled rejections; attached before the config loads so boot errors are kept. */
|
|
2466
3351
|
wireErrorListeners() {
|
|
2467
3352
|
const win = window;
|
|
2468
|
-
const onError = (e) => this.captureGlobalException(errorEventToException(e));
|
|
2469
|
-
const onRejection = (e) => this.captureGlobalException(rejectionToException(e));
|
|
3353
|
+
const onError = (e) => this.captureGlobalException(errorEventToException(e), void 0, e.error);
|
|
3354
|
+
const onRejection = (e) => this.captureGlobalException(rejectionToException(e), void 0, e.reason);
|
|
2470
3355
|
win.addEventListener("error", onError);
|
|
2471
3356
|
win.addEventListener("unhandledrejection", onRejection);
|
|
2472
3357
|
this.cleanups.push(() => {
|
|
2473
3358
|
win.removeEventListener("error", onError);
|
|
2474
3359
|
win.removeEventListener("unhandledrejection", onRejection);
|
|
2475
3360
|
});
|
|
3361
|
+
const crumbs = this.options.errors?.breadcrumbs;
|
|
3362
|
+
if (crumbs !== false) this.cleanups.push(instrumentBreadcrumbs((c, hint) => this.errorScope.addBreadcrumb(c, hint), { console: crumbs?.console, http: crumbs?.http, apiUrl: this.options.apiUrl }));
|
|
3363
|
+
}
|
|
3364
|
+
/** An automatic breadcrumb, unless the `errors.breadcrumbs` option turns its kind off. */
|
|
3365
|
+
autoBreadcrumb(kind, crumb, hint) {
|
|
3366
|
+
const o = this.options.errors?.breadcrumbs;
|
|
3367
|
+
if (o === false || o && o[kind] === false) return;
|
|
3368
|
+
this.errorScope.addBreadcrumb(crumb, hint === void 0 ? void 0 : { event: hint });
|
|
2476
3369
|
}
|
|
2477
3370
|
/** Re-fetches the published config and re-evaluates triggers. */
|
|
2478
3371
|
async refresh() {
|
|
@@ -2520,6 +3413,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2520
3413
|
this.queue.destroy();
|
|
2521
3414
|
this.outbox.destroy();
|
|
2522
3415
|
this.agentTurns.destroy();
|
|
3416
|
+
this.tracer?.destroy();
|
|
2523
3417
|
this.flags.destroy();
|
|
2524
3418
|
this.emitter.clear();
|
|
2525
3419
|
(0, import_guide_runtime5.destroyHost)();
|
|
@@ -2535,7 +3429,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2535
3429
|
this.user = { id: String(userId), traits: { ...this.user?.id === String(userId) ? this.user.traits : {}, ...traits || {} }, account: account ?? (this.user?.id === String(userId) ? this.user.account : void 0) };
|
|
2536
3430
|
writeJson(this.store, KEYS.user(this.options.projectId), this.user);
|
|
2537
3431
|
this.syncFlagAttributes();
|
|
2538
|
-
this.enqueue("identify", { properties: { traits: (0,
|
|
3432
|
+
this.enqueue("identify", { properties: { traits: (0, import_runtime23.sanitizeProperties)(this.user.traits) } });
|
|
2539
3433
|
await this.syncIdentify();
|
|
2540
3434
|
this.emitter.emit("identify", { userId: this.user.id, anonymousId: this.anonymousId });
|
|
2541
3435
|
if (this.isReady) this.evaluate("identify");
|
|
@@ -2611,7 +3505,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2611
3505
|
this.stopGuide();
|
|
2612
3506
|
this.user = null;
|
|
2613
3507
|
this.store.remove(KEYS.user(this.options.projectId));
|
|
2614
|
-
this.anonymousId = `anon_${(0,
|
|
3508
|
+
this.anonymousId = `anon_${(0, import_runtime23.randomId)(20)}`;
|
|
2615
3509
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
2616
3510
|
this.state = emptyState();
|
|
2617
3511
|
this.persistState();
|
|
@@ -2649,6 +3543,11 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2649
3543
|
analyticsEnabled() {
|
|
2650
3544
|
return this.options.analytics && this.consent === "granted" && (this.config?.settings?.analyticsEnabled ?? true);
|
|
2651
3545
|
}
|
|
3546
|
+
/** The host app's version (`appVersion` option), sent with every event and recording. */
|
|
3547
|
+
appVersion() {
|
|
3548
|
+
const v = this.options.appVersion;
|
|
3549
|
+
return v === void 0 || v === null || String(v).trim() === "" ? void 0 : String(v).trim().slice(0, 200);
|
|
3550
|
+
}
|
|
2652
3551
|
collectUrls() {
|
|
2653
3552
|
return this.config?.settings?.collectUrls ?? true;
|
|
2654
3553
|
}
|
|
@@ -2656,7 +3555,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2656
3555
|
if (this.consent === "denied" || !this.options.analytics) return null;
|
|
2657
3556
|
if (name !== "page_leave" && name !== "heatmap") this.touchSession();
|
|
2658
3557
|
const event = {
|
|
2659
|
-
eventId: (0,
|
|
3558
|
+
eventId: (0, import_runtime23.randomId)(24),
|
|
2660
3559
|
name,
|
|
2661
3560
|
occurredAt: nowIso(),
|
|
2662
3561
|
anonymousId: this.anonymousId,
|
|
@@ -2664,12 +3563,14 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2664
3563
|
accountId: this.user?.account?.id || null,
|
|
2665
3564
|
sessionId: this.session.id,
|
|
2666
3565
|
url: this.collectUrls() ? this.pageHref() : void 0,
|
|
3566
|
+
...this.appVersion() ? { appVersion: this.appVersion() } : {},
|
|
2667
3567
|
...extra
|
|
2668
3568
|
};
|
|
2669
|
-
if (extra.properties) event.properties = (0,
|
|
3569
|
+
if (extra.properties) event.properties = (0, import_runtime23.sanitizeProperties)(extra.properties);
|
|
2670
3570
|
const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
|
|
2671
3571
|
if (locale) event.properties = { ...event.properties ?? {}, locale };
|
|
2672
3572
|
this.queue.enqueue(event);
|
|
3573
|
+
if (name.startsWith("guide_") || name.startsWith("step_")) this.autoBreadcrumb("guides", { category: "guide", message: name, data: { guideId: event.guideId, ...event.stepIndex !== void 0 ? { stepIndex: event.stepIndex } : {} } });
|
|
2673
3574
|
this.replay?.addCustomEvent("guidely", { name, customName: event.customName, guideId: event.guideId });
|
|
2674
3575
|
this.emitter.emit("track", event);
|
|
2675
3576
|
return event;
|
|
@@ -2723,6 +3624,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2723
3624
|
handleNavigation(url, explicit = false) {
|
|
2724
3625
|
if (this.destroyed) return;
|
|
2725
3626
|
const changed = url !== this.lastUrl;
|
|
3627
|
+
if (changed && this.lastUrl) this.autoBreadcrumb("navigation", { category: "navigation", data: this.collectUrls() ? { from: breadcrumbUrl(this.lastUrl), to: breadcrumbUrl(url) } : {} });
|
|
2726
3628
|
this.lastUrl = url;
|
|
2727
3629
|
if (changed) this.syncFlagAttributes();
|
|
2728
3630
|
if (!explicit && changed) {
|
|
@@ -2751,6 +3653,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2751
3653
|
handleDocumentClick(e) {
|
|
2752
3654
|
const target = (0, import_guide_runtime5.composedTarget)(e);
|
|
2753
3655
|
if (!target || typeof target.closest !== "function") return;
|
|
3656
|
+
if (!isIgnoredElement(target)) this.autoBreadcrumb("clicks", { category: "ui.click", message: elementSelector(interactiveElement(target) ?? target) }, e);
|
|
2754
3657
|
this.autocaptureClick(e, target);
|
|
2755
3658
|
this.completeChecklistTasksBy("click", target);
|
|
2756
3659
|
if (this.isReady) this.evaluate("click", { target });
|
|
@@ -2760,17 +3663,17 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2760
3663
|
// ---------------------------------------------------------------------------
|
|
2761
3664
|
/** Records activity; starts a new session after `sessionTimeoutMinutes` of inactivity or 24h. */
|
|
2762
3665
|
touchSession() {
|
|
2763
|
-
const
|
|
3666
|
+
const now3 = Date.now();
|
|
2764
3667
|
const s = this.session;
|
|
2765
3668
|
const timeout = Math.max(1, this.config?.settings?.sessionTimeoutMinutes ?? 30) * 6e4;
|
|
2766
|
-
const last = s.lastActivityAt ??
|
|
2767
|
-
if (
|
|
2768
|
-
if (
|
|
2769
|
-
s.lastActivityAt =
|
|
3669
|
+
const last = s.lastActivityAt ?? now3;
|
|
3670
|
+
if (now3 - last > timeout || now3 - (Date.parse(s.startedAt) || now3) > DAY_MS) return this.rotateSession(now3);
|
|
3671
|
+
if (now3 - (s.lastActivityAt ?? 0) < 1e3) return;
|
|
3672
|
+
s.lastActivityAt = now3;
|
|
2770
3673
|
this.persistSession();
|
|
2771
3674
|
}
|
|
2772
|
-
rotateSession(
|
|
2773
|
-
this.session = newSession(
|
|
3675
|
+
rotateSession(now3) {
|
|
3676
|
+
this.session = newSession(now3);
|
|
2774
3677
|
this.persistSession();
|
|
2775
3678
|
this.log("session rotated", this.session.id);
|
|
2776
3679
|
if (this.replay?.activeSessionId) this.replay.stop();
|
|
@@ -2802,7 +3705,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2802
3705
|
const device = detectDevice(navigator.userAgent, window.innerWidth);
|
|
2803
3706
|
const props = { deviceType: device.type, browser: device.browser, os: device.os, screenWidth: window.screen?.width, viewportWidth: window.innerWidth };
|
|
2804
3707
|
if (collect) {
|
|
2805
|
-
props.path =
|
|
3708
|
+
props.path = pathOf3(url);
|
|
2806
3709
|
props.title = (document.title || "").slice(0, 200);
|
|
2807
3710
|
const ref = !this.session.pageViews ? externalReferrer() : void 0;
|
|
2808
3711
|
if (ref) props.referrer = ref;
|
|
@@ -2821,7 +3724,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2821
3724
|
if (!this.pageLeft && this.autocaptureOn("pageLeave")) {
|
|
2822
3725
|
const info = t.leave();
|
|
2823
3726
|
const collect = this.collectUrls();
|
|
2824
|
-
this.enqueue("page_leave", { url: collect ? info.url : void 0, properties: { ...collect ? { path:
|
|
3727
|
+
this.enqueue("page_leave", { url: collect ? info.url : void 0, properties: { ...collect ? { path: pathOf3(info.url) } : {}, durationMs: info.durationMs, scrollDepth: info.scrollDepth } });
|
|
2825
3728
|
}
|
|
2826
3729
|
if (nextUrl !== null) {
|
|
2827
3730
|
t.start(nextUrl);
|
|
@@ -2865,42 +3768,120 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2865
3768
|
if (!batch || !this.captureAllowed()) return;
|
|
2866
3769
|
const collect = this.collectUrls();
|
|
2867
3770
|
const de = document.documentElement;
|
|
2868
|
-
const properties = { path: collect ?
|
|
3771
|
+
const properties = { path: collect ? pathOf3(batch.url) : void 0, viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, pageHeight: Math.max(de.scrollHeight, document.body?.scrollHeight || 0), points: batch.points };
|
|
2869
3772
|
if (!collect) delete properties.path;
|
|
2870
3773
|
this.enqueue("heatmap", { url: collect ? batch.url : void 0, properties });
|
|
2871
3774
|
}
|
|
2872
|
-
captureGlobalException(props) {
|
|
3775
|
+
captureGlobalException(props, detail, error) {
|
|
2873
3776
|
if (!props || isGuidelyException(props) || this.options.captureErrors === false) return;
|
|
3777
|
+
const d = detail ?? this.errorDetail(error, props, props.source === "unhandledrejection" ? "onunhandledrejection" : "onerror", {}, "error");
|
|
2874
3778
|
if (!this.config) {
|
|
2875
|
-
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, void 0]);
|
|
3779
|
+
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, void 0, d]);
|
|
2876
3780
|
return;
|
|
2877
3781
|
}
|
|
2878
|
-
this.sendException(props);
|
|
3782
|
+
this.sendException(props, void 0, d);
|
|
2879
3783
|
}
|
|
2880
3784
|
/**
|
|
2881
3785
|
* Reports a caught error as an `exception` event (`source: "manual"`, `handled: true`), grouped
|
|
2882
|
-
* into an issue on the Errors page.
|
|
2883
|
-
*
|
|
3786
|
+
* into an issue on the Errors page. The second argument takes Sentry's capture context (`tags`,
|
|
3787
|
+
* `extra`, `level`, `fingerprint`, `contexts`, `user`); any other key is attached to the occurrence
|
|
3788
|
+
* as a property. Needs the project's error tracking on (plus analytics + consent);
|
|
3789
|
+
* `captureErrors: false` does not stop it. Returns the event id (empty when nothing is sent).
|
|
2884
3790
|
*/
|
|
2885
|
-
captureException(error,
|
|
2886
|
-
if (this.destroyed) return;
|
|
2887
|
-
const
|
|
3791
|
+
captureException(error, hint) {
|
|
3792
|
+
if (this.destroyed) return "";
|
|
3793
|
+
const { ctx, properties } = splitHint(hint);
|
|
3794
|
+
const props = exceptionFromError(error, "manual", ctx.mechanism?.handled ?? true);
|
|
3795
|
+
return this.captureWith(props, properties, this.errorDetail(error, props, ctx.mechanism?.type ?? "generic", ctx, ctx.level ?? "error"));
|
|
3796
|
+
}
|
|
3797
|
+
/** Reports a message (default level `info`) as its own issue, grouped by the message. */
|
|
3798
|
+
captureMessage(message, level = "info", hint) {
|
|
3799
|
+
if (this.destroyed) return "";
|
|
3800
|
+
const props = { type: "Message", message: String(message).slice(0, 1e3), frames: [], source: "message", handled: true };
|
|
3801
|
+
return this.captureWith(props, {}, this.errorDetail(void 0, props, "message", hint ?? {}, hint?.level ?? level));
|
|
3802
|
+
}
|
|
3803
|
+
captureWith(props, properties, detail) {
|
|
2888
3804
|
if (!this.config) {
|
|
2889
|
-
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, properties]);
|
|
2890
|
-
return;
|
|
3805
|
+
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, properties, detail]);
|
|
3806
|
+
return "";
|
|
2891
3807
|
}
|
|
2892
|
-
this.sendException(props, properties);
|
|
3808
|
+
return this.sendException(props, properties, detail);
|
|
3809
|
+
}
|
|
3810
|
+
/** The `detail` of an error: scope, breadcrumbs, browser contexts and the `cause` chain. */
|
|
3811
|
+
errorDetail(error, props, mechanism, ctx, level) {
|
|
3812
|
+
const device = detectDevice(navigator.userAgent, window.innerWidth);
|
|
3813
|
+
return buildDetail({ error, props, mechanism, scope: this.errorScope, ctx, level, contexts: browserContexts(navigator.userAgent, device) });
|
|
3814
|
+
}
|
|
3815
|
+
/** Adds a breadcrumb (sent with the next errors; the last 100 are kept). */
|
|
3816
|
+
addBreadcrumb(crumb) {
|
|
3817
|
+
if (!this.destroyed) this.errorScope.addBreadcrumb(crumb);
|
|
3818
|
+
}
|
|
3819
|
+
/** Tags every later error (searchable on the issue page). */
|
|
3820
|
+
setTag(key, value) {
|
|
3821
|
+
if (value === null || value === void 0) delete this.errorScope.tags[key];
|
|
3822
|
+
else this.errorScope.tags[key] = String(value);
|
|
2893
3823
|
}
|
|
2894
|
-
|
|
2895
|
-
|
|
3824
|
+
setTags(tags) {
|
|
3825
|
+
for (const [k, v] of Object.entries(tags)) this.setTag(k, v);
|
|
3826
|
+
}
|
|
3827
|
+
/** Arbitrary data attached to every later error. */
|
|
3828
|
+
setExtra(key, value) {
|
|
3829
|
+
if (value === void 0) delete this.errorScope.extra[key];
|
|
3830
|
+
else this.errorScope.extra[key] = value;
|
|
3831
|
+
}
|
|
3832
|
+
setExtras(extras) {
|
|
3833
|
+
for (const [k, v] of Object.entries(extras)) this.setExtra(k, v);
|
|
3834
|
+
}
|
|
3835
|
+
/** A named context (`setContext("cart", { items: 3 })`) on every later error; null removes it. */
|
|
3836
|
+
setContext(name, context) {
|
|
3837
|
+
if (context) this.errorScope.contexts[name] = context;
|
|
3838
|
+
else delete this.errorScope.contexts[name];
|
|
3839
|
+
}
|
|
3840
|
+
/**
|
|
3841
|
+
* Sentry's `setUser`: identifies the user by `id` (like `identify`) and keeps `email` / `username`
|
|
3842
|
+
* on their errors. `null` forgets the user (`reset()`).
|
|
3843
|
+
*/
|
|
3844
|
+
setUser(user) {
|
|
3845
|
+
if (!user) {
|
|
3846
|
+
this.errorScope.user = {};
|
|
3847
|
+
return this.reset();
|
|
3848
|
+
}
|
|
3849
|
+
const { id, email, username, ...traits } = user;
|
|
3850
|
+
this.errorScope.user = { ...email ? { email: String(email) } : {}, ...username ? { username: String(username) } : {} };
|
|
3851
|
+
if (id !== void 0 && id !== null && String(id)) void this.identify(String(id), { ...traits, ...email ? { email } : {}, ...username ? { username } : {} });
|
|
3852
|
+
}
|
|
3853
|
+
sendException(props, extra, detail) {
|
|
3854
|
+
if (!this.config?.settings?.errorTracking?.enabled || !this.captureAllowed()) return "";
|
|
3855
|
+
const o = this.options.errors;
|
|
3856
|
+
const file = props.frames.find((f) => f.file)?.file;
|
|
3857
|
+
if (o && (0, import_runtime23.errorFilterDrops)({ type: props.type, message: props.message, file }, o)) return "";
|
|
3858
|
+
if (o?.sampleRate !== void 0 && o.sampleRate < 1 && Math.random() >= Math.max(0, o.sampleRate)) return "";
|
|
3859
|
+
if (!this.exceptionThrottle.allow(props)) return "";
|
|
2896
3860
|
const count = this.session.exceptions ?? 0;
|
|
2897
|
-
if (count >= MAX_SESSION_EXCEPTIONS) return;
|
|
2898
|
-
const
|
|
3861
|
+
if (count >= MAX_SESSION_EXCEPTIONS) return "";
|
|
3862
|
+
const mechanism = detail?.exception?.values.at(-1)?.mechanism?.type;
|
|
3863
|
+
let properties = { ...props, level: detail?.level ?? "error", platform: "javascript", ...mechanism ? { mechanism } : {} };
|
|
2899
3864
|
if (this.options.release) properties.release = String(this.options.release).slice(0, 100);
|
|
3865
|
+
const trace = this.tracer?.traceContext();
|
|
3866
|
+
if (trace) Object.assign(properties, { traceId: trace.traceId, spanId: trace.spanId });
|
|
2900
3867
|
for (const [k, v] of Object.entries(extra ?? {})) if (!(k in properties)) properties[k] = v;
|
|
2901
|
-
|
|
3868
|
+
let d = detail;
|
|
3869
|
+
if (o?.beforeSend) {
|
|
3870
|
+
try {
|
|
3871
|
+
const out = o.beforeSend({ properties, detail: d ?? {} });
|
|
3872
|
+
if (!out) return "";
|
|
3873
|
+
properties = out.properties;
|
|
3874
|
+
d = out.detail;
|
|
3875
|
+
} catch (err) {
|
|
3876
|
+
this.reportError(err);
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
const event = this.enqueue("exception", { properties, ...d ? { detail: d } : {} });
|
|
3880
|
+
if (!event) return "";
|
|
3881
|
+
this.lastExceptionId = event.eventId;
|
|
2902
3882
|
this.session.exceptions = count + 1;
|
|
2903
3883
|
this.persistSession();
|
|
3884
|
+
return event.eventId;
|
|
2904
3885
|
}
|
|
2905
3886
|
maskSelector() {
|
|
2906
3887
|
return validSelectors([...DEFAULT_MASK_SELECTORS, ...this.config?.settings?.maskSelectors || [], ...this.options.maskSelectors || []]).join(",");
|
|
@@ -2934,9 +3915,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2934
3915
|
this.trackPageView(this.pageHref());
|
|
2935
3916
|
}
|
|
2936
3917
|
if (this.config) {
|
|
2937
|
-
for (const [props, extra] of this.earlyExceptions.splice(0)) {
|
|
2938
|
-
if (props.handled) this.sendException(props, extra);
|
|
2939
|
-
else this.captureGlobalException(props);
|
|
3918
|
+
for (const [props, extra, detail] of this.earlyExceptions.splice(0)) {
|
|
3919
|
+
if (props.handled) this.sendException(props, extra, detail);
|
|
3920
|
+
else this.captureGlobalException(props, detail);
|
|
2940
3921
|
}
|
|
2941
3922
|
}
|
|
2942
3923
|
this.syncReplay();
|
|
@@ -2944,7 +3925,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2944
3925
|
syncReplay() {
|
|
2945
3926
|
const cfg = this.isReady && this.captureAllowed() ? this.replayConfig() : null;
|
|
2946
3927
|
if (cfg && this.session.replay === void 0) {
|
|
2947
|
-
this.session.replay = (0,
|
|
3928
|
+
this.session.replay = (0, import_runtime23.rolloutBucket)(this.session.id, "replay") < cfg.sampleRate;
|
|
2948
3929
|
this.persistSession();
|
|
2949
3930
|
}
|
|
2950
3931
|
if (!cfg || !this.session.replay) {
|
|
@@ -2969,7 +3950,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2969
3950
|
url: this.collectUrls() ? this.pageHref() : void 0,
|
|
2970
3951
|
device: { type: device.type, browser: device.browser, os: device.os },
|
|
2971
3952
|
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
2972
|
-
sdk: { name: SDK_NAME, version: SDK_VERSION }
|
|
3953
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
3954
|
+
...this.appVersion() ? { appVersion: this.appVersion() } : {}
|
|
2973
3955
|
};
|
|
2974
3956
|
},
|
|
2975
3957
|
onDisabled: (sessionId) => {
|
|
@@ -3043,7 +4025,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3043
4025
|
seenGuides: keysOf(Object.keys(guides).filter((id) => (guides[id]?.impressions ?? 0) > 0)),
|
|
3044
4026
|
completedChecklists: keysOf(completedChecklists),
|
|
3045
4027
|
audienceIds: this.state.audienceIds,
|
|
3046
|
-
environment: this.
|
|
4028
|
+
environment: this.environment,
|
|
3047
4029
|
platform: "web",
|
|
3048
4030
|
...this.options.appVersion ? { appVersion: this.options.appVersion } : {}
|
|
3049
4031
|
});
|
|
@@ -3055,7 +4037,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3055
4037
|
if (!isWithinSchedule(guide)) return false;
|
|
3056
4038
|
if (guide.kind === "survey" && (this.state.surveysSubmitted.includes(guide.id) || this.state.surveysSubmitted.includes(guide.key))) return false;
|
|
3057
4039
|
ctx.rolloutSalt = guide.id;
|
|
3058
|
-
return (0,
|
|
4040
|
+
return (0, import_runtime23.evaluateRules)(c.targeting, ctx);
|
|
3059
4041
|
}
|
|
3060
4042
|
/** Guide throttling: may this guide launch automatically now? */
|
|
3061
4043
|
throttleAllows(guide) {
|
|
@@ -3079,14 +4061,14 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3079
4061
|
/** The visitor's language candidates, most preferred first. */
|
|
3080
4062
|
locales() {
|
|
3081
4063
|
const nav = typeof navigator !== "undefined" ? [...navigator.languages ?? [], navigator.language] : [];
|
|
3082
|
-
return (0,
|
|
4064
|
+
return (0, import_runtime23.visitorLocales)(this.locale ?? this.options.locale, this.user?.traits, nav);
|
|
3083
4065
|
}
|
|
3084
4066
|
defaultLocale() {
|
|
3085
4067
|
return this.config?.settings.localization?.defaultLocale || "en";
|
|
3086
4068
|
}
|
|
3087
4069
|
/** The guide in the visitor's language (remembered so its events carry `locale`). */
|
|
3088
4070
|
localize(guide) {
|
|
3089
|
-
const localized = (0,
|
|
4071
|
+
const localized = (0, import_runtime23.localizeGuide)(guide, this.locales(), this.defaultLocale());
|
|
3090
4072
|
this.guideLocales.set(guide.id, localized.locale);
|
|
3091
4073
|
return localized;
|
|
3092
4074
|
}
|
|
@@ -3095,7 +4077,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3095
4077
|
* Guides already on screen keep their language; the resource center re-renders.
|
|
3096
4078
|
*/
|
|
3097
4079
|
setLocale(locale) {
|
|
3098
|
-
this.locale = locale ? (0,
|
|
4080
|
+
this.locale = locale ? (0, import_runtime23.normalizeLocale)(locale) || null : null;
|
|
3099
4081
|
if (this.resourceCenter) {
|
|
3100
4082
|
this.resourceCenter.destroy();
|
|
3101
4083
|
this.resourceCenterScope?.destroy();
|
|
@@ -3206,17 +4188,17 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3206
4188
|
this.evaluate("visible", { guideId: guide.id });
|
|
3207
4189
|
};
|
|
3208
4190
|
if (typeof IntersectionObserver === "undefined") return fire();
|
|
3209
|
-
io = new IntersectionObserver((
|
|
3210
|
-
if (
|
|
4191
|
+
io = new IntersectionObserver((entries2) => {
|
|
4192
|
+
if (entries2.some((e) => e.isIntersecting)) fire();
|
|
3211
4193
|
});
|
|
3212
4194
|
io.observe(el);
|
|
3213
4195
|
}).catch(() => stop());
|
|
3214
4196
|
}
|
|
3215
|
-
setTimer(fn,
|
|
4197
|
+
setTimer(fn, ms2) {
|
|
3216
4198
|
const t = setTimeout(() => {
|
|
3217
4199
|
this.timers.delete(t);
|
|
3218
4200
|
if (!this.destroyed) fn();
|
|
3219
|
-
},
|
|
4201
|
+
}, ms2);
|
|
3220
4202
|
this.timers.add(t);
|
|
3221
4203
|
}
|
|
3222
4204
|
/** Decides whether a guide can show now, otherwise queues it. */
|
|
@@ -3357,6 +4339,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3357
4339
|
if (action === "start_guide" && value) this.setTimer(() => this.startGuide(value), 0);
|
|
3358
4340
|
else if (action === "event" && value) this.track(value);
|
|
3359
4341
|
else if (action === "open_resource_center") this.openResourceCenter();
|
|
4342
|
+
else if (action === "open_article" && value) this.openArticle(value);
|
|
3360
4343
|
},
|
|
3361
4344
|
onEnd: (reason) => {
|
|
3362
4345
|
if (exclusive) {
|
|
@@ -3422,7 +4405,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3422
4405
|
title: guide.name,
|
|
3423
4406
|
onDismiss: () => finish(this.state.surveysSubmitted.includes(guide.id) ? "submitted" : "dismissed"),
|
|
3424
4407
|
onSubmit: (answers) => {
|
|
3425
|
-
const responseId = (0,
|
|
4408
|
+
const responseId = (0, import_runtime23.randomId)(24);
|
|
3426
4409
|
this.state.surveysSubmitted.push(guide.id);
|
|
3427
4410
|
this.persistState();
|
|
3428
4411
|
this.recordEnd(guide, "completed");
|
|
@@ -3469,8 +4452,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3469
4452
|
this.resourceCenterScope = scope;
|
|
3470
4453
|
this.resourceCenter = (0, import_guide_runtime5.renderResourceCenter)({
|
|
3471
4454
|
scope: scope.el,
|
|
3472
|
-
config: (0,
|
|
3473
|
-
evaluate: (rules) => (0,
|
|
4455
|
+
config: (0, import_runtime23.localizeResourceCenter)(rc, this.locales(), this.defaultLocale()),
|
|
4456
|
+
evaluate: (rules) => (0, import_runtime23.evaluateRules)(rules, this.getContext()),
|
|
3474
4457
|
announcementsSeenAt: () => this.state.announcementsSeenAt ?? null,
|
|
3475
4458
|
onAnnouncementsSeen: () => {
|
|
3476
4459
|
this.state.announcementsSeenAt = nowIso();
|
|
@@ -3532,8 +4515,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3532
4515
|
const seen = readJson(this.store, KEYS.newsSeen(this.options.projectId), []);
|
|
3533
4516
|
writeJson(this.store, KEYS.newsSeen(this.options.projectId), Array.from(/* @__PURE__ */ new Set([...ids, ...Array.isArray(seen) ? seen : []])).slice(0, 200));
|
|
3534
4517
|
},
|
|
3535
|
-
resourceCenter: this.config.resourceCenter ? (0,
|
|
3536
|
-
evaluate: (rules) => (0,
|
|
4518
|
+
resourceCenter: this.config.resourceCenter ? (0, import_runtime23.localizeResourceCenter)(this.config.resourceCenter, this.locales(), this.defaultLocale()) : null,
|
|
4519
|
+
evaluate: (rules) => (0, import_runtime23.evaluateRules)(rules, this.getContext()),
|
|
3537
4520
|
audienceIds: () => this.state.audienceIds,
|
|
3538
4521
|
guides: this.config.guides.map((g) => ({ id: g.id, key: g.key, name: g.name, kind: g.kind })),
|
|
3539
4522
|
onStartGuide: (idOrKey) => this.startGuide(idOrKey),
|
|
@@ -3568,12 +4551,12 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3568
4551
|
isAudienceTargeted: (g) => {
|
|
3569
4552
|
if (!isWithinSchedule(g)) return false;
|
|
3570
4553
|
ctx.rolloutSalt = g.id;
|
|
3571
|
-
return (0,
|
|
4554
|
+
return (0, import_runtime23.evaluateRules)(g.config.targeting, ctx);
|
|
3572
4555
|
},
|
|
3573
4556
|
newsSeen: Array.isArray(seen) ? seen : []
|
|
3574
4557
|
});
|
|
3575
4558
|
const rc = this.config?.resourceCenter;
|
|
3576
|
-
const rcNews = rc?.announcements?.length ? (0,
|
|
4559
|
+
const rcNews = rc?.announcements?.length ? (0, import_runtime23.visibleAnnouncements)((0, import_runtime23.localizeResourceCenter)(rc, this.locales(), this.defaultLocale()).announcements, this.state.audienceIds).map((a) => ({
|
|
3577
4560
|
id: `rc_${a.id}`,
|
|
3578
4561
|
key: a.guideId ?? "",
|
|
3579
4562
|
title: a.title,
|
|
@@ -3597,7 +4580,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3597
4580
|
rcLocale() {
|
|
3598
4581
|
const rc = this.config?.resourceCenter;
|
|
3599
4582
|
if (!rc) return null;
|
|
3600
|
-
const locale = (0,
|
|
4583
|
+
const locale = (0, import_runtime23.localizeResourceCenter)(rc, this.locales(), this.defaultLocale()).locale;
|
|
3601
4584
|
return locale.toLowerCase() === this.defaultLocale().toLowerCase() ? null : locale;
|
|
3602
4585
|
}
|
|
3603
4586
|
async loadArticle(id) {
|
|
@@ -3767,6 +4750,19 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3767
4750
|
if (!this.resourceCenter) this.renderPersistentWidgets();
|
|
3768
4751
|
this.resourceCenter?.open();
|
|
3769
4752
|
}
|
|
4753
|
+
/**
|
|
4754
|
+
* Opens one help article: in Scout's Help tab when the messenger is on, otherwise in the resource
|
|
4755
|
+
* center. Help-article links in guides (`#guidely-article:<id>`) call this.
|
|
4756
|
+
*/
|
|
4757
|
+
openArticle(articleId) {
|
|
4758
|
+
if (this.config?.messenger?.enabled) {
|
|
4759
|
+
void this.loadMessenger().then((m) => m?.openArticle(articleId));
|
|
4760
|
+
return;
|
|
4761
|
+
}
|
|
4762
|
+
if (!this.resourceCenter) this.renderPersistentWidgets();
|
|
4763
|
+
if (this.resourceCenter) this.resourceCenter.openArticle(articleId);
|
|
4764
|
+
else this.log("openArticle: the resource center is not enabled or published for this environment");
|
|
4765
|
+
}
|
|
3770
4766
|
closeResourceCenter() {
|
|
3771
4767
|
if (this.messenger) return this.closeMessenger();
|
|
3772
4768
|
this.resourceCenter?.close();
|
|
@@ -3807,6 +4803,49 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3807
4803
|
if (!this.config?.messenger?.enabled || !this.config.messenger.ideas) return;
|
|
3808
4804
|
void this.loadMessenger().then((m) => m?.openIdeas(requestId));
|
|
3809
4805
|
}
|
|
4806
|
+
/**
|
|
4807
|
+
* Sends a bug report to Monitor → User feedback (Sentry's `captureFeedback`). It is linked to the visitor,
|
|
4808
|
+
* this session's recording and, unless `associatedEventId` says otherwise, the last error this page
|
|
4809
|
+
* reported. Resolves with the report's id; rejects when it can't be sent.
|
|
4810
|
+
*/
|
|
4811
|
+
async captureFeedback(input, source = "api") {
|
|
4812
|
+
if (this.destroyed) throw new Error("[Guidely] captureFeedback: the SDK was destroyed");
|
|
4813
|
+
if (!input || !String(input.message ?? "").trim()) throw new Error("[Guidely] captureFeedback: message is required");
|
|
4814
|
+
const screenshot = await encodeScreenshot(input.screenshot);
|
|
4815
|
+
if (this.lastExceptionId) await this.queue.flush().catch(() => {
|
|
4816
|
+
});
|
|
4817
|
+
const body = bugReportBody(
|
|
4818
|
+
input,
|
|
4819
|
+
{ anonymousId: this.anonymousId, userId: this.user?.id || null, sessionId: this.session.id, url: this.collectUrls() ? this.pageHref() : void 0, appVersion: this.appVersion(), lastExceptionId: this.lastExceptionId, sdk: { name: SDK_NAME, version: SDK_VERSION } },
|
|
4820
|
+
source,
|
|
4821
|
+
screenshot
|
|
4822
|
+
);
|
|
4823
|
+
return sendBugReport(this.api, body);
|
|
4824
|
+
}
|
|
4825
|
+
/** Opens the "Report a bug" dialog (message, name, email, a screenshot), which sends with captureFeedback. */
|
|
4826
|
+
showBugReport(options = {}) {
|
|
4827
|
+
if (this.destroyed || typeof document === "undefined" || this.bugReportDialog) return;
|
|
4828
|
+
void import("@guidelytech/guide-runtime/bug-report").then(({ renderBugReport }) => {
|
|
4829
|
+
if (this.destroyed || this.bugReportDialog) return;
|
|
4830
|
+
const scope = (0, import_guide_runtime6.createScope)(this.theme());
|
|
4831
|
+
const traits = this.user?.traits ?? {};
|
|
4832
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
4833
|
+
this.bugReportDialog = renderBugReport({
|
|
4834
|
+
scope: scope.el,
|
|
4835
|
+
title: options.title,
|
|
4836
|
+
message: options.message,
|
|
4837
|
+
name: options.name ?? str(traits.name),
|
|
4838
|
+
email: options.email ?? str(traits.email),
|
|
4839
|
+
onSubmit: async (i) => {
|
|
4840
|
+
await this.captureFeedback({ message: i.message, name: i.name || null, email: i.email || null, screenshot: i.screenshot }, "widget");
|
|
4841
|
+
},
|
|
4842
|
+
onClose: () => {
|
|
4843
|
+
this.bugReportDialog = null;
|
|
4844
|
+
scope.destroy();
|
|
4845
|
+
}
|
|
4846
|
+
});
|
|
4847
|
+
}).catch((err) => this.reportError(err));
|
|
4848
|
+
}
|
|
3810
4849
|
/** Opens a new messenger conversation, optionally pre-filling the message. */
|
|
3811
4850
|
showNewMessage(text) {
|
|
3812
4851
|
if (!this.config?.messenger?.enabled) return;
|
|
@@ -3837,12 +4876,12 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3837
4876
|
const account = this.user?.account;
|
|
3838
4877
|
return {
|
|
3839
4878
|
url: this.pageHref(),
|
|
3840
|
-
path:
|
|
4879
|
+
path: pathOf3(this.pageHref()),
|
|
3841
4880
|
deviceType: device.type,
|
|
3842
4881
|
browser: device.browser,
|
|
3843
4882
|
os: device.os,
|
|
3844
4883
|
locale: navigator.language || void 0,
|
|
3845
|
-
environment: this.
|
|
4884
|
+
environment: this.environment,
|
|
3846
4885
|
platform: "web",
|
|
3847
4886
|
...this.options.appVersion ? { appVersion: this.options.appVersion } : {},
|
|
3848
4887
|
...this.user?.traits ?? {},
|
|
@@ -4017,9 +5056,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
4017
5056
|
};
|
|
4018
5057
|
var current2 = null;
|
|
4019
5058
|
function webOnly(config) {
|
|
4020
|
-
return config.guides.every((g) => (0,
|
|
5059
|
+
return config.guides.every((g) => (0, import_runtime23.supportsPlatform)(g.config, "web")) ? config : { ...config, guides: config.guides.filter((g) => (0, import_runtime23.supportsPlatform)(g.config, "web")) };
|
|
4021
5060
|
}
|
|
4022
|
-
function
|
|
5061
|
+
function pathOf3(url) {
|
|
4023
5062
|
try {
|
|
4024
5063
|
return new URL(url, "http://localhost").pathname;
|
|
4025
5064
|
} catch {
|
|
@@ -4070,6 +5109,7 @@ var index_default = Guidely;
|
|
|
4070
5109
|
createGuidely,
|
|
4071
5110
|
createStorage,
|
|
4072
5111
|
detectDevice,
|
|
5112
|
+
environmentFromKey,
|
|
4073
5113
|
isAppUrl,
|
|
4074
5114
|
isExclusiveGuide,
|
|
4075
5115
|
isFrequencyEligible,
|