@guidelytech/sdk-core 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/{chunk-2E2YUQAG.js → chunk-GFLVZRTS.js} +28 -3
- package/dist/chunk-GFLVZRTS.js.map +1 -0
- package/dist/{types-Bkw8ErmO.d.cts → environment-D63OzdWf.d.cts} +172 -5
- package/dist/{types-Dy_uPu6S.d.ts → environment-DAT4iYvZ.d.ts} +172 -5
- package/dist/headless.cjs +27 -1
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +3 -1
- package/dist/index.cjs +1155 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -10
- package/dist/index.d.ts +71 -10
- package/dist/index.js +1089 -72
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/dist/capture-CgN69Lxq.d.cts +0 -100
- package/dist/capture-Csz721-A.d.ts +0 -100
- package/dist/chunk-2E2YUQAG.js.map +0 -1
- package/dist/chunk-JUSM7J5N.js +0 -376
- package/dist/chunk-JUSM7J5N.js.map +0 -1
- package/dist/chunk-ZAF2KRJA.js +0 -334
- package/dist/chunk-ZAF2KRJA.js.map +0 -1
- package/dist/health-sbP9JwTE.d.cts +0 -90
- package/dist/health-sbP9JwTE.d.ts +0 -90
- package/dist/http-Cb6KYYFv.d.cts +0 -61
- package/dist/http-D047Ngxk.d.ts +0 -61
- package/dist/types-BDlebXrm.d.ts +0 -314
- package/dist/types-Mtlmlj5C.d.cts +0 -314
package/dist/index.js
CHANGED
|
@@ -5,24 +5,26 @@ import {
|
|
|
5
5
|
areMutuallyExclusive,
|
|
6
6
|
countsTowardThrottle,
|
|
7
7
|
dismissedAfterMerge,
|
|
8
|
+
environmentFromKey,
|
|
8
9
|
isExclusiveGuide,
|
|
9
10
|
isFrequencyEligible,
|
|
10
11
|
isThrottleEligible,
|
|
11
12
|
isWithinSchedule,
|
|
12
13
|
pruneLaunches
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-GFLVZRTS.js";
|
|
14
15
|
import {
|
|
15
16
|
Api,
|
|
16
17
|
KEYS,
|
|
17
18
|
__publicField,
|
|
18
19
|
createStorage,
|
|
20
|
+
isServerFailure,
|
|
19
21
|
postWithRetry,
|
|
20
22
|
readJson,
|
|
21
23
|
writeJson
|
|
22
24
|
} from "./chunk-XML74K2P.js";
|
|
23
25
|
|
|
24
26
|
// src/client.ts
|
|
25
|
-
import { evaluateRules, localizeGuide, localizeResourceCenter, normalizeLocale, randomId as randomId2, rolloutBucket, sanitizeProperties, supportsPlatform, visibleAnnouncements, visitorLocales } from "@guidelytech/shared/runtime";
|
|
27
|
+
import { articleIdFromHref, errorFilterDrops, evaluateRules, localizeGuide, localizeResourceCenter, normalizeLocale, randomId as randomId2, rolloutBucket, sanitizeProperties, supportsPlatform, visibleAnnouncements, visitorLocales } from "@guidelytech/shared/runtime";
|
|
26
28
|
import {
|
|
27
29
|
closestComposed as closestComposed4,
|
|
28
30
|
composedTarget,
|
|
@@ -572,10 +574,10 @@ var AuthoringBridge = class {
|
|
|
572
574
|
this.observer.observe(document.documentElement, { subtree: true, childList: true, attributes: true, characterData: true });
|
|
573
575
|
}
|
|
574
576
|
scheduleSnapshot() {
|
|
575
|
-
const
|
|
576
|
-
if (!this.snapshotTimer) this.snapshotDue =
|
|
577
|
+
const now3 = Date.now();
|
|
578
|
+
if (!this.snapshotTimer) this.snapshotDue = now3 + SNAPSHOT_MAX_WAIT_MS;
|
|
577
579
|
else clearTimeout(this.snapshotTimer);
|
|
578
|
-
this.snapshotTimer = setTimeout(() => this.sendSnapshot(), Math.max(0, Math.min(SNAPSHOT_DEBOUNCE_MS, this.snapshotDue -
|
|
580
|
+
this.snapshotTimer = setTimeout(() => this.sendSnapshot(), Math.max(0, Math.min(SNAPSHOT_DEBOUNCE_MS, this.snapshotDue - now3)));
|
|
579
581
|
}
|
|
580
582
|
sendSnapshot() {
|
|
581
583
|
if (this.snapshotTimer) clearTimeout(this.snapshotTimer);
|
|
@@ -880,12 +882,12 @@ function describe(value) {
|
|
|
880
882
|
}
|
|
881
883
|
}
|
|
882
884
|
function exceptionFromError(error, source, handled, fallbackType = "Error") {
|
|
883
|
-
const
|
|
885
|
+
const isError2 = error instanceof Error || !!error && typeof error === "object" && typeof error.message === "string" && typeof error.stack === "string";
|
|
884
886
|
const e = error;
|
|
885
887
|
return {
|
|
886
|
-
type:
|
|
887
|
-
message: (
|
|
888
|
-
frames:
|
|
888
|
+
type: isError2 && e.name || fallbackType,
|
|
889
|
+
message: (isError2 ? e.message : describe(error)).slice(0, 1e3),
|
|
890
|
+
frames: isError2 ? fitFrames(parseStack(e.stack)) : [],
|
|
889
891
|
source,
|
|
890
892
|
handled
|
|
891
893
|
};
|
|
@@ -935,6 +937,222 @@ var ExceptionThrottle = class {
|
|
|
935
937
|
}
|
|
936
938
|
};
|
|
937
939
|
|
|
940
|
+
// src/error-monitoring.ts
|
|
941
|
+
import { MAX_BREADCRUMBS, MAX_EXCEPTION_CHAIN, normalizeTags, parseStack as parseStack2, trimExceptionDetail } from "@guidelytech/shared/runtime";
|
|
942
|
+
var CONTEXT_KEYS = ["tags", "extra", "level", "fingerprint", "contexts", "user", "mechanism"];
|
|
943
|
+
function splitHint(hint) {
|
|
944
|
+
const ctx = {};
|
|
945
|
+
const properties = {};
|
|
946
|
+
for (const [k, v] of Object.entries(hint ?? {})) (CONTEXT_KEYS.includes(k) ? ctx : properties)[k] = v;
|
|
947
|
+
return { ctx, properties };
|
|
948
|
+
}
|
|
949
|
+
var now = () => Date.now() / 1e3;
|
|
950
|
+
var ErrorScope = class {
|
|
951
|
+
constructor(opts) {
|
|
952
|
+
__publicField(this, "opts", opts);
|
|
953
|
+
__publicField(this, "tags", {});
|
|
954
|
+
__publicField(this, "extra", {});
|
|
955
|
+
__publicField(this, "contexts", {});
|
|
956
|
+
__publicField(this, "user", {});
|
|
957
|
+
__publicField(this, "crumbs", []);
|
|
958
|
+
}
|
|
959
|
+
get max() {
|
|
960
|
+
return Math.max(0, Math.min(MAX_BREADCRUMBS, this.opts()?.maxBreadcrumbs ?? MAX_BREADCRUMBS));
|
|
961
|
+
}
|
|
962
|
+
addBreadcrumb(crumb, hint) {
|
|
963
|
+
let c = { timestamp: now(), ...crumb };
|
|
964
|
+
const before = this.opts()?.beforeBreadcrumb;
|
|
965
|
+
if (before) {
|
|
966
|
+
try {
|
|
967
|
+
c = before(c, hint);
|
|
968
|
+
} catch {
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (!c || !this.max) return;
|
|
972
|
+
this.crumbs.push(c);
|
|
973
|
+
if (this.crumbs.length > this.max) this.crumbs.splice(0, this.crumbs.length - this.max);
|
|
974
|
+
}
|
|
975
|
+
breadcrumbs() {
|
|
976
|
+
return this.crumbs.slice();
|
|
977
|
+
}
|
|
978
|
+
clear() {
|
|
979
|
+
this.tags = {};
|
|
980
|
+
this.extra = {};
|
|
981
|
+
this.contexts = {};
|
|
982
|
+
this.user = {};
|
|
983
|
+
this.crumbs = [];
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
function toDetailFrames(frames) {
|
|
987
|
+
const out = [];
|
|
988
|
+
for (let i = frames.length - 1; i >= 0; i--) {
|
|
989
|
+
const f = frames[i];
|
|
990
|
+
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 } : {} });
|
|
991
|
+
}
|
|
992
|
+
return out;
|
|
993
|
+
}
|
|
994
|
+
var isError = (v) => v instanceof Error || !!v && typeof v === "object" && typeof v.message === "string" && typeof v.stack === "string";
|
|
995
|
+
function exceptionChain(error, main, mechanism) {
|
|
996
|
+
const chain = [{ type: main.type, value: main.message, mechanism: { type: mechanism, handled: main.handled !== false, exception_id: 0 }, stacktrace: { frames: toDetailFrames(main.frames) } }];
|
|
997
|
+
const seen = /* @__PURE__ */ new Set([error]);
|
|
998
|
+
let cur = isError(error) ? error.cause : void 0;
|
|
999
|
+
while (cur !== void 0 && cur !== null && !seen.has(cur) && chain.length < MAX_EXCEPTION_CHAIN) {
|
|
1000
|
+
seen.add(cur);
|
|
1001
|
+
const e = cur;
|
|
1002
|
+
const id = chain.length;
|
|
1003
|
+
chain.push({
|
|
1004
|
+
type: isError(e) && e.name || "Error",
|
|
1005
|
+
value: (isError(e) ? e.message : String(cur)).slice(0, 1e3),
|
|
1006
|
+
mechanism: { type: "chained", source: "cause", handled: true, exception_id: id, parent_id: id - 1 },
|
|
1007
|
+
...isError(e) ? { stacktrace: { frames: toDetailFrames(parseStack2(e.stack, 50)) } } : {}
|
|
1008
|
+
});
|
|
1009
|
+
cur = isError(e) ? e.cause : void 0;
|
|
1010
|
+
}
|
|
1011
|
+
return chain.reverse();
|
|
1012
|
+
}
|
|
1013
|
+
var VERSION_TOKEN = { chrome: "(?:Chrome|CriOS)", edge: "Edg", firefox: "(?:Firefox|FxiOS)", safari: "Version", opera: "OPR", samsung: "SamsungBrowser", electron: "Electron" };
|
|
1014
|
+
var OS_VERSION = /(?:Mac OS X|Windows NT|Android|CPU (?:iPhone )?OS) ([\d._]+)/;
|
|
1015
|
+
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" };
|
|
1016
|
+
function browserContexts(ua, device) {
|
|
1017
|
+
const out = {};
|
|
1018
|
+
const token = device.browser ? VERSION_TOKEN[device.browser] : void 0;
|
|
1019
|
+
const v = token ? new RegExp(`${token}/([\\d.]+)`).exec(ua)?.[1] : void 0;
|
|
1020
|
+
if (device.browser) out.browser = { name: NAMES[device.browser] ?? device.browser, ...v ? { version: v } : {} };
|
|
1021
|
+
const ov = OS_VERSION.exec(ua)?.[1]?.replace(/_/g, ".");
|
|
1022
|
+
if (device.os) out.os = { name: NAMES[device.os] ?? device.os, ...ov ? { version: ov } : {} };
|
|
1023
|
+
out.device = { family: device.type === "desktop" ? "Desktop" : device.type === "tablet" ? "Tablet" : "Mobile" };
|
|
1024
|
+
return out;
|
|
1025
|
+
}
|
|
1026
|
+
function buildDetail(input) {
|
|
1027
|
+
const { scope, ctx } = input;
|
|
1028
|
+
const contexts = { ...input.contexts, ...scope.contexts };
|
|
1029
|
+
for (const [k, v] of Object.entries(ctx.contexts ?? {})) if (v) contexts[k] = v;
|
|
1030
|
+
const user = { ...scope.user, ...ctx.user ?? {} };
|
|
1031
|
+
const detail = {
|
|
1032
|
+
level: input.level,
|
|
1033
|
+
exception: { values: input.props.type === "Message" ? [] : exceptionChain(input.error, input.props, input.mechanism) },
|
|
1034
|
+
breadcrumbs: { values: scope.breadcrumbs() },
|
|
1035
|
+
contexts,
|
|
1036
|
+
tags: normalizeTags({ ...scope.tags, ...ctx.tags ?? {} }),
|
|
1037
|
+
extra: { ...scope.extra, ...ctx.extra ?? {} },
|
|
1038
|
+
...Object.keys(user).length ? { user } : {},
|
|
1039
|
+
...ctx.fingerprint?.length ? { fingerprint: ctx.fingerprint.map(String) } : {}
|
|
1040
|
+
};
|
|
1041
|
+
return trimExceptionDetail(detail);
|
|
1042
|
+
}
|
|
1043
|
+
var isOwnRequest = (url, apiUrl) => !!apiUrl && url.startsWith(apiUrl) && /\/api\/v1\/sdk\//.test(url);
|
|
1044
|
+
function breadcrumbUrl(raw) {
|
|
1045
|
+
try {
|
|
1046
|
+
const u = new URL(raw, typeof location !== "undefined" ? location.href : "http://localhost/");
|
|
1047
|
+
return (u.origin === "null" ? u.protocol : u.origin) + u.pathname;
|
|
1048
|
+
} catch {
|
|
1049
|
+
return raw.replace(/[?#].*$/, "");
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function describeArg(a) {
|
|
1053
|
+
if (typeof a === "string") return a;
|
|
1054
|
+
if (a instanceof Error) return `${a.name}: ${a.message}`;
|
|
1055
|
+
try {
|
|
1056
|
+
return JSON.stringify(a) ?? String(a);
|
|
1057
|
+
} catch {
|
|
1058
|
+
return String(a);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
var XHR_INFO = "__guidelyCrumb";
|
|
1062
|
+
function instrumentBreadcrumbs(add, opts) {
|
|
1063
|
+
let on = true;
|
|
1064
|
+
const undo = [];
|
|
1065
|
+
const g = globalThis;
|
|
1066
|
+
if (opts.console !== false && g.console) {
|
|
1067
|
+
const levels = opts.console === "all" ? ["log", "info", "warn", "error", "debug"] : ["warn", "error"];
|
|
1068
|
+
for (const level of levels) {
|
|
1069
|
+
const original = g.console[level];
|
|
1070
|
+
if (typeof original !== "function") continue;
|
|
1071
|
+
const wrapped = function(...args) {
|
|
1072
|
+
if (on) {
|
|
1073
|
+
try {
|
|
1074
|
+
const message = args.map(describeArg).join(" ").slice(0, 1e3);
|
|
1075
|
+
if (!message.startsWith("[Guidely]")) add({ category: "console", level: level === "warn" ? "warning" : level === "log" ? "info" : level, message, data: { logger: "console" } }, { event: args });
|
|
1076
|
+
} catch {
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return original.apply(this, args);
|
|
1080
|
+
};
|
|
1081
|
+
g.console[level] = wrapped;
|
|
1082
|
+
undo.push(() => {
|
|
1083
|
+
if (g.console[level] === wrapped) g.console[level] = original;
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (opts.http !== false && typeof g.fetch === "function") {
|
|
1088
|
+
const original = g.fetch;
|
|
1089
|
+
const wrapped = function(input, init) {
|
|
1090
|
+
const started = Date.now();
|
|
1091
|
+
let url = "";
|
|
1092
|
+
let method = "GET";
|
|
1093
|
+
try {
|
|
1094
|
+
url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1095
|
+
method = (init?.method ?? (typeof input === "object" && "method" in input ? input.method : "GET")).toUpperCase();
|
|
1096
|
+
} catch {
|
|
1097
|
+
}
|
|
1098
|
+
const p = original.call(this, input, init);
|
|
1099
|
+
if (on && url && !isOwnRequest(url, opts.apiUrl)) {
|
|
1100
|
+
const crumb = (status) => {
|
|
1101
|
+
try {
|
|
1102
|
+
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 } });
|
|
1103
|
+
} catch {
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
p.then(
|
|
1107
|
+
(res) => crumb(res.status),
|
|
1108
|
+
() => crumb(void 0)
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
return p;
|
|
1112
|
+
};
|
|
1113
|
+
g.fetch = wrapped;
|
|
1114
|
+
undo.push(() => {
|
|
1115
|
+
if (g.fetch === wrapped) g.fetch = original;
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
const proto = opts.http !== false ? g.XMLHttpRequest?.prototype : void 0;
|
|
1119
|
+
if (proto) {
|
|
1120
|
+
const open = proto.open;
|
|
1121
|
+
const send = proto.send;
|
|
1122
|
+
const wrappedOpen = function(method, url, ...rest) {
|
|
1123
|
+
try {
|
|
1124
|
+
this[XHR_INFO] = { method: String(method).toUpperCase(), url: String(url) };
|
|
1125
|
+
} catch {
|
|
1126
|
+
}
|
|
1127
|
+
return open.call(this, method, url, ...rest);
|
|
1128
|
+
};
|
|
1129
|
+
const wrappedSend = function(body) {
|
|
1130
|
+
const info = this[XHR_INFO];
|
|
1131
|
+
if (on && info && !isOwnRequest(info.url, opts.apiUrl)) {
|
|
1132
|
+
const started = Date.now();
|
|
1133
|
+
this.addEventListener("loadend", () => {
|
|
1134
|
+
try {
|
|
1135
|
+
const status = this.status || void 0;
|
|
1136
|
+
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 });
|
|
1137
|
+
} catch {
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
return send.call(this, body);
|
|
1142
|
+
};
|
|
1143
|
+
proto.open = wrappedOpen;
|
|
1144
|
+
proto.send = wrappedSend;
|
|
1145
|
+
undo.push(() => {
|
|
1146
|
+
if (proto.open === wrappedOpen) proto.open = open;
|
|
1147
|
+
if (proto.send === wrappedSend) proto.send = send;
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
return () => {
|
|
1151
|
+
on = false;
|
|
1152
|
+
for (const u of undo) u();
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
|
|
938
1156
|
// src/messenger-api.ts
|
|
939
1157
|
var MAX_STORED = 50;
|
|
940
1158
|
var TOKEN_HEADER = "X-Guidely-Conversation";
|
|
@@ -1098,7 +1316,7 @@ function createMessengerTransport(opts) {
|
|
|
1098
1316
|
}
|
|
1099
1317
|
|
|
1100
1318
|
// src/messenger-visitor.ts
|
|
1101
|
-
import { firstNameFromTraits } from "@guidelytech/shared/runtime";
|
|
1319
|
+
import { firstNameFromTraits, videoThumbnailUrl } from "@guidelytech/shared/runtime";
|
|
1102
1320
|
import { urlMatches } from "@guidelytech/guide-runtime";
|
|
1103
1321
|
var PAGE_GUIDE_KINDS = /* @__PURE__ */ new Set(["flow", "tooltip", "modal"]);
|
|
1104
1322
|
var CATCH_ALL = /* @__PURE__ */ new Set(["", "*", "**", "/*", "/**", "/"]);
|
|
@@ -1123,7 +1341,7 @@ function plainText(html, max = 280) {
|
|
|
1123
1341
|
return text.length > max ? `${text.slice(0, max - 1).trimEnd()}\u2026` : text;
|
|
1124
1342
|
}
|
|
1125
1343
|
function buildMessengerVisitor(input) {
|
|
1126
|
-
const
|
|
1344
|
+
const now3 = input.now ?? Date.now();
|
|
1127
1345
|
const { guides, state } = input;
|
|
1128
1346
|
const done = (g) => !!state.guides[g.id]?.completedAt;
|
|
1129
1347
|
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 }));
|
|
@@ -1133,7 +1351,7 @@ function buildMessengerVisitor(input) {
|
|
|
1133
1351
|
const s = state.guides[g.id];
|
|
1134
1352
|
if (g.kind !== "flow" || !s?.lastStepId || s.completedAt || g.config.steps.length < 2) continue;
|
|
1135
1353
|
const at = Date.parse(s.lastShownAt ?? "");
|
|
1136
|
-
if (!Number.isFinite(at) ||
|
|
1354
|
+
if (!Number.isFinite(at) || now3 - at > RESUME_WITHIN_MS || at <= resumeAt) continue;
|
|
1137
1355
|
const stepIndex = g.config.steps.findIndex((st) => st.id === s.lastStepId);
|
|
1138
1356
|
if (stepIndex < 1 || !isWithinSchedule(g)) continue;
|
|
1139
1357
|
resumeGuide = { id: g.id, name: g.name, stepId: s.lastStepId, stepIndex, stepCount: g.config.steps.length };
|
|
@@ -1152,14 +1370,14 @@ function buildMessengerVisitor(input) {
|
|
|
1152
1370
|
break;
|
|
1153
1371
|
}
|
|
1154
1372
|
const seen = new Set(input.newsSeen);
|
|
1155
|
-
const news = guides.filter((g) => g.kind === "announcement" && g.config.steps.length > 0 &&
|
|
1373
|
+
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) => {
|
|
1156
1374
|
const step = g.config.steps[0];
|
|
1157
1375
|
return {
|
|
1158
1376
|
id: g.id,
|
|
1159
1377
|
key: g.key,
|
|
1160
1378
|
title: step.title || g.name,
|
|
1161
1379
|
body: plainText(step.body ?? ""),
|
|
1162
|
-
imageUrl: step.imageUrl || null,
|
|
1380
|
+
imageUrl: step.imageUrl || videoThumbnailUrl(step.videoUrl) || null,
|
|
1163
1381
|
publishedAt: g.publishedAt,
|
|
1164
1382
|
// Read once opened in the News tab, or once the announcement itself was shown.
|
|
1165
1383
|
unread: !seen.has(g.id) && !(state.guides[g.id]?.impressions ?? 0)
|
|
@@ -1282,19 +1500,19 @@ var PageTracker = class {
|
|
|
1282
1500
|
__publicField(this, "maxScroll", 0);
|
|
1283
1501
|
}
|
|
1284
1502
|
/** Starts measuring a new page. */
|
|
1285
|
-
start(url,
|
|
1503
|
+
start(url, now3 = Date.now()) {
|
|
1286
1504
|
this.url = url;
|
|
1287
1505
|
this.visibleMs = 0;
|
|
1288
|
-
this.visibleSince = typeof document !== "undefined" && document.visibilityState === "hidden" ? null :
|
|
1506
|
+
this.visibleSince = typeof document !== "undefined" && document.visibilityState === "hidden" ? null : now3;
|
|
1289
1507
|
this.maxScroll = 0;
|
|
1290
1508
|
this.measureScroll();
|
|
1291
1509
|
}
|
|
1292
1510
|
/** Pauses / resumes the visible-time clock. */
|
|
1293
|
-
setHidden(hidden,
|
|
1511
|
+
setHidden(hidden, now3 = Date.now()) {
|
|
1294
1512
|
if (hidden && this.visibleSince !== null) {
|
|
1295
|
-
this.visibleMs += Math.max(0,
|
|
1513
|
+
this.visibleMs += Math.max(0, now3 - this.visibleSince);
|
|
1296
1514
|
this.visibleSince = null;
|
|
1297
|
-
} else if (!hidden && this.visibleSince === null) this.visibleSince =
|
|
1515
|
+
} else if (!hidden && this.visibleSince === null) this.visibleSince = now3;
|
|
1298
1516
|
}
|
|
1299
1517
|
measureScroll() {
|
|
1300
1518
|
try {
|
|
@@ -1303,9 +1521,9 @@ var PageTracker = class {
|
|
|
1303
1521
|
}
|
|
1304
1522
|
}
|
|
1305
1523
|
/** Final numbers for the current page. */
|
|
1306
|
-
leave(
|
|
1524
|
+
leave(now3 = Date.now()) {
|
|
1307
1525
|
this.measureScroll();
|
|
1308
|
-
const running = this.visibleSince !== null ? Math.max(0,
|
|
1526
|
+
const running = this.visibleSince !== null ? Math.max(0, now3 - this.visibleSince) : 0;
|
|
1309
1527
|
return { url: this.url, durationMs: Math.round(this.visibleMs + running), scrollDepth: this.maxScroll };
|
|
1310
1528
|
}
|
|
1311
1529
|
};
|
|
@@ -1413,17 +1631,622 @@ function openPrototypeOverlay(testUrl, opts) {
|
|
|
1413
1631
|
// src/load-recorder.ts
|
|
1414
1632
|
var loadRecorder = async () => (await import("./recorder.js")).recorder;
|
|
1415
1633
|
|
|
1634
|
+
// src/trace-propagation.ts
|
|
1635
|
+
function hex(bytes) {
|
|
1636
|
+
const a = new Uint8Array(bytes);
|
|
1637
|
+
try {
|
|
1638
|
+
crypto.getRandomValues(a);
|
|
1639
|
+
} catch {
|
|
1640
|
+
for (let i = 0; i < bytes; i++) a[i] = Math.floor(Math.random() * 256);
|
|
1641
|
+
}
|
|
1642
|
+
let out = "";
|
|
1643
|
+
for (const b of a) out += (b < 16 ? "0" : "") + b.toString(16);
|
|
1644
|
+
return out;
|
|
1645
|
+
}
|
|
1646
|
+
function stripUrl(url) {
|
|
1647
|
+
return url.split(/[?#]/)[0];
|
|
1648
|
+
}
|
|
1649
|
+
function resolveUrl(url) {
|
|
1650
|
+
try {
|
|
1651
|
+
return new URL(url, location.href).href;
|
|
1652
|
+
} catch {
|
|
1653
|
+
return url;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
function shouldPropagate(url, targets) {
|
|
1657
|
+
const abs = resolveUrl(url);
|
|
1658
|
+
if (!targets) {
|
|
1659
|
+
try {
|
|
1660
|
+
return new URL(abs).origin === location.origin;
|
|
1661
|
+
} catch {
|
|
1662
|
+
return false;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
return targets.some((t) => {
|
|
1666
|
+
if (typeof t === "string") return url.includes(t) || abs.includes(t);
|
|
1667
|
+
t.lastIndex = 0;
|
|
1668
|
+
if (t.test(url)) return true;
|
|
1669
|
+
t.lastIndex = 0;
|
|
1670
|
+
return t.test(abs);
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
function mergeBaggage(existing, ours) {
|
|
1674
|
+
const kept = (existing || "").split(",").map((s) => s.trim()).filter((s) => s && !s.startsWith("sentry-"));
|
|
1675
|
+
return [...kept, ours].join(",");
|
|
1676
|
+
}
|
|
1677
|
+
function traceHeaders(dsc, spanId) {
|
|
1678
|
+
const flag = dsc.sampled ? "1" : "0";
|
|
1679
|
+
const e = encodeURIComponent;
|
|
1680
|
+
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(",");
|
|
1681
|
+
return { "sentry-trace": `${dsc.traceId}-${spanId}-${flag}`, traceparent: `00-${dsc.traceId}-${spanId}-0${flag}`, baggage };
|
|
1682
|
+
}
|
|
1683
|
+
function httpSpanStatus(code) {
|
|
1684
|
+
if (code >= 100 && code < 400) return "ok";
|
|
1685
|
+
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";
|
|
1686
|
+
if (code >= 500 && code < 600) return { 501: "unimplemented", 503: "unavailable", 504: "deadline_exceeded" }[code] ?? "internal_error";
|
|
1687
|
+
return "unknown_error";
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// src/web-vitals.ts
|
|
1691
|
+
function observe(type, cb, extra = {}) {
|
|
1692
|
+
try {
|
|
1693
|
+
if (typeof PerformanceObserver === "undefined" || !PerformanceObserver.supportedEntryTypes?.includes(type)) return null;
|
|
1694
|
+
const po = new PerformanceObserver((list) => cb(list.getEntries()));
|
|
1695
|
+
po.observe({ type, buffered: true, ...extra });
|
|
1696
|
+
return { take: () => cb(po.takeRecords()), stop: () => po.disconnect() };
|
|
1697
|
+
} catch {
|
|
1698
|
+
return null;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
var newCls = () => ({ value: 0, session: 0, first: 0, last: 0 });
|
|
1702
|
+
function addLayoutShift(s, e) {
|
|
1703
|
+
if (e.hadRecentInput) return;
|
|
1704
|
+
if (s.session && e.startTime - s.last < 1e3 && e.startTime - s.first < 5e3) s.session += e.value;
|
|
1705
|
+
else {
|
|
1706
|
+
s.session = e.value;
|
|
1707
|
+
s.first = e.startTime;
|
|
1708
|
+
}
|
|
1709
|
+
s.last = e.startTime;
|
|
1710
|
+
if (s.session > s.value) s.value = s.session;
|
|
1711
|
+
}
|
|
1712
|
+
var newInp = () => ({ longest: [], ids: /* @__PURE__ */ new Set() });
|
|
1713
|
+
function addInteraction(s, e) {
|
|
1714
|
+
const id = e.interactionId;
|
|
1715
|
+
if (!id) return;
|
|
1716
|
+
s.ids.add(id);
|
|
1717
|
+
const known = s.longest.find((i) => i.id === id);
|
|
1718
|
+
if (known) known.latency = Math.max(known.latency, e.duration);
|
|
1719
|
+
else if (s.longest.length < 10 || e.duration > s.longest[s.longest.length - 1].latency) s.longest.push({ id, latency: e.duration });
|
|
1720
|
+
else return;
|
|
1721
|
+
s.longest.sort((a, b) => b.latency - a.latency);
|
|
1722
|
+
s.longest.splice(10);
|
|
1723
|
+
}
|
|
1724
|
+
function inpValue(s, count = s.ids.size) {
|
|
1725
|
+
if (!s.longest.length) return void 0;
|
|
1726
|
+
return s.longest[Math.min(s.longest.length - 1, Math.floor(count / 50))].latency;
|
|
1727
|
+
}
|
|
1728
|
+
var ms = (value) => ({ value, unit: "millisecond" });
|
|
1729
|
+
var WebVitals = class {
|
|
1730
|
+
constructor(nav) {
|
|
1731
|
+
__publicField(this, "lcp");
|
|
1732
|
+
__publicField(this, "fcp");
|
|
1733
|
+
__publicField(this, "ttfb");
|
|
1734
|
+
__publicField(this, "cls", newCls());
|
|
1735
|
+
__publicField(this, "inp", newInp());
|
|
1736
|
+
__publicField(this, "observed", []);
|
|
1737
|
+
__publicField(this, "lcpFinal", false);
|
|
1738
|
+
__publicField(this, "hiddenAt");
|
|
1739
|
+
__publicField(this, "offs", []);
|
|
1740
|
+
const activation = nav?.activationStart || 0;
|
|
1741
|
+
const rel = (t) => Math.max(0, t - activation);
|
|
1742
|
+
this.hiddenAt = document.visibilityState === "hidden" ? 0 : Infinity;
|
|
1743
|
+
if (nav && nav.responseStart > 0) this.ttfb = rel(nav.responseStart);
|
|
1744
|
+
const add = (o) => o && this.observed.push(o);
|
|
1745
|
+
add(
|
|
1746
|
+
observe("paint", (es) => {
|
|
1747
|
+
for (const e of es) if (e.name === "first-contentful-paint" && e.startTime < this.hiddenAt) this.fcp = rel(e.startTime);
|
|
1748
|
+
})
|
|
1749
|
+
);
|
|
1750
|
+
add(
|
|
1751
|
+
observe("largest-contentful-paint", (es) => {
|
|
1752
|
+
const e = es[es.length - 1];
|
|
1753
|
+
if (e && !this.lcpFinal && e.startTime < this.hiddenAt) this.lcp = rel(e.startTime);
|
|
1754
|
+
})
|
|
1755
|
+
);
|
|
1756
|
+
add(observe("layout-shift", (es) => es.forEach((e) => addLayoutShift(this.cls, e))));
|
|
1757
|
+
const onEvent = (es) => es.forEach((e) => addInteraction(this.inp, e));
|
|
1758
|
+
add(observe("event", onEvent, { durationThreshold: 40 }));
|
|
1759
|
+
add(observe("first-input", onEvent));
|
|
1760
|
+
const freeze = () => {
|
|
1761
|
+
this.lcpFinal = true;
|
|
1762
|
+
this.offs.splice(0).forEach((f) => f());
|
|
1763
|
+
};
|
|
1764
|
+
for (const type of ["keydown", "pointerdown"]) {
|
|
1765
|
+
addEventListener(type, freeze, { once: true, capture: true });
|
|
1766
|
+
this.offs.push(() => removeEventListener(type, freeze, { capture: true }));
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
/** The page was hidden: deliver pending entries and freeze LCP. */
|
|
1770
|
+
hidden() {
|
|
1771
|
+
this.observed.forEach((o) => o.take());
|
|
1772
|
+
this.lcpFinal = true;
|
|
1773
|
+
if (this.hiddenAt === Infinity) this.hiddenAt = performance.now();
|
|
1774
|
+
}
|
|
1775
|
+
/** Known at load: LCP (so far), FCP and TTFB. */
|
|
1776
|
+
pageload() {
|
|
1777
|
+
const m = {};
|
|
1778
|
+
if (this.lcp !== void 0) m.lcp = ms(this.lcp);
|
|
1779
|
+
if (this.fcp !== void 0) m.fcp = ms(this.fcp);
|
|
1780
|
+
if (this.ttfb !== void 0) m.ttfb = ms(this.ttfb);
|
|
1781
|
+
return m;
|
|
1782
|
+
}
|
|
1783
|
+
/** Known only when the page is hidden: CLS, and INP once there was an interaction. */
|
|
1784
|
+
late() {
|
|
1785
|
+
const count = performance.interactionCount;
|
|
1786
|
+
const inp = inpValue(this.inp, count || void 0);
|
|
1787
|
+
return { cls: { value: this.cls.value, unit: "" }, ...inp !== void 0 ? { inp: ms(inp) } : {} };
|
|
1788
|
+
}
|
|
1789
|
+
stop() {
|
|
1790
|
+
this.observed.splice(0).forEach((o) => o.stop());
|
|
1791
|
+
this.offs.splice(0).forEach((f) => f());
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
|
|
1795
|
+
// src/tracing.ts
|
|
1796
|
+
var TRACES_SDK_NAME = "guidely.javascript";
|
|
1797
|
+
var MAX_SPANS = 1e3;
|
|
1798
|
+
var MAX_QUEUE = 20;
|
|
1799
|
+
var BATCH = 10;
|
|
1800
|
+
var now2 = () => performance.now();
|
|
1801
|
+
function entries(type) {
|
|
1802
|
+
try {
|
|
1803
|
+
return performance.getEntriesByType(type);
|
|
1804
|
+
} catch {
|
|
1805
|
+
return [];
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
function pathOf2(href) {
|
|
1809
|
+
try {
|
|
1810
|
+
return new URL(href).pathname || "/";
|
|
1811
|
+
} catch {
|
|
1812
|
+
return stripUrl(href) || "/";
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
function browserContext() {
|
|
1816
|
+
const ua = navigator.userAgent || "";
|
|
1817
|
+
const name = detectDevice(ua, 1024).browser;
|
|
1818
|
+
if (!name) return void 0;
|
|
1819
|
+
const token = { edge: "Edg", opera: "OPR", samsung: "SamsungBrowser", chrome: "(?:Chrome|CriOS)", firefox: "(?:Firefox|FxiOS)", safari: "Version", electron: "Electron" }[name];
|
|
1820
|
+
const version = token ? new RegExp(`${token}/([\\d.]+)`).exec(ua)?.[1] : void 0;
|
|
1821
|
+
return { name: name[0].toUpperCase() + name.slice(1), ...version ? { version } : {} };
|
|
1822
|
+
}
|
|
1823
|
+
function startTracer(config) {
|
|
1824
|
+
if (typeof performance === "undefined" || typeof performance.now !== "function" || typeof window === "undefined") return null;
|
|
1825
|
+
return new Tracer(config);
|
|
1826
|
+
}
|
|
1827
|
+
var Tracer = class {
|
|
1828
|
+
constructor(o) {
|
|
1829
|
+
__publicField(this, "o", o);
|
|
1830
|
+
/** null until the project setting is known; then whether tracing is on for this page load. */
|
|
1831
|
+
__publicField(this, "enabled", null);
|
|
1832
|
+
__publicField(this, "sampled", false);
|
|
1833
|
+
__publicField(this, "rate", 0);
|
|
1834
|
+
__publicField(this, "txn", null);
|
|
1835
|
+
__publicField(this, "pageload");
|
|
1836
|
+
__publicField(this, "origin");
|
|
1837
|
+
__publicField(this, "vitals");
|
|
1838
|
+
__publicField(this, "longTasks", []);
|
|
1839
|
+
/** Finished before sampling was decided. */
|
|
1840
|
+
__publicField(this, "held", []);
|
|
1841
|
+
__publicField(this, "out", []);
|
|
1842
|
+
__publicField(this, "flushTimer", null);
|
|
1843
|
+
__publicField(this, "cleanups", []);
|
|
1844
|
+
__publicField(this, "stopped", false);
|
|
1845
|
+
__publicField(this, "vitalsSent", false);
|
|
1846
|
+
__publicField(this, "apiPrefix");
|
|
1847
|
+
__publicField(this, "idleMs");
|
|
1848
|
+
__publicField(this, "finalMs");
|
|
1849
|
+
this.idleMs = o.idleMs ?? 1e3;
|
|
1850
|
+
this.finalMs = o.finalMs ?? 3e4;
|
|
1851
|
+
this.origin = performance.timeOrigin || Date.now() - now2();
|
|
1852
|
+
this.apiPrefix = `${resolveUrl(o.apiUrl).replace(/\/+$/, "")}/api/v1/sdk/`;
|
|
1853
|
+
this.vitals = new WebVitals(entries("navigation")[0]);
|
|
1854
|
+
const lt = observe("longtask", (es) => {
|
|
1855
|
+
for (const e of es) if (this.longTasks.length < 500) this.longTasks.push(e);
|
|
1856
|
+
});
|
|
1857
|
+
if (lt) this.cleanups.push(lt.stop);
|
|
1858
|
+
this.instrumentFetch();
|
|
1859
|
+
this.instrumentXhr();
|
|
1860
|
+
this.cleanups.push(watchNavigation((href) => this.navigate(href), 0));
|
|
1861
|
+
const onVisibility = () => document.visibilityState === "hidden" && this.onHide();
|
|
1862
|
+
const onPageHide = () => this.onHide();
|
|
1863
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
1864
|
+
addEventListener("pagehide", onPageHide);
|
|
1865
|
+
this.cleanups.push(() => {
|
|
1866
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
1867
|
+
removeEventListener("pagehide", onPageHide);
|
|
1868
|
+
});
|
|
1869
|
+
const t = this.pageload = this.begin("pageload", 0);
|
|
1870
|
+
const onLoad = () => setTimeout(() => {
|
|
1871
|
+
t.loaded = true;
|
|
1872
|
+
this.scheduleIdle(t);
|
|
1873
|
+
}, 0);
|
|
1874
|
+
if (document.readyState === "complete") onLoad();
|
|
1875
|
+
else {
|
|
1876
|
+
addEventListener("load", onLoad, { once: true });
|
|
1877
|
+
this.cleanups.push(() => removeEventListener("load", onLoad));
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
/** Applies the project's `performance` setting; sampling is decided the first time it is on. */
|
|
1881
|
+
configure(settings) {
|
|
1882
|
+
if (this.stopped) return;
|
|
1883
|
+
if (!settings?.enabled) {
|
|
1884
|
+
this.enabled = false;
|
|
1885
|
+
this.held = [];
|
|
1886
|
+
this.out = [];
|
|
1887
|
+
return this.stop();
|
|
1888
|
+
}
|
|
1889
|
+
if (this.enabled) return;
|
|
1890
|
+
this.enabled = true;
|
|
1891
|
+
const sdkRate = this.o.tracesSampleRate ?? 1;
|
|
1892
|
+
this.rate = Math.max(0, Math.min((settings.sampleRate ?? 100) / 100, Number.isFinite(sdkRate) ? sdkRate : 1, 1));
|
|
1893
|
+
this.sampled = Math.random() < this.rate;
|
|
1894
|
+
if (!this.sampled) this.vitals.stop();
|
|
1895
|
+
for (const e of this.held.splice(0)) if (this.sampled) this.enqueue(e);
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* The trace an error happening now belongs to (the open navigation, else the page load), so error
|
|
1899
|
+
* events link to it. Null when this page load isn't traced.
|
|
1900
|
+
*/
|
|
1901
|
+
traceContext() {
|
|
1902
|
+
if (!this.enabled || !this.sampled || this.stopped) return null;
|
|
1903
|
+
const t = this.txn ?? this.pageload;
|
|
1904
|
+
return { traceId: t.traceId, spanId: t.spanId };
|
|
1905
|
+
}
|
|
1906
|
+
/** Stops tracing and sends what is queued. */
|
|
1907
|
+
destroy() {
|
|
1908
|
+
this.flush(true);
|
|
1909
|
+
this.stop();
|
|
1910
|
+
}
|
|
1911
|
+
stop() {
|
|
1912
|
+
if (this.stopped) return;
|
|
1913
|
+
this.stopped = true;
|
|
1914
|
+
if (this.flushTimer) clearTimeout(this.flushTimer);
|
|
1915
|
+
for (const t of [this.txn, this.pageload]) {
|
|
1916
|
+
clearTimeout(t?.idle);
|
|
1917
|
+
clearTimeout(t?.final);
|
|
1918
|
+
}
|
|
1919
|
+
this.vitals.stop();
|
|
1920
|
+
this.cleanups.splice(0).forEach((f) => f());
|
|
1921
|
+
}
|
|
1922
|
+
ts(rel) {
|
|
1923
|
+
return (this.origin + rel) / 1e3;
|
|
1924
|
+
}
|
|
1925
|
+
begin(op, start, href) {
|
|
1926
|
+
const url = this.o.pageHref(href);
|
|
1927
|
+
const t = { op, name: pathOf2(url), url, traceId: hex(16), spanId: hex(8), start, last: start, inflight: 0, spans: [], loaded: op !== "pageload" };
|
|
1928
|
+
t.final = setTimeout(() => this.finish(t), Math.max(0, this.finalMs - (now2() - start)));
|
|
1929
|
+
this.txn = t;
|
|
1930
|
+
return t;
|
|
1931
|
+
}
|
|
1932
|
+
scheduleIdle(t) {
|
|
1933
|
+
clearTimeout(t.idle);
|
|
1934
|
+
if (t.done || t.inflight > 0 || !t.loaded) return;
|
|
1935
|
+
t.idle = setTimeout(() => this.finish(t), this.idleMs);
|
|
1936
|
+
}
|
|
1937
|
+
navigate(href) {
|
|
1938
|
+
if (this.stopped || pathOf2(this.o.pageHref(href)) === this.txn?.name) return;
|
|
1939
|
+
if (this.txn && !this.txn.done) this.finish(this.txn);
|
|
1940
|
+
this.scheduleIdle(this.begin("navigation", now2(), href));
|
|
1941
|
+
}
|
|
1942
|
+
onHide() {
|
|
1943
|
+
if (this.stopped) return;
|
|
1944
|
+
this.vitals.hidden();
|
|
1945
|
+
if (this.txn && !this.txn.done) this.finish(this.txn, now2());
|
|
1946
|
+
if (this.enabled && this.sampled && !this.vitalsSent) {
|
|
1947
|
+
this.vitalsSent = true;
|
|
1948
|
+
const at = this.ts(now2());
|
|
1949
|
+
const p = this.pageload;
|
|
1950
|
+
const e = this.event(p, "ui.webvitals", at, at, [], this.vitals.late());
|
|
1951
|
+
e.contexts.trace.span_id = hex(8);
|
|
1952
|
+
e.contexts.trace.parent_span_id = p.spanId;
|
|
1953
|
+
this.enqueue(e);
|
|
1954
|
+
}
|
|
1955
|
+
this.flush(true);
|
|
1956
|
+
}
|
|
1957
|
+
/** Ends a transaction: adds the spans read from performance entries and queues (or holds) it. */
|
|
1958
|
+
finish(t, endAt) {
|
|
1959
|
+
if (t.done) return;
|
|
1960
|
+
t.done = true;
|
|
1961
|
+
clearTimeout(t.idle);
|
|
1962
|
+
clearTimeout(t.final);
|
|
1963
|
+
if (this.stopped || this.enabled === false || this.enabled && !this.sampled) return;
|
|
1964
|
+
const until = now2();
|
|
1965
|
+
const spans = t.spans;
|
|
1966
|
+
const add = (op, description, start, end2, origin, data) => {
|
|
1967
|
+
if (spans.length >= MAX_SPANS || !(end2 >= start) || start < t.start || start > until) return;
|
|
1968
|
+
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 } : {} });
|
|
1969
|
+
};
|
|
1970
|
+
if (t.op === "pageload") {
|
|
1971
|
+
const n = entries("navigation")[0];
|
|
1972
|
+
if (n) {
|
|
1973
|
+
const timing = (name, s, e) => s > 0 && e > 0 && add("browser", name, s, e, "auto.ui.browser.metrics");
|
|
1974
|
+
timing("redirect", n.redirectStart, n.redirectEnd);
|
|
1975
|
+
timing("cache", n.fetchStart, n.domainLookupStart);
|
|
1976
|
+
timing("DNS", n.domainLookupStart, n.domainLookupEnd);
|
|
1977
|
+
timing("connect", n.connectStart, n.connectEnd);
|
|
1978
|
+
timing("TLS/SSL", n.secureConnectionStart, n.connectEnd);
|
|
1979
|
+
timing("request", n.requestStart, n.responseEnd);
|
|
1980
|
+
timing("response", n.responseStart, n.responseEnd);
|
|
1981
|
+
timing("domContentLoadedEvent", n.domContentLoadedEventStart, n.domContentLoadedEventEnd);
|
|
1982
|
+
timing("loadEvent", n.loadEventStart, n.loadEventEnd);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
for (const e of entries("resource")) {
|
|
1986
|
+
const it = e.initiatorType;
|
|
1987
|
+
if (it === "fetch" || it === "xmlhttprequest" || this.own(e.name)) continue;
|
|
1988
|
+
const kind = it === "script" || it === "css" || it === "link" ? it : it === "img" || it === "image" ? "img" : "other";
|
|
1989
|
+
const data = { "http.response_content_length": e.encodedBodySize, "http.decoded_response_content_length": e.decodedBodySize, "http.response_transfer_size": e.transferSize };
|
|
1990
|
+
if (e.renderBlockingStatus) data["resource.render_blocking_status"] = e.renderBlockingStatus;
|
|
1991
|
+
add(`resource.${kind}`, stripUrl(e.name), e.startTime, e.responseEnd || e.startTime + e.duration, "auto.resource.browser.metrics", data);
|
|
1992
|
+
}
|
|
1993
|
+
for (const e of this.longTasks) add("ui.long-task", "Main UI thread blocked", e.startTime, e.startTime + e.duration, "auto.ui.browser.metrics");
|
|
1994
|
+
for (const e of entries("mark")) add("mark", e.name, e.startTime, e.startTime, "auto.ui.browser.metrics");
|
|
1995
|
+
for (const e of entries("measure")) add("measure", e.name, e.startTime, e.startTime + e.duration, "auto.ui.browser.metrics");
|
|
1996
|
+
const end = endAt !== void 0 ? this.ts(endAt) : spans.reduce((m2, s) => Math.max(m2, s.timestamp), this.ts(t.last));
|
|
1997
|
+
const m = t.op === "pageload" ? this.vitals.pageload() : {};
|
|
1998
|
+
const event = this.event(t, t.op, this.ts(t.start), end, spans, Object.keys(m).length ? m : void 0);
|
|
1999
|
+
if (this.enabled) this.enqueue(event);
|
|
2000
|
+
else if (this.held.length < MAX_QUEUE) this.held.push(event);
|
|
2001
|
+
}
|
|
2002
|
+
event(t, op, start, end, spans, measurements) {
|
|
2003
|
+
const c = this.o.context();
|
|
2004
|
+
const url = c.collectUrls ? stripUrl(t.url) : void 0;
|
|
2005
|
+
const browser = browserContext();
|
|
2006
|
+
return {
|
|
2007
|
+
type: "transaction",
|
|
2008
|
+
event_id: hex(16),
|
|
2009
|
+
platform: "javascript",
|
|
2010
|
+
transaction: t.name,
|
|
2011
|
+
transaction_info: { source: "url" },
|
|
2012
|
+
start_timestamp: start,
|
|
2013
|
+
timestamp: end,
|
|
2014
|
+
...c.release ? { release: c.release } : {},
|
|
2015
|
+
environment: c.environment,
|
|
2016
|
+
contexts: {
|
|
2017
|
+
trace: { trace_id: t.traceId, span_id: t.spanId, op, status: "ok", origin: `auto.${op}.guidely` },
|
|
2018
|
+
...browser ? { browser } : {},
|
|
2019
|
+
guidely: { anonymousId: c.anonymousId, sessionId: c.sessionId, ...url ? { url } : {} }
|
|
2020
|
+
},
|
|
2021
|
+
spans,
|
|
2022
|
+
...measurements ? { measurements } : {},
|
|
2023
|
+
...url ? { request: { url } } : {},
|
|
2024
|
+
...c.userId ? { user: { id: c.userId } } : {}
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
// --- requests ---------------------------------------------------------------------------------
|
|
2028
|
+
/** A Guidely API request (never traced, never given headers). */
|
|
2029
|
+
own(url) {
|
|
2030
|
+
return resolveUrl(url).startsWith(this.apiPrefix);
|
|
2031
|
+
}
|
|
2032
|
+
startHttp(method, url, type) {
|
|
2033
|
+
if (this.stopped || this.enabled === false || this.own(url)) return null;
|
|
2034
|
+
const abs = resolveUrl(url);
|
|
2035
|
+
const t = this.txn && !this.txn.done ? this.txn : null;
|
|
2036
|
+
const spanId = hex(8);
|
|
2037
|
+
let span = null;
|
|
2038
|
+
if (t && (this.enabled === null || this.sampled)) {
|
|
2039
|
+
const u = stripUrl(abs);
|
|
2040
|
+
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 } };
|
|
2041
|
+
t.inflight += 1;
|
|
2042
|
+
clearTimeout(t.idle);
|
|
2043
|
+
}
|
|
2044
|
+
return { txn: t, span, spanId, url };
|
|
2045
|
+
}
|
|
2046
|
+
endHttp(live, status, length) {
|
|
2047
|
+
const { txn: t, span } = live;
|
|
2048
|
+
if (!t || !span) return;
|
|
2049
|
+
t.inflight = Math.max(0, t.inflight - 1);
|
|
2050
|
+
t.last = now2();
|
|
2051
|
+
span.timestamp = this.ts(t.last);
|
|
2052
|
+
span.status = status ? httpSpanStatus(status) : "unknown_error";
|
|
2053
|
+
if (status) span.data["http.response.status_code"] = status;
|
|
2054
|
+
if (length && Number(length) >= 0) span.data["http.response_content_length"] = Number(length);
|
|
2055
|
+
if (!t.done && t.spans.length < MAX_SPANS) t.spans.push(span);
|
|
2056
|
+
this.scheduleIdle(t);
|
|
2057
|
+
}
|
|
2058
|
+
/** Trace headers for a request, or null when it must not get any (tracing undecided / off, or not a target). */
|
|
2059
|
+
headers(live, baggage) {
|
|
2060
|
+
if (!this.enabled || this.stopped || !shouldPropagate(live.url, this.o.tracePropagationTargets)) return null;
|
|
2061
|
+
const c = this.o.context();
|
|
2062
|
+
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);
|
|
2063
|
+
return { ...h, baggage: mergeBaggage(baggage, h.baggage) };
|
|
2064
|
+
}
|
|
2065
|
+
instrumentFetch() {
|
|
2066
|
+
const win = window;
|
|
2067
|
+
const orig = win.fetch;
|
|
2068
|
+
if (typeof orig !== "function") return;
|
|
2069
|
+
const patched = (input, init) => {
|
|
2070
|
+
let live = null;
|
|
2071
|
+
try {
|
|
2072
|
+
const isReq = typeof Request !== "undefined" && input instanceof Request;
|
|
2073
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
2074
|
+
live = this.startHttp(String(init?.method || (isReq ? input.method : "GET")).toUpperCase(), url, "fetch");
|
|
2075
|
+
if (live && typeof Headers !== "undefined") {
|
|
2076
|
+
const headers = new Headers(init?.headers ?? (isReq ? input.headers : void 0));
|
|
2077
|
+
const add = headers.has("sentry-trace") ? null : this.headers(live, headers.get("baggage"));
|
|
2078
|
+
if (add) {
|
|
2079
|
+
for (const [k, v] of Object.entries(add)) if (k !== "traceparent" || !headers.has(k)) headers.set(k, v);
|
|
2080
|
+
init = { ...init, headers };
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
} catch {
|
|
2084
|
+
}
|
|
2085
|
+
const p = orig.call(win, input, init);
|
|
2086
|
+
if (live?.span) {
|
|
2087
|
+
const l = live;
|
|
2088
|
+
p.then(
|
|
2089
|
+
(res) => this.endHttp(l, res.status, res.headers?.get?.("content-length")),
|
|
2090
|
+
() => this.endHttp(l, 0)
|
|
2091
|
+
);
|
|
2092
|
+
}
|
|
2093
|
+
return p;
|
|
2094
|
+
};
|
|
2095
|
+
win.fetch = patched;
|
|
2096
|
+
this.cleanups.push(() => {
|
|
2097
|
+
if (win.fetch === patched) win.fetch = orig;
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
instrumentXhr() {
|
|
2101
|
+
const proto = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest.prototype : null;
|
|
2102
|
+
if (!proto) return;
|
|
2103
|
+
const { open, send, setRequestHeader } = proto;
|
|
2104
|
+
const self = { startHttp: this.startHttp.bind(this), headers: this.headers.bind(this), endHttp: this.endHttp.bind(this) };
|
|
2105
|
+
const meta = /* @__PURE__ */ new WeakMap();
|
|
2106
|
+
const pOpen = function(method, url, ...rest) {
|
|
2107
|
+
meta.set(this, { method: String(method).toUpperCase(), url: String(url), headers: {} });
|
|
2108
|
+
return open.call(this, method, url, ...rest);
|
|
2109
|
+
};
|
|
2110
|
+
const pSetHeader = function(name, value) {
|
|
2111
|
+
const m = meta.get(this);
|
|
2112
|
+
if (m) m.headers[String(name).toLowerCase()] = String(value);
|
|
2113
|
+
return setRequestHeader.call(this, name, value);
|
|
2114
|
+
};
|
|
2115
|
+
const pSend = function(body) {
|
|
2116
|
+
try {
|
|
2117
|
+
const m = meta.get(this);
|
|
2118
|
+
const live = m ? self.startHttp(m.method, m.url, "xhr") : null;
|
|
2119
|
+
if (live && m && !m.headers["sentry-trace"]) {
|
|
2120
|
+
const existing = m.headers.baggage ?? null;
|
|
2121
|
+
const add = self.headers(live, null);
|
|
2122
|
+
if (add) {
|
|
2123
|
+
setRequestHeader.call(this, "sentry-trace", add["sentry-trace"]);
|
|
2124
|
+
if (!m.headers.traceparent) setRequestHeader.call(this, "traceparent", add.traceparent);
|
|
2125
|
+
if (!existing?.includes("sentry-")) setRequestHeader.call(this, "baggage", add.baggage);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
if (live?.span) this.addEventListener("loadend", () => self.endHttp(live, this.status, safeHeader(this, "content-length")), { once: true });
|
|
2129
|
+
} catch {
|
|
2130
|
+
}
|
|
2131
|
+
return send.call(this, body);
|
|
2132
|
+
};
|
|
2133
|
+
proto.open = pOpen;
|
|
2134
|
+
proto.setRequestHeader = pSetHeader;
|
|
2135
|
+
proto.send = pSend;
|
|
2136
|
+
this.cleanups.push(() => {
|
|
2137
|
+
if (proto.open === pOpen) proto.open = open;
|
|
2138
|
+
if (proto.setRequestHeader === pSetHeader) proto.setRequestHeader = setRequestHeader;
|
|
2139
|
+
if (proto.send === pSend) proto.send = send;
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
// --- sending ----------------------------------------------------------------------------------
|
|
2143
|
+
enqueue(e) {
|
|
2144
|
+
this.out.push(e);
|
|
2145
|
+
if (this.out.length > MAX_QUEUE) this.out.shift();
|
|
2146
|
+
this.flushTimer ?? (this.flushTimer = setTimeout(() => this.flush(), this.o.flushMs ?? 1e3));
|
|
2147
|
+
}
|
|
2148
|
+
/** Sends queued transactions (`hide`: with `sendBeacon` / keepalive, the page is going away). */
|
|
2149
|
+
flush(hide = false) {
|
|
2150
|
+
if (this.flushTimer) clearTimeout(this.flushTimer);
|
|
2151
|
+
this.flushTimer = null;
|
|
2152
|
+
if (!this.out.length) return;
|
|
2153
|
+
const allowed = this.o.allowed();
|
|
2154
|
+
if (allowed === false) {
|
|
2155
|
+
this.out = [];
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
if (allowed === null) return;
|
|
2159
|
+
const { api } = this.o;
|
|
2160
|
+
if (!hide && !api.health.canSend()) {
|
|
2161
|
+
this.flushTimer = setTimeout(() => this.flush(), api.health.waitMs());
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
while (this.out.length) {
|
|
2165
|
+
const batch = this.out.splice(0, BATCH);
|
|
2166
|
+
const body = { sdk: { name: TRACES_SDK_NAME, version: this.o.sdkVersion }, transactions: batch };
|
|
2167
|
+
if (hide && api.beacon("/traces", body)) continue;
|
|
2168
|
+
api.post("/traces", body, { keepalive: hide }).then(
|
|
2169
|
+
(res) => isServerFailure(res.status) && this.retry(batch),
|
|
2170
|
+
() => this.retry(batch)
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
retry(batch) {
|
|
2175
|
+
if (this.stopped) return;
|
|
2176
|
+
this.out.unshift(...batch);
|
|
2177
|
+
this.out.splice(MAX_QUEUE);
|
|
2178
|
+
this.flushTimer ?? (this.flushTimer = setTimeout(() => this.flush(), Math.max(5e3, this.o.api.health.waitMs())));
|
|
2179
|
+
}
|
|
2180
|
+
};
|
|
2181
|
+
function safeHeader(xhr, name) {
|
|
2182
|
+
try {
|
|
2183
|
+
return xhr.getResponseHeader(name);
|
|
2184
|
+
} catch {
|
|
2185
|
+
return null;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
// src/bug-report.ts
|
|
2190
|
+
import { MAX_BUG_REPORT_LENGTH, MAX_BUG_REPORT_SCREENSHOT_BYTES } from "@guidelytech/shared/runtime";
|
|
2191
|
+
async function encodeScreenshot(s) {
|
|
2192
|
+
if (!s) return null;
|
|
2193
|
+
if (!(typeof Blob !== "undefined" && s instanceof Blob)) {
|
|
2194
|
+
const encoded = s;
|
|
2195
|
+
const data = encoded.data.replace(/^data:[^,]*,/, "");
|
|
2196
|
+
if (data.length * 0.75 > MAX_BUG_REPORT_SCREENSHOT_BYTES) throw new Error("[Guidely] captureFeedback: screenshots can be up to 4 MB");
|
|
2197
|
+
return { fileName: encoded.fileName || "screenshot.png", data };
|
|
2198
|
+
}
|
|
2199
|
+
if (s.size > MAX_BUG_REPORT_SCREENSHOT_BYTES) throw new Error("[Guidely] captureFeedback: screenshots can be up to 4 MB");
|
|
2200
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
2201
|
+
const r = new FileReader();
|
|
2202
|
+
r.onload = () => resolve(String(r.result));
|
|
2203
|
+
r.onerror = () => reject(new Error("[Guidely] captureFeedback: the screenshot couldn't be read"));
|
|
2204
|
+
r.readAsDataURL(s);
|
|
2205
|
+
});
|
|
2206
|
+
const name = s.name || (s.type === "image/jpeg" ? "screenshot.jpg" : "screenshot.png");
|
|
2207
|
+
return { fileName: name, data: dataUrl.slice(dataUrl.indexOf(",") + 1) };
|
|
2208
|
+
}
|
|
2209
|
+
function bugReportBody(input, ctx, source, screenshot) {
|
|
2210
|
+
const tags = {};
|
|
2211
|
+
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);
|
|
2212
|
+
const email = input.email?.trim();
|
|
2213
|
+
const name = input.name?.trim();
|
|
2214
|
+
const associated = input.associatedEventId ?? ctx.lastExceptionId;
|
|
2215
|
+
return {
|
|
2216
|
+
anonymousId: ctx.anonymousId,
|
|
2217
|
+
userId: ctx.userId,
|
|
2218
|
+
sessionId: ctx.sessionId,
|
|
2219
|
+
message: String(input.message ?? "").trim().slice(0, MAX_BUG_REPORT_LENGTH),
|
|
2220
|
+
...email ? { email } : {},
|
|
2221
|
+
...name ? { name } : {},
|
|
2222
|
+
...associated ? { associatedEventId: associated } : {},
|
|
2223
|
+
...ctx.url ? { url: ctx.url } : {},
|
|
2224
|
+
...ctx.appVersion ? { appVersion: ctx.appVersion } : {},
|
|
2225
|
+
source,
|
|
2226
|
+
...Object.keys(tags).length ? { tags } : {},
|
|
2227
|
+
sdk: ctx.sdk,
|
|
2228
|
+
...screenshot ? { screenshot } : {}
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
async function sendBugReport(api, body) {
|
|
2232
|
+
const res = await api.request("POST", "/bug-reports", { body, timeoutMs: body.screenshot ? 6e4 : 2e4 });
|
|
2233
|
+
if (res.ok && res.data && "id" in res.data) return res.data;
|
|
2234
|
+
if (res.status === 429) throw new Error("You've sent several reports just now. Please try again later.");
|
|
2235
|
+
const message = res.data && "error" in res.data ? res.data.error?.message : null;
|
|
2236
|
+
throw new Error(res.status === 400 && message ? message : "Your report couldn't be sent. Please try again.");
|
|
2237
|
+
}
|
|
2238
|
+
|
|
1416
2239
|
// src/client.ts
|
|
1417
2240
|
var SDK_NAME = "@guidelytech/sdk-core";
|
|
1418
|
-
var SDK_VERSION = "0.
|
|
2241
|
+
var SDK_VERSION = "0.17.0";
|
|
1419
2242
|
var DEFAULT_API_URL = "https://api.guidely.app";
|
|
1420
2243
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
1421
2244
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1422
2245
|
var MAX_SESSION_EXCEPTIONS = 100;
|
|
1423
2246
|
var CACHED_START_MS = 1500;
|
|
1424
|
-
function settlesWithin(promise,
|
|
2247
|
+
function settlesWithin(promise, ms2) {
|
|
1425
2248
|
return new Promise((resolve) => {
|
|
1426
|
-
const timer = setTimeout(() => resolve(false),
|
|
2249
|
+
const timer = setTimeout(() => resolve(false), ms2);
|
|
1427
2250
|
const done = () => {
|
|
1428
2251
|
clearTimeout(timer);
|
|
1429
2252
|
resolve(true);
|
|
@@ -1440,8 +2263,8 @@ function nowIso() {
|
|
|
1440
2263
|
function emptyState() {
|
|
1441
2264
|
return { guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] };
|
|
1442
2265
|
}
|
|
1443
|
-
function newSession(
|
|
1444
|
-
return { id: `s_${randomId2(16)}`, startedAt: new Date(
|
|
2266
|
+
function newSession(now3 = Date.now()) {
|
|
2267
|
+
return { id: `s_${randomId2(16)}`, startedAt: new Date(now3).toISOString(), shownGuides: [], events: [], lastActivityAt: now3 };
|
|
1445
2268
|
}
|
|
1446
2269
|
var GuidelyClient = class _GuidelyClient {
|
|
1447
2270
|
constructor(options) {
|
|
@@ -1456,6 +2279,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1456
2279
|
/** Agent Analytics turns (agentConversation), in their own outbox. */
|
|
1457
2280
|
__publicField(this, "agentTurns");
|
|
1458
2281
|
__publicField(this, "config", null);
|
|
2282
|
+
/** The `environment` option as passed (the resolved one is in `options.environment`). */
|
|
2283
|
+
__publicField(this, "envOption");
|
|
1459
2284
|
__publicField(this, "etag", null);
|
|
1460
2285
|
__publicField(this, "anonymousId");
|
|
1461
2286
|
__publicField(this, "user", null);
|
|
@@ -1497,6 +2322,11 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1497
2322
|
__publicField(this, "exceptionThrottle", new ExceptionThrottle());
|
|
1498
2323
|
/** Exceptions seen before the config was loaded (reported once it is known whether the server accepts them). */
|
|
1499
2324
|
__publicField(this, "earlyExceptions", []);
|
|
2325
|
+
/** The last exception this page reported, which bug reports link to by default. */
|
|
2326
|
+
__publicField(this, "lastExceptionId", null);
|
|
2327
|
+
__publicField(this, "bugReportDialog", null);
|
|
2328
|
+
/** Tags, extra, contexts and breadcrumbs sent with every error (`setTag`, `addBreadcrumb`…). */
|
|
2329
|
+
__publicField(this, "errorScope", new ErrorScope(() => this.options.errors));
|
|
1500
2330
|
__publicField(this, "initialPageViewSent", false);
|
|
1501
2331
|
__publicField(this, "pageViewUrl", null);
|
|
1502
2332
|
__publicField(this, "pageLeft", false);
|
|
@@ -1508,6 +2338,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1508
2338
|
__publicField(this, "locale", null);
|
|
1509
2339
|
/** Language each shown guide was rendered in; added to its events as `locale`. */
|
|
1510
2340
|
__publicField(this, "guideLocales", /* @__PURE__ */ new Map());
|
|
2341
|
+
/** Performance tracing (`tracing` option + the project's `performance` setting); see tracing.ts. */
|
|
2342
|
+
__publicField(this, "tracer");
|
|
1511
2343
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
1512
2344
|
__publicField(this, "eventBus", {
|
|
1513
2345
|
on: (name, cb) => {
|
|
@@ -1524,9 +2356,10 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1524
2356
|
});
|
|
1525
2357
|
if (!isBrowser()) throw new Error("[Guidely] createGuidely() must be called in the browser. Create the client inside useEffect / onMount, or use the framework adapters.");
|
|
1526
2358
|
if (!options || !options.projectId) throw new Error("[Guidely] `projectId` (your environment public key, e.g. pk_dev_\u2026) is required.");
|
|
2359
|
+
this.envOption = options.environment || void 0;
|
|
1527
2360
|
this.options = {
|
|
1528
2361
|
...options,
|
|
1529
|
-
environment: options.environment
|
|
2362
|
+
environment: options.environment || environmentFromKey(options.projectId),
|
|
1530
2363
|
apiUrl: options.apiUrl ?? DEFAULT_API_URL,
|
|
1531
2364
|
analytics: options.analytics ?? true,
|
|
1532
2365
|
consent: options.consent ?? "granted",
|
|
@@ -1570,6 +2403,16 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1570
2403
|
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 } });
|
|
1571
2404
|
if (options.user) this.user = { ...options.user };
|
|
1572
2405
|
this.flags = this.createFlags(options.flags);
|
|
2406
|
+
this.tracer = options.tracing === false ? null : startTracer({
|
|
2407
|
+
...typeof options.tracing === "object" ? options.tracing : {},
|
|
2408
|
+
api: this.api,
|
|
2409
|
+
publicKey: this.options.projectId,
|
|
2410
|
+
apiUrl: this.options.apiUrl,
|
|
2411
|
+
sdkVersion: SDK_VERSION,
|
|
2412
|
+
pageHref: (href) => this.pageHref(href),
|
|
2413
|
+
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() }),
|
|
2414
|
+
allowed: () => this.consent === "pending" ? null : this.analyticsEnabled() && !this.editing()
|
|
2415
|
+
});
|
|
1573
2416
|
}
|
|
1574
2417
|
createFlags(opts) {
|
|
1575
2418
|
const base = { sdkKey: this.options.projectId, apiUrl: this.options.apiUrl, fetch: this.options.fetch, sdk: { name: SDK_NAME, version: SDK_VERSION } };
|
|
@@ -1624,7 +2467,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1624
2467
|
try {
|
|
1625
2468
|
const cached = readJson(this.store, KEYS.config(this.options.projectId), null);
|
|
1626
2469
|
this.etag = this.store.get(KEYS.etag(this.options.projectId));
|
|
1627
|
-
if (cached && cached.guides) this.
|
|
2470
|
+
if (cached && cached.guides) this.useConfig(webOnly(cached));
|
|
1628
2471
|
const before = this.config?.configVersion;
|
|
1629
2472
|
const fresh = this.fetchConfig();
|
|
1630
2473
|
if (!this.config) await fresh;
|
|
@@ -1653,12 +2496,22 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1653
2496
|
this.resolveReady?.();
|
|
1654
2497
|
}
|
|
1655
2498
|
}
|
|
2499
|
+
/** Environment guides and flags target: the `environment` option, else the one the loaded config names, else a guess from the key. */
|
|
2500
|
+
get environment() {
|
|
2501
|
+
return this.envOption ?? this.config?.environment ?? this.options.environment;
|
|
2502
|
+
}
|
|
2503
|
+
useConfig(config) {
|
|
2504
|
+
const env = this.environment;
|
|
2505
|
+
this.config = config;
|
|
2506
|
+
if (this.environment !== env) this.syncFlagAttributes();
|
|
2507
|
+
this.tracer?.configure(config.settings?.performance);
|
|
2508
|
+
}
|
|
1656
2509
|
async fetchConfig() {
|
|
1657
2510
|
try {
|
|
1658
2511
|
const res = await this.api.get("/config", { etag: this.etag, params: { platform: "web" } });
|
|
1659
2512
|
if (res.status === 304) return true;
|
|
1660
2513
|
if (res.ok && res.data && Array.isArray(res.data.guides)) {
|
|
1661
|
-
this.
|
|
2514
|
+
this.useConfig(webOnly(res.data));
|
|
1662
2515
|
this.etag = res.etag;
|
|
1663
2516
|
writeJson(this.store, KEYS.config(this.options.projectId), res.data);
|
|
1664
2517
|
if (res.etag) this.store.set(KEYS.etag(this.options.projectId), res.etag);
|
|
@@ -1681,6 +2534,15 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1681
2534
|
const onClick = (e) => this.handleDocumentClick(e);
|
|
1682
2535
|
doc.addEventListener("click", onClick, true);
|
|
1683
2536
|
this.cleanups.push(() => doc.removeEventListener("click", onClick, true));
|
|
2537
|
+
const onArticleLink = (e) => {
|
|
2538
|
+
if (e.defaultPrevented) return;
|
|
2539
|
+
const articleId = articleIdFromHref(composedTarget(e)?.closest?.("a[href]")?.getAttribute("href"));
|
|
2540
|
+
if (!articleId) return;
|
|
2541
|
+
e.preventDefault();
|
|
2542
|
+
this.openArticle(articleId);
|
|
2543
|
+
};
|
|
2544
|
+
doc.addEventListener("click", onArticleLink);
|
|
2545
|
+
this.cleanups.push(() => doc.removeEventListener("click", onArticleLink));
|
|
1684
2546
|
const onVisibility = () => {
|
|
1685
2547
|
const hidden = doc.visibilityState === "hidden";
|
|
1686
2548
|
this.pageTracker.setHidden(hidden);
|
|
@@ -1702,9 +2564,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1702
2564
|
};
|
|
1703
2565
|
let lastScroll = 0;
|
|
1704
2566
|
const onScroll = () => {
|
|
1705
|
-
const
|
|
1706
|
-
if (
|
|
1707
|
-
lastScroll =
|
|
2567
|
+
const now3 = Date.now();
|
|
2568
|
+
if (now3 - lastScroll < 250) return;
|
|
2569
|
+
lastScroll = now3;
|
|
1708
2570
|
this.pageTracker.measureScroll();
|
|
1709
2571
|
this.touchSession();
|
|
1710
2572
|
};
|
|
@@ -1762,14 +2624,22 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1762
2624
|
/** Uncaught errors + unhandled rejections; attached before the config loads so boot errors are kept. */
|
|
1763
2625
|
wireErrorListeners() {
|
|
1764
2626
|
const win = window;
|
|
1765
|
-
const onError = (e) => this.captureGlobalException(errorEventToException(e));
|
|
1766
|
-
const onRejection = (e) => this.captureGlobalException(rejectionToException(e));
|
|
2627
|
+
const onError = (e) => this.captureGlobalException(errorEventToException(e), void 0, e.error);
|
|
2628
|
+
const onRejection = (e) => this.captureGlobalException(rejectionToException(e), void 0, e.reason);
|
|
1767
2629
|
win.addEventListener("error", onError);
|
|
1768
2630
|
win.addEventListener("unhandledrejection", onRejection);
|
|
1769
2631
|
this.cleanups.push(() => {
|
|
1770
2632
|
win.removeEventListener("error", onError);
|
|
1771
2633
|
win.removeEventListener("unhandledrejection", onRejection);
|
|
1772
2634
|
});
|
|
2635
|
+
const crumbs = this.options.errors?.breadcrumbs;
|
|
2636
|
+
if (crumbs !== false) this.cleanups.push(instrumentBreadcrumbs((c, hint) => this.errorScope.addBreadcrumb(c, hint), { console: crumbs?.console, http: crumbs?.http, apiUrl: this.options.apiUrl }));
|
|
2637
|
+
}
|
|
2638
|
+
/** An automatic breadcrumb, unless the `errors.breadcrumbs` option turns its kind off. */
|
|
2639
|
+
autoBreadcrumb(kind, crumb, hint) {
|
|
2640
|
+
const o = this.options.errors?.breadcrumbs;
|
|
2641
|
+
if (o === false || o && o[kind] === false) return;
|
|
2642
|
+
this.errorScope.addBreadcrumb(crumb, hint === void 0 ? void 0 : { event: hint });
|
|
1773
2643
|
}
|
|
1774
2644
|
/** Re-fetches the published config and re-evaluates triggers. */
|
|
1775
2645
|
async refresh() {
|
|
@@ -1817,6 +2687,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1817
2687
|
this.queue.destroy();
|
|
1818
2688
|
this.outbox.destroy();
|
|
1819
2689
|
this.agentTurns.destroy();
|
|
2690
|
+
this.tracer?.destroy();
|
|
1820
2691
|
this.flags.destroy();
|
|
1821
2692
|
this.emitter.clear();
|
|
1822
2693
|
destroyHost();
|
|
@@ -1946,6 +2817,11 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1946
2817
|
analyticsEnabled() {
|
|
1947
2818
|
return this.options.analytics && this.consent === "granted" && (this.config?.settings?.analyticsEnabled ?? true);
|
|
1948
2819
|
}
|
|
2820
|
+
/** The host app's version (`appVersion` option), sent with every event and recording. */
|
|
2821
|
+
appVersion() {
|
|
2822
|
+
const v = this.options.appVersion;
|
|
2823
|
+
return v === void 0 || v === null || String(v).trim() === "" ? void 0 : String(v).trim().slice(0, 200);
|
|
2824
|
+
}
|
|
1949
2825
|
collectUrls() {
|
|
1950
2826
|
return this.config?.settings?.collectUrls ?? true;
|
|
1951
2827
|
}
|
|
@@ -1961,12 +2837,14 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1961
2837
|
accountId: this.user?.account?.id || null,
|
|
1962
2838
|
sessionId: this.session.id,
|
|
1963
2839
|
url: this.collectUrls() ? this.pageHref() : void 0,
|
|
2840
|
+
...this.appVersion() ? { appVersion: this.appVersion() } : {},
|
|
1964
2841
|
...extra
|
|
1965
2842
|
};
|
|
1966
2843
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
1967
2844
|
const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
|
|
1968
2845
|
if (locale) event.properties = { ...event.properties ?? {}, locale };
|
|
1969
2846
|
this.queue.enqueue(event);
|
|
2847
|
+
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 } : {} } });
|
|
1970
2848
|
this.replay?.addCustomEvent("guidely", { name, customName: event.customName, guideId: event.guideId });
|
|
1971
2849
|
this.emitter.emit("track", event);
|
|
1972
2850
|
return event;
|
|
@@ -2020,6 +2898,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2020
2898
|
handleNavigation(url, explicit = false) {
|
|
2021
2899
|
if (this.destroyed) return;
|
|
2022
2900
|
const changed = url !== this.lastUrl;
|
|
2901
|
+
if (changed && this.lastUrl) this.autoBreadcrumb("navigation", { category: "navigation", data: this.collectUrls() ? { from: breadcrumbUrl(this.lastUrl), to: breadcrumbUrl(url) } : {} });
|
|
2023
2902
|
this.lastUrl = url;
|
|
2024
2903
|
if (changed) this.syncFlagAttributes();
|
|
2025
2904
|
if (!explicit && changed) {
|
|
@@ -2048,6 +2927,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2048
2927
|
handleDocumentClick(e) {
|
|
2049
2928
|
const target = composedTarget(e);
|
|
2050
2929
|
if (!target || typeof target.closest !== "function") return;
|
|
2930
|
+
if (!isIgnoredElement(target)) this.autoBreadcrumb("clicks", { category: "ui.click", message: elementSelector(interactiveElement(target) ?? target) }, e);
|
|
2051
2931
|
this.autocaptureClick(e, target);
|
|
2052
2932
|
this.completeChecklistTasksBy("click", target);
|
|
2053
2933
|
if (this.isReady) this.evaluate("click", { target });
|
|
@@ -2057,17 +2937,17 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2057
2937
|
// ---------------------------------------------------------------------------
|
|
2058
2938
|
/** Records activity; starts a new session after `sessionTimeoutMinutes` of inactivity or 24h. */
|
|
2059
2939
|
touchSession() {
|
|
2060
|
-
const
|
|
2940
|
+
const now3 = Date.now();
|
|
2061
2941
|
const s = this.session;
|
|
2062
2942
|
const timeout = Math.max(1, this.config?.settings?.sessionTimeoutMinutes ?? 30) * 6e4;
|
|
2063
|
-
const last = s.lastActivityAt ??
|
|
2064
|
-
if (
|
|
2065
|
-
if (
|
|
2066
|
-
s.lastActivityAt =
|
|
2943
|
+
const last = s.lastActivityAt ?? now3;
|
|
2944
|
+
if (now3 - last > timeout || now3 - (Date.parse(s.startedAt) || now3) > DAY_MS) return this.rotateSession(now3);
|
|
2945
|
+
if (now3 - (s.lastActivityAt ?? 0) < 1e3) return;
|
|
2946
|
+
s.lastActivityAt = now3;
|
|
2067
2947
|
this.persistSession();
|
|
2068
2948
|
}
|
|
2069
|
-
rotateSession(
|
|
2070
|
-
this.session = newSession(
|
|
2949
|
+
rotateSession(now3) {
|
|
2950
|
+
this.session = newSession(now3);
|
|
2071
2951
|
this.persistSession();
|
|
2072
2952
|
this.log("session rotated", this.session.id);
|
|
2073
2953
|
if (this.replay?.activeSessionId) this.replay.stop();
|
|
@@ -2099,7 +2979,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2099
2979
|
const device = detectDevice(navigator.userAgent, window.innerWidth);
|
|
2100
2980
|
const props = { deviceType: device.type, browser: device.browser, os: device.os, screenWidth: window.screen?.width, viewportWidth: window.innerWidth };
|
|
2101
2981
|
if (collect) {
|
|
2102
|
-
props.path =
|
|
2982
|
+
props.path = pathOf3(url);
|
|
2103
2983
|
props.title = (document.title || "").slice(0, 200);
|
|
2104
2984
|
const ref = !this.session.pageViews ? externalReferrer() : void 0;
|
|
2105
2985
|
if (ref) props.referrer = ref;
|
|
@@ -2118,7 +2998,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2118
2998
|
if (!this.pageLeft && this.autocaptureOn("pageLeave")) {
|
|
2119
2999
|
const info = t.leave();
|
|
2120
3000
|
const collect = this.collectUrls();
|
|
2121
|
-
this.enqueue("page_leave", { url: collect ? info.url : void 0, properties: { ...collect ? { path:
|
|
3001
|
+
this.enqueue("page_leave", { url: collect ? info.url : void 0, properties: { ...collect ? { path: pathOf3(info.url) } : {}, durationMs: info.durationMs, scrollDepth: info.scrollDepth } });
|
|
2122
3002
|
}
|
|
2123
3003
|
if (nextUrl !== null) {
|
|
2124
3004
|
t.start(nextUrl);
|
|
@@ -2162,42 +3042,120 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2162
3042
|
if (!batch || !this.captureAllowed()) return;
|
|
2163
3043
|
const collect = this.collectUrls();
|
|
2164
3044
|
const de = document.documentElement;
|
|
2165
|
-
const properties = { path: collect ?
|
|
3045
|
+
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 };
|
|
2166
3046
|
if (!collect) delete properties.path;
|
|
2167
3047
|
this.enqueue("heatmap", { url: collect ? batch.url : void 0, properties });
|
|
2168
3048
|
}
|
|
2169
|
-
captureGlobalException(props) {
|
|
3049
|
+
captureGlobalException(props, detail, error) {
|
|
2170
3050
|
if (!props || isGuidelyException(props) || this.options.captureErrors === false) return;
|
|
3051
|
+
const d = detail ?? this.errorDetail(error, props, props.source === "unhandledrejection" ? "onunhandledrejection" : "onerror", {}, "error");
|
|
2171
3052
|
if (!this.config) {
|
|
2172
|
-
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, void 0]);
|
|
3053
|
+
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, void 0, d]);
|
|
2173
3054
|
return;
|
|
2174
3055
|
}
|
|
2175
|
-
this.sendException(props);
|
|
3056
|
+
this.sendException(props, void 0, d);
|
|
2176
3057
|
}
|
|
2177
3058
|
/**
|
|
2178
3059
|
* Reports a caught error as an `exception` event (`source: "manual"`, `handled: true`), grouped
|
|
2179
|
-
* into an issue on the Errors page.
|
|
2180
|
-
*
|
|
3060
|
+
* into an issue on the Errors page. The second argument takes Sentry's capture context (`tags`,
|
|
3061
|
+
* `extra`, `level`, `fingerprint`, `contexts`, `user`); any other key is attached to the occurrence
|
|
3062
|
+
* as a property. Needs the project's error tracking on (plus analytics + consent);
|
|
3063
|
+
* `captureErrors: false` does not stop it. Returns the event id (empty when nothing is sent).
|
|
2181
3064
|
*/
|
|
2182
|
-
captureException(error,
|
|
2183
|
-
if (this.destroyed) return;
|
|
2184
|
-
const
|
|
3065
|
+
captureException(error, hint) {
|
|
3066
|
+
if (this.destroyed) return "";
|
|
3067
|
+
const { ctx, properties } = splitHint(hint);
|
|
3068
|
+
const props = exceptionFromError(error, "manual", ctx.mechanism?.handled ?? true);
|
|
3069
|
+
return this.captureWith(props, properties, this.errorDetail(error, props, ctx.mechanism?.type ?? "generic", ctx, ctx.level ?? "error"));
|
|
3070
|
+
}
|
|
3071
|
+
/** Reports a message (default level `info`) as its own issue, grouped by the message. */
|
|
3072
|
+
captureMessage(message, level = "info", hint) {
|
|
3073
|
+
if (this.destroyed) return "";
|
|
3074
|
+
const props = { type: "Message", message: String(message).slice(0, 1e3), frames: [], source: "message", handled: true };
|
|
3075
|
+
return this.captureWith(props, {}, this.errorDetail(void 0, props, "message", hint ?? {}, hint?.level ?? level));
|
|
3076
|
+
}
|
|
3077
|
+
captureWith(props, properties, detail) {
|
|
2185
3078
|
if (!this.config) {
|
|
2186
|
-
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, properties]);
|
|
2187
|
-
return;
|
|
3079
|
+
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, properties, detail]);
|
|
3080
|
+
return "";
|
|
2188
3081
|
}
|
|
2189
|
-
this.sendException(props, properties);
|
|
3082
|
+
return this.sendException(props, properties, detail);
|
|
3083
|
+
}
|
|
3084
|
+
/** The `detail` of an error: scope, breadcrumbs, browser contexts and the `cause` chain. */
|
|
3085
|
+
errorDetail(error, props, mechanism, ctx, level) {
|
|
3086
|
+
const device = detectDevice(navigator.userAgent, window.innerWidth);
|
|
3087
|
+
return buildDetail({ error, props, mechanism, scope: this.errorScope, ctx, level, contexts: browserContexts(navigator.userAgent, device) });
|
|
3088
|
+
}
|
|
3089
|
+
/** Adds a breadcrumb (sent with the next errors; the last 100 are kept). */
|
|
3090
|
+
addBreadcrumb(crumb) {
|
|
3091
|
+
if (!this.destroyed) this.errorScope.addBreadcrumb(crumb);
|
|
3092
|
+
}
|
|
3093
|
+
/** Tags every later error (searchable on the issue page). */
|
|
3094
|
+
setTag(key, value) {
|
|
3095
|
+
if (value === null || value === void 0) delete this.errorScope.tags[key];
|
|
3096
|
+
else this.errorScope.tags[key] = String(value);
|
|
3097
|
+
}
|
|
3098
|
+
setTags(tags) {
|
|
3099
|
+
for (const [k, v] of Object.entries(tags)) this.setTag(k, v);
|
|
2190
3100
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
3101
|
+
/** Arbitrary data attached to every later error. */
|
|
3102
|
+
setExtra(key, value) {
|
|
3103
|
+
if (value === void 0) delete this.errorScope.extra[key];
|
|
3104
|
+
else this.errorScope.extra[key] = value;
|
|
3105
|
+
}
|
|
3106
|
+
setExtras(extras) {
|
|
3107
|
+
for (const [k, v] of Object.entries(extras)) this.setExtra(k, v);
|
|
3108
|
+
}
|
|
3109
|
+
/** A named context (`setContext("cart", { items: 3 })`) on every later error; null removes it. */
|
|
3110
|
+
setContext(name, context) {
|
|
3111
|
+
if (context) this.errorScope.contexts[name] = context;
|
|
3112
|
+
else delete this.errorScope.contexts[name];
|
|
3113
|
+
}
|
|
3114
|
+
/**
|
|
3115
|
+
* Sentry's `setUser`: identifies the user by `id` (like `identify`) and keeps `email` / `username`
|
|
3116
|
+
* on their errors. `null` forgets the user (`reset()`).
|
|
3117
|
+
*/
|
|
3118
|
+
setUser(user) {
|
|
3119
|
+
if (!user) {
|
|
3120
|
+
this.errorScope.user = {};
|
|
3121
|
+
return this.reset();
|
|
3122
|
+
}
|
|
3123
|
+
const { id, email, username, ...traits } = user;
|
|
3124
|
+
this.errorScope.user = { ...email ? { email: String(email) } : {}, ...username ? { username: String(username) } : {} };
|
|
3125
|
+
if (id !== void 0 && id !== null && String(id)) void this.identify(String(id), { ...traits, ...email ? { email } : {}, ...username ? { username } : {} });
|
|
3126
|
+
}
|
|
3127
|
+
sendException(props, extra, detail) {
|
|
3128
|
+
if (!this.config?.settings?.errorTracking?.enabled || !this.captureAllowed()) return "";
|
|
3129
|
+
const o = this.options.errors;
|
|
3130
|
+
const file = props.frames.find((f) => f.file)?.file;
|
|
3131
|
+
if (o && errorFilterDrops({ type: props.type, message: props.message, file }, o)) return "";
|
|
3132
|
+
if (o?.sampleRate !== void 0 && o.sampleRate < 1 && Math.random() >= Math.max(0, o.sampleRate)) return "";
|
|
3133
|
+
if (!this.exceptionThrottle.allow(props)) return "";
|
|
2193
3134
|
const count = this.session.exceptions ?? 0;
|
|
2194
|
-
if (count >= MAX_SESSION_EXCEPTIONS) return;
|
|
2195
|
-
const
|
|
3135
|
+
if (count >= MAX_SESSION_EXCEPTIONS) return "";
|
|
3136
|
+
const mechanism = detail?.exception?.values.at(-1)?.mechanism?.type;
|
|
3137
|
+
let properties = { ...props, level: detail?.level ?? "error", platform: "javascript", ...mechanism ? { mechanism } : {} };
|
|
2196
3138
|
if (this.options.release) properties.release = String(this.options.release).slice(0, 100);
|
|
3139
|
+
const trace = this.tracer?.traceContext();
|
|
3140
|
+
if (trace) Object.assign(properties, { traceId: trace.traceId, spanId: trace.spanId });
|
|
2197
3141
|
for (const [k, v] of Object.entries(extra ?? {})) if (!(k in properties)) properties[k] = v;
|
|
2198
|
-
|
|
3142
|
+
let d = detail;
|
|
3143
|
+
if (o?.beforeSend) {
|
|
3144
|
+
try {
|
|
3145
|
+
const out = o.beforeSend({ properties, detail: d ?? {} });
|
|
3146
|
+
if (!out) return "";
|
|
3147
|
+
properties = out.properties;
|
|
3148
|
+
d = out.detail;
|
|
3149
|
+
} catch (err) {
|
|
3150
|
+
this.reportError(err);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
const event = this.enqueue("exception", { properties, ...d ? { detail: d } : {} });
|
|
3154
|
+
if (!event) return "";
|
|
3155
|
+
this.lastExceptionId = event.eventId;
|
|
2199
3156
|
this.session.exceptions = count + 1;
|
|
2200
3157
|
this.persistSession();
|
|
3158
|
+
return event.eventId;
|
|
2201
3159
|
}
|
|
2202
3160
|
maskSelector() {
|
|
2203
3161
|
return validSelectors([...DEFAULT_MASK_SELECTORS, ...this.config?.settings?.maskSelectors || [], ...this.options.maskSelectors || []]).join(",");
|
|
@@ -2231,9 +3189,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2231
3189
|
this.trackPageView(this.pageHref());
|
|
2232
3190
|
}
|
|
2233
3191
|
if (this.config) {
|
|
2234
|
-
for (const [props, extra] of this.earlyExceptions.splice(0)) {
|
|
2235
|
-
if (props.handled) this.sendException(props, extra);
|
|
2236
|
-
else this.captureGlobalException(props);
|
|
3192
|
+
for (const [props, extra, detail] of this.earlyExceptions.splice(0)) {
|
|
3193
|
+
if (props.handled) this.sendException(props, extra, detail);
|
|
3194
|
+
else this.captureGlobalException(props, detail);
|
|
2237
3195
|
}
|
|
2238
3196
|
}
|
|
2239
3197
|
this.syncReplay();
|
|
@@ -2266,7 +3224,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2266
3224
|
url: this.collectUrls() ? this.pageHref() : void 0,
|
|
2267
3225
|
device: { type: device.type, browser: device.browser, os: device.os },
|
|
2268
3226
|
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
2269
|
-
sdk: { name: SDK_NAME, version: SDK_VERSION }
|
|
3227
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
3228
|
+
...this.appVersion() ? { appVersion: this.appVersion() } : {}
|
|
2270
3229
|
};
|
|
2271
3230
|
},
|
|
2272
3231
|
onDisabled: (sessionId) => {
|
|
@@ -2340,7 +3299,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2340
3299
|
seenGuides: keysOf(Object.keys(guides).filter((id) => (guides[id]?.impressions ?? 0) > 0)),
|
|
2341
3300
|
completedChecklists: keysOf(completedChecklists),
|
|
2342
3301
|
audienceIds: this.state.audienceIds,
|
|
2343
|
-
environment: this.
|
|
3302
|
+
environment: this.environment,
|
|
2344
3303
|
platform: "web",
|
|
2345
3304
|
...this.options.appVersion ? { appVersion: this.options.appVersion } : {}
|
|
2346
3305
|
});
|
|
@@ -2503,17 +3462,17 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2503
3462
|
this.evaluate("visible", { guideId: guide.id });
|
|
2504
3463
|
};
|
|
2505
3464
|
if (typeof IntersectionObserver === "undefined") return fire();
|
|
2506
|
-
io = new IntersectionObserver((
|
|
2507
|
-
if (
|
|
3465
|
+
io = new IntersectionObserver((entries2) => {
|
|
3466
|
+
if (entries2.some((e) => e.isIntersecting)) fire();
|
|
2508
3467
|
});
|
|
2509
3468
|
io.observe(el);
|
|
2510
3469
|
}).catch(() => stop());
|
|
2511
3470
|
}
|
|
2512
|
-
setTimer(fn,
|
|
3471
|
+
setTimer(fn, ms2) {
|
|
2513
3472
|
const t = setTimeout(() => {
|
|
2514
3473
|
this.timers.delete(t);
|
|
2515
3474
|
if (!this.destroyed) fn();
|
|
2516
|
-
},
|
|
3475
|
+
}, ms2);
|
|
2517
3476
|
this.timers.add(t);
|
|
2518
3477
|
}
|
|
2519
3478
|
/** Decides whether a guide can show now, otherwise queues it. */
|
|
@@ -2654,6 +3613,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2654
3613
|
if (action === "start_guide" && value) this.setTimer(() => this.startGuide(value), 0);
|
|
2655
3614
|
else if (action === "event" && value) this.track(value);
|
|
2656
3615
|
else if (action === "open_resource_center") this.openResourceCenter();
|
|
3616
|
+
else if (action === "open_article" && value) this.openArticle(value);
|
|
2657
3617
|
},
|
|
2658
3618
|
onEnd: (reason) => {
|
|
2659
3619
|
if (exclusive) {
|
|
@@ -3064,6 +4024,19 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3064
4024
|
if (!this.resourceCenter) this.renderPersistentWidgets();
|
|
3065
4025
|
this.resourceCenter?.open();
|
|
3066
4026
|
}
|
|
4027
|
+
/**
|
|
4028
|
+
* Opens one help article: in Scout's Help tab when the messenger is on, otherwise in the resource
|
|
4029
|
+
* center. Help-article links in guides (`#guidely-article:<id>`) call this.
|
|
4030
|
+
*/
|
|
4031
|
+
openArticle(articleId) {
|
|
4032
|
+
if (this.config?.messenger?.enabled) {
|
|
4033
|
+
void this.loadMessenger().then((m) => m?.openArticle(articleId));
|
|
4034
|
+
return;
|
|
4035
|
+
}
|
|
4036
|
+
if (!this.resourceCenter) this.renderPersistentWidgets();
|
|
4037
|
+
if (this.resourceCenter) this.resourceCenter.openArticle(articleId);
|
|
4038
|
+
else this.log("openArticle: the resource center is not enabled or published for this environment");
|
|
4039
|
+
}
|
|
3067
4040
|
closeResourceCenter() {
|
|
3068
4041
|
if (this.messenger) return this.closeMessenger();
|
|
3069
4042
|
this.resourceCenter?.close();
|
|
@@ -3104,6 +4077,49 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3104
4077
|
if (!this.config?.messenger?.enabled || !this.config.messenger.ideas) return;
|
|
3105
4078
|
void this.loadMessenger().then((m) => m?.openIdeas(requestId));
|
|
3106
4079
|
}
|
|
4080
|
+
/**
|
|
4081
|
+
* Sends a bug report to Monitor → User feedback (Sentry's `captureFeedback`). It is linked to the visitor,
|
|
4082
|
+
* this session's recording and, unless `associatedEventId` says otherwise, the last error this page
|
|
4083
|
+
* reported. Resolves with the report's id; rejects when it can't be sent.
|
|
4084
|
+
*/
|
|
4085
|
+
async captureFeedback(input, source = "api") {
|
|
4086
|
+
if (this.destroyed) throw new Error("[Guidely] captureFeedback: the SDK was destroyed");
|
|
4087
|
+
if (!input || !String(input.message ?? "").trim()) throw new Error("[Guidely] captureFeedback: message is required");
|
|
4088
|
+
const screenshot = await encodeScreenshot(input.screenshot);
|
|
4089
|
+
if (this.lastExceptionId) await this.queue.flush().catch(() => {
|
|
4090
|
+
});
|
|
4091
|
+
const body = bugReportBody(
|
|
4092
|
+
input,
|
|
4093
|
+
{ 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 } },
|
|
4094
|
+
source,
|
|
4095
|
+
screenshot
|
|
4096
|
+
);
|
|
4097
|
+
return sendBugReport(this.api, body);
|
|
4098
|
+
}
|
|
4099
|
+
/** Opens the "Report a bug" dialog (message, name, email, a screenshot), which sends with captureFeedback. */
|
|
4100
|
+
showBugReport(options = {}) {
|
|
4101
|
+
if (this.destroyed || typeof document === "undefined" || this.bugReportDialog) return;
|
|
4102
|
+
void import("@guidelytech/guide-runtime/bug-report").then(({ renderBugReport }) => {
|
|
4103
|
+
if (this.destroyed || this.bugReportDialog) return;
|
|
4104
|
+
const scope = createScope(this.theme());
|
|
4105
|
+
const traits = this.user?.traits ?? {};
|
|
4106
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
4107
|
+
this.bugReportDialog = renderBugReport({
|
|
4108
|
+
scope: scope.el,
|
|
4109
|
+
title: options.title,
|
|
4110
|
+
message: options.message,
|
|
4111
|
+
name: options.name ?? str(traits.name),
|
|
4112
|
+
email: options.email ?? str(traits.email),
|
|
4113
|
+
onSubmit: async (i) => {
|
|
4114
|
+
await this.captureFeedback({ message: i.message, name: i.name || null, email: i.email || null, screenshot: i.screenshot }, "widget");
|
|
4115
|
+
},
|
|
4116
|
+
onClose: () => {
|
|
4117
|
+
this.bugReportDialog = null;
|
|
4118
|
+
scope.destroy();
|
|
4119
|
+
}
|
|
4120
|
+
});
|
|
4121
|
+
}).catch((err) => this.reportError(err));
|
|
4122
|
+
}
|
|
3107
4123
|
/** Opens a new messenger conversation, optionally pre-filling the message. */
|
|
3108
4124
|
showNewMessage(text) {
|
|
3109
4125
|
if (!this.config?.messenger?.enabled) return;
|
|
@@ -3134,12 +4150,12 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3134
4150
|
const account = this.user?.account;
|
|
3135
4151
|
return {
|
|
3136
4152
|
url: this.pageHref(),
|
|
3137
|
-
path:
|
|
4153
|
+
path: pathOf3(this.pageHref()),
|
|
3138
4154
|
deviceType: device.type,
|
|
3139
4155
|
browser: device.browser,
|
|
3140
4156
|
os: device.os,
|
|
3141
4157
|
locale: navigator.language || void 0,
|
|
3142
|
-
environment: this.
|
|
4158
|
+
environment: this.environment,
|
|
3143
4159
|
platform: "web",
|
|
3144
4160
|
...this.options.appVersion ? { appVersion: this.options.appVersion } : {},
|
|
3145
4161
|
...this.user?.traits ?? {},
|
|
@@ -3316,7 +4332,7 @@ var current2 = null;
|
|
|
3316
4332
|
function webOnly(config) {
|
|
3317
4333
|
return config.guides.every((g) => supportsPlatform(g.config, "web")) ? config : { ...config, guides: config.guides.filter((g) => supportsPlatform(g.config, "web")) };
|
|
3318
4334
|
}
|
|
3319
|
-
function
|
|
4335
|
+
function pathOf3(url) {
|
|
3320
4336
|
try {
|
|
3321
4337
|
return new URL(url, "http://localhost").pathname;
|
|
3322
4338
|
} catch {
|
|
@@ -3367,6 +4383,7 @@ export {
|
|
|
3367
4383
|
createStorage,
|
|
3368
4384
|
index_default as default,
|
|
3369
4385
|
detectDevice,
|
|
4386
|
+
environmentFromKey,
|
|
3370
4387
|
isAppUrl,
|
|
3371
4388
|
isExclusiveGuide,
|
|
3372
4389
|
isFrequencyEligible,
|