@guidelytech/sdk-core 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -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 +1133 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -10
- package/dist/index.d.ts +66 -10
- package/dist/index.js +1064 -70
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-2E2YUQAG.js.map +0 -1
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 || (0,
|
|
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);
|
|
@@ -2386,7 +3262,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2386
3262
|
this.cleanups.push(() => doc.removeEventListener("click", onClick, true));
|
|
2387
3263
|
const onArticleLink = (e) => {
|
|
2388
3264
|
if (e.defaultPrevented) return;
|
|
2389
|
-
const articleId = (0,
|
|
3265
|
+
const articleId = (0, import_runtime23.articleIdFromHref)((0, import_guide_runtime5.composedTarget)(e)?.closest?.("a[href]")?.getAttribute("href"));
|
|
2390
3266
|
if (!articleId) return;
|
|
2391
3267
|
e.preventDefault();
|
|
2392
3268
|
this.openArticle(articleId);
|
|
@@ -2414,9 +3290,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2414
3290
|
};
|
|
2415
3291
|
let lastScroll = 0;
|
|
2416
3292
|
const onScroll = () => {
|
|
2417
|
-
const
|
|
2418
|
-
if (
|
|
2419
|
-
lastScroll =
|
|
3293
|
+
const now3 = Date.now();
|
|
3294
|
+
if (now3 - lastScroll < 250) return;
|
|
3295
|
+
lastScroll = now3;
|
|
2420
3296
|
this.pageTracker.measureScroll();
|
|
2421
3297
|
this.touchSession();
|
|
2422
3298
|
};
|
|
@@ -2474,14 +3350,22 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2474
3350
|
/** Uncaught errors + unhandled rejections; attached before the config loads so boot errors are kept. */
|
|
2475
3351
|
wireErrorListeners() {
|
|
2476
3352
|
const win = window;
|
|
2477
|
-
const onError = (e) => this.captureGlobalException(errorEventToException(e));
|
|
2478
|
-
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);
|
|
2479
3355
|
win.addEventListener("error", onError);
|
|
2480
3356
|
win.addEventListener("unhandledrejection", onRejection);
|
|
2481
3357
|
this.cleanups.push(() => {
|
|
2482
3358
|
win.removeEventListener("error", onError);
|
|
2483
3359
|
win.removeEventListener("unhandledrejection", onRejection);
|
|
2484
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 });
|
|
2485
3369
|
}
|
|
2486
3370
|
/** Re-fetches the published config and re-evaluates triggers. */
|
|
2487
3371
|
async refresh() {
|
|
@@ -2529,6 +3413,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2529
3413
|
this.queue.destroy();
|
|
2530
3414
|
this.outbox.destroy();
|
|
2531
3415
|
this.agentTurns.destroy();
|
|
3416
|
+
this.tracer?.destroy();
|
|
2532
3417
|
this.flags.destroy();
|
|
2533
3418
|
this.emitter.clear();
|
|
2534
3419
|
(0, import_guide_runtime5.destroyHost)();
|
|
@@ -2544,7 +3429,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2544
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) };
|
|
2545
3430
|
writeJson(this.store, KEYS.user(this.options.projectId), this.user);
|
|
2546
3431
|
this.syncFlagAttributes();
|
|
2547
|
-
this.enqueue("identify", { properties: { traits: (0,
|
|
3432
|
+
this.enqueue("identify", { properties: { traits: (0, import_runtime23.sanitizeProperties)(this.user.traits) } });
|
|
2548
3433
|
await this.syncIdentify();
|
|
2549
3434
|
this.emitter.emit("identify", { userId: this.user.id, anonymousId: this.anonymousId });
|
|
2550
3435
|
if (this.isReady) this.evaluate("identify");
|
|
@@ -2620,7 +3505,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2620
3505
|
this.stopGuide();
|
|
2621
3506
|
this.user = null;
|
|
2622
3507
|
this.store.remove(KEYS.user(this.options.projectId));
|
|
2623
|
-
this.anonymousId = `anon_${(0,
|
|
3508
|
+
this.anonymousId = `anon_${(0, import_runtime23.randomId)(20)}`;
|
|
2624
3509
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
2625
3510
|
this.state = emptyState();
|
|
2626
3511
|
this.persistState();
|
|
@@ -2658,6 +3543,11 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2658
3543
|
analyticsEnabled() {
|
|
2659
3544
|
return this.options.analytics && this.consent === "granted" && (this.config?.settings?.analyticsEnabled ?? true);
|
|
2660
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
|
+
}
|
|
2661
3551
|
collectUrls() {
|
|
2662
3552
|
return this.config?.settings?.collectUrls ?? true;
|
|
2663
3553
|
}
|
|
@@ -2665,7 +3555,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2665
3555
|
if (this.consent === "denied" || !this.options.analytics) return null;
|
|
2666
3556
|
if (name !== "page_leave" && name !== "heatmap") this.touchSession();
|
|
2667
3557
|
const event = {
|
|
2668
|
-
eventId: (0,
|
|
3558
|
+
eventId: (0, import_runtime23.randomId)(24),
|
|
2669
3559
|
name,
|
|
2670
3560
|
occurredAt: nowIso(),
|
|
2671
3561
|
anonymousId: this.anonymousId,
|
|
@@ -2673,12 +3563,14 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2673
3563
|
accountId: this.user?.account?.id || null,
|
|
2674
3564
|
sessionId: this.session.id,
|
|
2675
3565
|
url: this.collectUrls() ? this.pageHref() : void 0,
|
|
3566
|
+
...this.appVersion() ? { appVersion: this.appVersion() } : {},
|
|
2676
3567
|
...extra
|
|
2677
3568
|
};
|
|
2678
|
-
if (extra.properties) event.properties = (0,
|
|
3569
|
+
if (extra.properties) event.properties = (0, import_runtime23.sanitizeProperties)(extra.properties);
|
|
2679
3570
|
const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
|
|
2680
3571
|
if (locale) event.properties = { ...event.properties ?? {}, locale };
|
|
2681
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 } : {} } });
|
|
2682
3574
|
this.replay?.addCustomEvent("guidely", { name, customName: event.customName, guideId: event.guideId });
|
|
2683
3575
|
this.emitter.emit("track", event);
|
|
2684
3576
|
return event;
|
|
@@ -2732,6 +3624,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2732
3624
|
handleNavigation(url, explicit = false) {
|
|
2733
3625
|
if (this.destroyed) return;
|
|
2734
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) } : {} });
|
|
2735
3628
|
this.lastUrl = url;
|
|
2736
3629
|
if (changed) this.syncFlagAttributes();
|
|
2737
3630
|
if (!explicit && changed) {
|
|
@@ -2760,6 +3653,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2760
3653
|
handleDocumentClick(e) {
|
|
2761
3654
|
const target = (0, import_guide_runtime5.composedTarget)(e);
|
|
2762
3655
|
if (!target || typeof target.closest !== "function") return;
|
|
3656
|
+
if (!isIgnoredElement(target)) this.autoBreadcrumb("clicks", { category: "ui.click", message: elementSelector(interactiveElement(target) ?? target) }, e);
|
|
2763
3657
|
this.autocaptureClick(e, target);
|
|
2764
3658
|
this.completeChecklistTasksBy("click", target);
|
|
2765
3659
|
if (this.isReady) this.evaluate("click", { target });
|
|
@@ -2769,17 +3663,17 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2769
3663
|
// ---------------------------------------------------------------------------
|
|
2770
3664
|
/** Records activity; starts a new session after `sessionTimeoutMinutes` of inactivity or 24h. */
|
|
2771
3665
|
touchSession() {
|
|
2772
|
-
const
|
|
3666
|
+
const now3 = Date.now();
|
|
2773
3667
|
const s = this.session;
|
|
2774
3668
|
const timeout = Math.max(1, this.config?.settings?.sessionTimeoutMinutes ?? 30) * 6e4;
|
|
2775
|
-
const last = s.lastActivityAt ??
|
|
2776
|
-
if (
|
|
2777
|
-
if (
|
|
2778
|
-
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;
|
|
2779
3673
|
this.persistSession();
|
|
2780
3674
|
}
|
|
2781
|
-
rotateSession(
|
|
2782
|
-
this.session = newSession(
|
|
3675
|
+
rotateSession(now3) {
|
|
3676
|
+
this.session = newSession(now3);
|
|
2783
3677
|
this.persistSession();
|
|
2784
3678
|
this.log("session rotated", this.session.id);
|
|
2785
3679
|
if (this.replay?.activeSessionId) this.replay.stop();
|
|
@@ -2811,7 +3705,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2811
3705
|
const device = detectDevice(navigator.userAgent, window.innerWidth);
|
|
2812
3706
|
const props = { deviceType: device.type, browser: device.browser, os: device.os, screenWidth: window.screen?.width, viewportWidth: window.innerWidth };
|
|
2813
3707
|
if (collect) {
|
|
2814
|
-
props.path =
|
|
3708
|
+
props.path = pathOf3(url);
|
|
2815
3709
|
props.title = (document.title || "").slice(0, 200);
|
|
2816
3710
|
const ref = !this.session.pageViews ? externalReferrer() : void 0;
|
|
2817
3711
|
if (ref) props.referrer = ref;
|
|
@@ -2830,7 +3724,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2830
3724
|
if (!this.pageLeft && this.autocaptureOn("pageLeave")) {
|
|
2831
3725
|
const info = t.leave();
|
|
2832
3726
|
const collect = this.collectUrls();
|
|
2833
|
-
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 } });
|
|
2834
3728
|
}
|
|
2835
3729
|
if (nextUrl !== null) {
|
|
2836
3730
|
t.start(nextUrl);
|
|
@@ -2874,42 +3768,120 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2874
3768
|
if (!batch || !this.captureAllowed()) return;
|
|
2875
3769
|
const collect = this.collectUrls();
|
|
2876
3770
|
const de = document.documentElement;
|
|
2877
|
-
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 };
|
|
2878
3772
|
if (!collect) delete properties.path;
|
|
2879
3773
|
this.enqueue("heatmap", { url: collect ? batch.url : void 0, properties });
|
|
2880
3774
|
}
|
|
2881
|
-
captureGlobalException(props) {
|
|
3775
|
+
captureGlobalException(props, detail, error) {
|
|
2882
3776
|
if (!props || isGuidelyException(props) || this.options.captureErrors === false) return;
|
|
3777
|
+
const d = detail ?? this.errorDetail(error, props, props.source === "unhandledrejection" ? "onunhandledrejection" : "onerror", {}, "error");
|
|
2883
3778
|
if (!this.config) {
|
|
2884
|
-
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, void 0]);
|
|
3779
|
+
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, void 0, d]);
|
|
2885
3780
|
return;
|
|
2886
3781
|
}
|
|
2887
|
-
this.sendException(props);
|
|
3782
|
+
this.sendException(props, void 0, d);
|
|
2888
3783
|
}
|
|
2889
3784
|
/**
|
|
2890
3785
|
* Reports a caught error as an `exception` event (`source: "manual"`, `handled: true`), grouped
|
|
2891
|
-
* into an issue on the Errors page.
|
|
2892
|
-
*
|
|
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).
|
|
2893
3790
|
*/
|
|
2894
|
-
captureException(error,
|
|
2895
|
-
if (this.destroyed) return;
|
|
2896
|
-
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) {
|
|
2897
3804
|
if (!this.config) {
|
|
2898
|
-
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, properties]);
|
|
2899
|
-
return;
|
|
3805
|
+
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, properties, detail]);
|
|
3806
|
+
return "";
|
|
2900
3807
|
}
|
|
2901
|
-
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);
|
|
3823
|
+
}
|
|
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;
|
|
2902
3831
|
}
|
|
2903
|
-
|
|
2904
|
-
|
|
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 "";
|
|
2905
3860
|
const count = this.session.exceptions ?? 0;
|
|
2906
|
-
if (count >= MAX_SESSION_EXCEPTIONS) return;
|
|
2907
|
-
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 } : {} };
|
|
2908
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 });
|
|
2909
3867
|
for (const [k, v] of Object.entries(extra ?? {})) if (!(k in properties)) properties[k] = v;
|
|
2910
|
-
|
|
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;
|
|
2911
3882
|
this.session.exceptions = count + 1;
|
|
2912
3883
|
this.persistSession();
|
|
3884
|
+
return event.eventId;
|
|
2913
3885
|
}
|
|
2914
3886
|
maskSelector() {
|
|
2915
3887
|
return validSelectors([...DEFAULT_MASK_SELECTORS, ...this.config?.settings?.maskSelectors || [], ...this.options.maskSelectors || []]).join(",");
|
|
@@ -2943,9 +3915,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2943
3915
|
this.trackPageView(this.pageHref());
|
|
2944
3916
|
}
|
|
2945
3917
|
if (this.config) {
|
|
2946
|
-
for (const [props, extra] of this.earlyExceptions.splice(0)) {
|
|
2947
|
-
if (props.handled) this.sendException(props, extra);
|
|
2948
|
-
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);
|
|
2949
3921
|
}
|
|
2950
3922
|
}
|
|
2951
3923
|
this.syncReplay();
|
|
@@ -2953,7 +3925,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2953
3925
|
syncReplay() {
|
|
2954
3926
|
const cfg = this.isReady && this.captureAllowed() ? this.replayConfig() : null;
|
|
2955
3927
|
if (cfg && this.session.replay === void 0) {
|
|
2956
|
-
this.session.replay = (0,
|
|
3928
|
+
this.session.replay = (0, import_runtime23.rolloutBucket)(this.session.id, "replay") < cfg.sampleRate;
|
|
2957
3929
|
this.persistSession();
|
|
2958
3930
|
}
|
|
2959
3931
|
if (!cfg || !this.session.replay) {
|
|
@@ -2978,7 +3950,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2978
3950
|
url: this.collectUrls() ? this.pageHref() : void 0,
|
|
2979
3951
|
device: { type: device.type, browser: device.browser, os: device.os },
|
|
2980
3952
|
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
2981
|
-
sdk: { name: SDK_NAME, version: SDK_VERSION }
|
|
3953
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
3954
|
+
...this.appVersion() ? { appVersion: this.appVersion() } : {}
|
|
2982
3955
|
};
|
|
2983
3956
|
},
|
|
2984
3957
|
onDisabled: (sessionId) => {
|
|
@@ -3052,7 +4025,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3052
4025
|
seenGuides: keysOf(Object.keys(guides).filter((id) => (guides[id]?.impressions ?? 0) > 0)),
|
|
3053
4026
|
completedChecklists: keysOf(completedChecklists),
|
|
3054
4027
|
audienceIds: this.state.audienceIds,
|
|
3055
|
-
environment: this.
|
|
4028
|
+
environment: this.environment,
|
|
3056
4029
|
platform: "web",
|
|
3057
4030
|
...this.options.appVersion ? { appVersion: this.options.appVersion } : {}
|
|
3058
4031
|
});
|
|
@@ -3064,7 +4037,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3064
4037
|
if (!isWithinSchedule(guide)) return false;
|
|
3065
4038
|
if (guide.kind === "survey" && (this.state.surveysSubmitted.includes(guide.id) || this.state.surveysSubmitted.includes(guide.key))) return false;
|
|
3066
4039
|
ctx.rolloutSalt = guide.id;
|
|
3067
|
-
return (0,
|
|
4040
|
+
return (0, import_runtime23.evaluateRules)(c.targeting, ctx);
|
|
3068
4041
|
}
|
|
3069
4042
|
/** Guide throttling: may this guide launch automatically now? */
|
|
3070
4043
|
throttleAllows(guide) {
|
|
@@ -3088,14 +4061,14 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3088
4061
|
/** The visitor's language candidates, most preferred first. */
|
|
3089
4062
|
locales() {
|
|
3090
4063
|
const nav = typeof navigator !== "undefined" ? [...navigator.languages ?? [], navigator.language] : [];
|
|
3091
|
-
return (0,
|
|
4064
|
+
return (0, import_runtime23.visitorLocales)(this.locale ?? this.options.locale, this.user?.traits, nav);
|
|
3092
4065
|
}
|
|
3093
4066
|
defaultLocale() {
|
|
3094
4067
|
return this.config?.settings.localization?.defaultLocale || "en";
|
|
3095
4068
|
}
|
|
3096
4069
|
/** The guide in the visitor's language (remembered so its events carry `locale`). */
|
|
3097
4070
|
localize(guide) {
|
|
3098
|
-
const localized = (0,
|
|
4071
|
+
const localized = (0, import_runtime23.localizeGuide)(guide, this.locales(), this.defaultLocale());
|
|
3099
4072
|
this.guideLocales.set(guide.id, localized.locale);
|
|
3100
4073
|
return localized;
|
|
3101
4074
|
}
|
|
@@ -3104,7 +4077,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3104
4077
|
* Guides already on screen keep their language; the resource center re-renders.
|
|
3105
4078
|
*/
|
|
3106
4079
|
setLocale(locale) {
|
|
3107
|
-
this.locale = locale ? (0,
|
|
4080
|
+
this.locale = locale ? (0, import_runtime23.normalizeLocale)(locale) || null : null;
|
|
3108
4081
|
if (this.resourceCenter) {
|
|
3109
4082
|
this.resourceCenter.destroy();
|
|
3110
4083
|
this.resourceCenterScope?.destroy();
|
|
@@ -3215,17 +4188,17 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3215
4188
|
this.evaluate("visible", { guideId: guide.id });
|
|
3216
4189
|
};
|
|
3217
4190
|
if (typeof IntersectionObserver === "undefined") return fire();
|
|
3218
|
-
io = new IntersectionObserver((
|
|
3219
|
-
if (
|
|
4191
|
+
io = new IntersectionObserver((entries2) => {
|
|
4192
|
+
if (entries2.some((e) => e.isIntersecting)) fire();
|
|
3220
4193
|
});
|
|
3221
4194
|
io.observe(el);
|
|
3222
4195
|
}).catch(() => stop());
|
|
3223
4196
|
}
|
|
3224
|
-
setTimer(fn,
|
|
4197
|
+
setTimer(fn, ms2) {
|
|
3225
4198
|
const t = setTimeout(() => {
|
|
3226
4199
|
this.timers.delete(t);
|
|
3227
4200
|
if (!this.destroyed) fn();
|
|
3228
|
-
},
|
|
4201
|
+
}, ms2);
|
|
3229
4202
|
this.timers.add(t);
|
|
3230
4203
|
}
|
|
3231
4204
|
/** Decides whether a guide can show now, otherwise queues it. */
|
|
@@ -3432,7 +4405,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3432
4405
|
title: guide.name,
|
|
3433
4406
|
onDismiss: () => finish(this.state.surveysSubmitted.includes(guide.id) ? "submitted" : "dismissed"),
|
|
3434
4407
|
onSubmit: (answers) => {
|
|
3435
|
-
const responseId = (0,
|
|
4408
|
+
const responseId = (0, import_runtime23.randomId)(24);
|
|
3436
4409
|
this.state.surveysSubmitted.push(guide.id);
|
|
3437
4410
|
this.persistState();
|
|
3438
4411
|
this.recordEnd(guide, "completed");
|
|
@@ -3479,8 +4452,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3479
4452
|
this.resourceCenterScope = scope;
|
|
3480
4453
|
this.resourceCenter = (0, import_guide_runtime5.renderResourceCenter)({
|
|
3481
4454
|
scope: scope.el,
|
|
3482
|
-
config: (0,
|
|
3483
|
-
evaluate: (rules) => (0,
|
|
4455
|
+
config: (0, import_runtime23.localizeResourceCenter)(rc, this.locales(), this.defaultLocale()),
|
|
4456
|
+
evaluate: (rules) => (0, import_runtime23.evaluateRules)(rules, this.getContext()),
|
|
3484
4457
|
announcementsSeenAt: () => this.state.announcementsSeenAt ?? null,
|
|
3485
4458
|
onAnnouncementsSeen: () => {
|
|
3486
4459
|
this.state.announcementsSeenAt = nowIso();
|
|
@@ -3542,8 +4515,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3542
4515
|
const seen = readJson(this.store, KEYS.newsSeen(this.options.projectId), []);
|
|
3543
4516
|
writeJson(this.store, KEYS.newsSeen(this.options.projectId), Array.from(/* @__PURE__ */ new Set([...ids, ...Array.isArray(seen) ? seen : []])).slice(0, 200));
|
|
3544
4517
|
},
|
|
3545
|
-
resourceCenter: this.config.resourceCenter ? (0,
|
|
3546
|
-
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()),
|
|
3547
4520
|
audienceIds: () => this.state.audienceIds,
|
|
3548
4521
|
guides: this.config.guides.map((g) => ({ id: g.id, key: g.key, name: g.name, kind: g.kind })),
|
|
3549
4522
|
onStartGuide: (idOrKey) => this.startGuide(idOrKey),
|
|
@@ -3578,12 +4551,12 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3578
4551
|
isAudienceTargeted: (g) => {
|
|
3579
4552
|
if (!isWithinSchedule(g)) return false;
|
|
3580
4553
|
ctx.rolloutSalt = g.id;
|
|
3581
|
-
return (0,
|
|
4554
|
+
return (0, import_runtime23.evaluateRules)(g.config.targeting, ctx);
|
|
3582
4555
|
},
|
|
3583
4556
|
newsSeen: Array.isArray(seen) ? seen : []
|
|
3584
4557
|
});
|
|
3585
4558
|
const rc = this.config?.resourceCenter;
|
|
3586
|
-
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) => ({
|
|
3587
4560
|
id: `rc_${a.id}`,
|
|
3588
4561
|
key: a.guideId ?? "",
|
|
3589
4562
|
title: a.title,
|
|
@@ -3607,7 +4580,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3607
4580
|
rcLocale() {
|
|
3608
4581
|
const rc = this.config?.resourceCenter;
|
|
3609
4582
|
if (!rc) return null;
|
|
3610
|
-
const locale = (0,
|
|
4583
|
+
const locale = (0, import_runtime23.localizeResourceCenter)(rc, this.locales(), this.defaultLocale()).locale;
|
|
3611
4584
|
return locale.toLowerCase() === this.defaultLocale().toLowerCase() ? null : locale;
|
|
3612
4585
|
}
|
|
3613
4586
|
async loadArticle(id) {
|
|
@@ -3830,6 +4803,49 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3830
4803
|
if (!this.config?.messenger?.enabled || !this.config.messenger.ideas) return;
|
|
3831
4804
|
void this.loadMessenger().then((m) => m?.openIdeas(requestId));
|
|
3832
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
|
+
}
|
|
3833
4849
|
/** Opens a new messenger conversation, optionally pre-filling the message. */
|
|
3834
4850
|
showNewMessage(text) {
|
|
3835
4851
|
if (!this.config?.messenger?.enabled) return;
|
|
@@ -3860,12 +4876,12 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3860
4876
|
const account = this.user?.account;
|
|
3861
4877
|
return {
|
|
3862
4878
|
url: this.pageHref(),
|
|
3863
|
-
path:
|
|
4879
|
+
path: pathOf3(this.pageHref()),
|
|
3864
4880
|
deviceType: device.type,
|
|
3865
4881
|
browser: device.browser,
|
|
3866
4882
|
os: device.os,
|
|
3867
4883
|
locale: navigator.language || void 0,
|
|
3868
|
-
environment: this.
|
|
4884
|
+
environment: this.environment,
|
|
3869
4885
|
platform: "web",
|
|
3870
4886
|
...this.options.appVersion ? { appVersion: this.options.appVersion } : {},
|
|
3871
4887
|
...this.user?.traits ?? {},
|
|
@@ -4040,9 +5056,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
4040
5056
|
};
|
|
4041
5057
|
var current2 = null;
|
|
4042
5058
|
function webOnly(config) {
|
|
4043
|
-
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")) };
|
|
4044
5060
|
}
|
|
4045
|
-
function
|
|
5061
|
+
function pathOf3(url) {
|
|
4046
5062
|
try {
|
|
4047
5063
|
return new URL(url, "http://localhost").pathname;
|
|
4048
5064
|
} catch {
|
|
@@ -4093,6 +5109,7 @@ var index_default = Guidely;
|
|
|
4093
5109
|
createGuidely,
|
|
4094
5110
|
createStorage,
|
|
4095
5111
|
detectDevice,
|
|
5112
|
+
environmentFromKey,
|
|
4096
5113
|
isAppUrl,
|
|
4097
5114
|
isExclusiveGuide,
|
|
4098
5115
|
isFrequencyEligible,
|