@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.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 { articleIdFromHref, 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";
|
|
@@ -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,7 +1370,7 @@ 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,
|
|
@@ -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);
|
|
@@ -1711,9 +2564,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1711
2564
|
};
|
|
1712
2565
|
let lastScroll = 0;
|
|
1713
2566
|
const onScroll = () => {
|
|
1714
|
-
const
|
|
1715
|
-
if (
|
|
1716
|
-
lastScroll =
|
|
2567
|
+
const now3 = Date.now();
|
|
2568
|
+
if (now3 - lastScroll < 250) return;
|
|
2569
|
+
lastScroll = now3;
|
|
1717
2570
|
this.pageTracker.measureScroll();
|
|
1718
2571
|
this.touchSession();
|
|
1719
2572
|
};
|
|
@@ -1771,14 +2624,22 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1771
2624
|
/** Uncaught errors + unhandled rejections; attached before the config loads so boot errors are kept. */
|
|
1772
2625
|
wireErrorListeners() {
|
|
1773
2626
|
const win = window;
|
|
1774
|
-
const onError = (e) => this.captureGlobalException(errorEventToException(e));
|
|
1775
|
-
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);
|
|
1776
2629
|
win.addEventListener("error", onError);
|
|
1777
2630
|
win.addEventListener("unhandledrejection", onRejection);
|
|
1778
2631
|
this.cleanups.push(() => {
|
|
1779
2632
|
win.removeEventListener("error", onError);
|
|
1780
2633
|
win.removeEventListener("unhandledrejection", onRejection);
|
|
1781
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 });
|
|
1782
2643
|
}
|
|
1783
2644
|
/** Re-fetches the published config and re-evaluates triggers. */
|
|
1784
2645
|
async refresh() {
|
|
@@ -1826,6 +2687,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1826
2687
|
this.queue.destroy();
|
|
1827
2688
|
this.outbox.destroy();
|
|
1828
2689
|
this.agentTurns.destroy();
|
|
2690
|
+
this.tracer?.destroy();
|
|
1829
2691
|
this.flags.destroy();
|
|
1830
2692
|
this.emitter.clear();
|
|
1831
2693
|
destroyHost();
|
|
@@ -1955,6 +2817,11 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1955
2817
|
analyticsEnabled() {
|
|
1956
2818
|
return this.options.analytics && this.consent === "granted" && (this.config?.settings?.analyticsEnabled ?? true);
|
|
1957
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
|
+
}
|
|
1958
2825
|
collectUrls() {
|
|
1959
2826
|
return this.config?.settings?.collectUrls ?? true;
|
|
1960
2827
|
}
|
|
@@ -1970,12 +2837,14 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1970
2837
|
accountId: this.user?.account?.id || null,
|
|
1971
2838
|
sessionId: this.session.id,
|
|
1972
2839
|
url: this.collectUrls() ? this.pageHref() : void 0,
|
|
2840
|
+
...this.appVersion() ? { appVersion: this.appVersion() } : {},
|
|
1973
2841
|
...extra
|
|
1974
2842
|
};
|
|
1975
2843
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
1976
2844
|
const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
|
|
1977
2845
|
if (locale) event.properties = { ...event.properties ?? {}, locale };
|
|
1978
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 } : {} } });
|
|
1979
2848
|
this.replay?.addCustomEvent("guidely", { name, customName: event.customName, guideId: event.guideId });
|
|
1980
2849
|
this.emitter.emit("track", event);
|
|
1981
2850
|
return event;
|
|
@@ -2029,6 +2898,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2029
2898
|
handleNavigation(url, explicit = false) {
|
|
2030
2899
|
if (this.destroyed) return;
|
|
2031
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) } : {} });
|
|
2032
2902
|
this.lastUrl = url;
|
|
2033
2903
|
if (changed) this.syncFlagAttributes();
|
|
2034
2904
|
if (!explicit && changed) {
|
|
@@ -2057,6 +2927,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2057
2927
|
handleDocumentClick(e) {
|
|
2058
2928
|
const target = composedTarget(e);
|
|
2059
2929
|
if (!target || typeof target.closest !== "function") return;
|
|
2930
|
+
if (!isIgnoredElement(target)) this.autoBreadcrumb("clicks", { category: "ui.click", message: elementSelector(interactiveElement(target) ?? target) }, e);
|
|
2060
2931
|
this.autocaptureClick(e, target);
|
|
2061
2932
|
this.completeChecklistTasksBy("click", target);
|
|
2062
2933
|
if (this.isReady) this.evaluate("click", { target });
|
|
@@ -2066,17 +2937,17 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2066
2937
|
// ---------------------------------------------------------------------------
|
|
2067
2938
|
/** Records activity; starts a new session after `sessionTimeoutMinutes` of inactivity or 24h. */
|
|
2068
2939
|
touchSession() {
|
|
2069
|
-
const
|
|
2940
|
+
const now3 = Date.now();
|
|
2070
2941
|
const s = this.session;
|
|
2071
2942
|
const timeout = Math.max(1, this.config?.settings?.sessionTimeoutMinutes ?? 30) * 6e4;
|
|
2072
|
-
const last = s.lastActivityAt ??
|
|
2073
|
-
if (
|
|
2074
|
-
if (
|
|
2075
|
-
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;
|
|
2076
2947
|
this.persistSession();
|
|
2077
2948
|
}
|
|
2078
|
-
rotateSession(
|
|
2079
|
-
this.session = newSession(
|
|
2949
|
+
rotateSession(now3) {
|
|
2950
|
+
this.session = newSession(now3);
|
|
2080
2951
|
this.persistSession();
|
|
2081
2952
|
this.log("session rotated", this.session.id);
|
|
2082
2953
|
if (this.replay?.activeSessionId) this.replay.stop();
|
|
@@ -2108,7 +2979,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2108
2979
|
const device = detectDevice(navigator.userAgent, window.innerWidth);
|
|
2109
2980
|
const props = { deviceType: device.type, browser: device.browser, os: device.os, screenWidth: window.screen?.width, viewportWidth: window.innerWidth };
|
|
2110
2981
|
if (collect) {
|
|
2111
|
-
props.path =
|
|
2982
|
+
props.path = pathOf3(url);
|
|
2112
2983
|
props.title = (document.title || "").slice(0, 200);
|
|
2113
2984
|
const ref = !this.session.pageViews ? externalReferrer() : void 0;
|
|
2114
2985
|
if (ref) props.referrer = ref;
|
|
@@ -2127,7 +2998,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2127
2998
|
if (!this.pageLeft && this.autocaptureOn("pageLeave")) {
|
|
2128
2999
|
const info = t.leave();
|
|
2129
3000
|
const collect = this.collectUrls();
|
|
2130
|
-
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 } });
|
|
2131
3002
|
}
|
|
2132
3003
|
if (nextUrl !== null) {
|
|
2133
3004
|
t.start(nextUrl);
|
|
@@ -2171,42 +3042,120 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2171
3042
|
if (!batch || !this.captureAllowed()) return;
|
|
2172
3043
|
const collect = this.collectUrls();
|
|
2173
3044
|
const de = document.documentElement;
|
|
2174
|
-
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 };
|
|
2175
3046
|
if (!collect) delete properties.path;
|
|
2176
3047
|
this.enqueue("heatmap", { url: collect ? batch.url : void 0, properties });
|
|
2177
3048
|
}
|
|
2178
|
-
captureGlobalException(props) {
|
|
3049
|
+
captureGlobalException(props, detail, error) {
|
|
2179
3050
|
if (!props || isGuidelyException(props) || this.options.captureErrors === false) return;
|
|
3051
|
+
const d = detail ?? this.errorDetail(error, props, props.source === "unhandledrejection" ? "onunhandledrejection" : "onerror", {}, "error");
|
|
2180
3052
|
if (!this.config) {
|
|
2181
|
-
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, void 0]);
|
|
3053
|
+
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, void 0, d]);
|
|
2182
3054
|
return;
|
|
2183
3055
|
}
|
|
2184
|
-
this.sendException(props);
|
|
3056
|
+
this.sendException(props, void 0, d);
|
|
2185
3057
|
}
|
|
2186
3058
|
/**
|
|
2187
3059
|
* Reports a caught error as an `exception` event (`source: "manual"`, `handled: true`), grouped
|
|
2188
|
-
* into an issue on the Errors page.
|
|
2189
|
-
*
|
|
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).
|
|
2190
3064
|
*/
|
|
2191
|
-
captureException(error,
|
|
2192
|
-
if (this.destroyed) return;
|
|
2193
|
-
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) {
|
|
2194
3078
|
if (!this.config) {
|
|
2195
|
-
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, properties]);
|
|
2196
|
-
return;
|
|
3079
|
+
if (this.earlyExceptions.length < 10) this.earlyExceptions.push([props, properties, detail]);
|
|
3080
|
+
return "";
|
|
2197
3081
|
}
|
|
2198
|
-
this.sendException(props, properties);
|
|
3082
|
+
return this.sendException(props, properties, detail);
|
|
2199
3083
|
}
|
|
2200
|
-
|
|
2201
|
-
|
|
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);
|
|
3100
|
+
}
|
|
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 "";
|
|
2202
3134
|
const count = this.session.exceptions ?? 0;
|
|
2203
|
-
if (count >= MAX_SESSION_EXCEPTIONS) return;
|
|
2204
|
-
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 } : {} };
|
|
2205
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 });
|
|
2206
3141
|
for (const [k, v] of Object.entries(extra ?? {})) if (!(k in properties)) properties[k] = v;
|
|
2207
|
-
|
|
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;
|
|
2208
3156
|
this.session.exceptions = count + 1;
|
|
2209
3157
|
this.persistSession();
|
|
3158
|
+
return event.eventId;
|
|
2210
3159
|
}
|
|
2211
3160
|
maskSelector() {
|
|
2212
3161
|
return validSelectors([...DEFAULT_MASK_SELECTORS, ...this.config?.settings?.maskSelectors || [], ...this.options.maskSelectors || []]).join(",");
|
|
@@ -2240,9 +3189,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2240
3189
|
this.trackPageView(this.pageHref());
|
|
2241
3190
|
}
|
|
2242
3191
|
if (this.config) {
|
|
2243
|
-
for (const [props, extra] of this.earlyExceptions.splice(0)) {
|
|
2244
|
-
if (props.handled) this.sendException(props, extra);
|
|
2245
|
-
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);
|
|
2246
3195
|
}
|
|
2247
3196
|
}
|
|
2248
3197
|
this.syncReplay();
|
|
@@ -2275,7 +3224,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2275
3224
|
url: this.collectUrls() ? this.pageHref() : void 0,
|
|
2276
3225
|
device: { type: device.type, browser: device.browser, os: device.os },
|
|
2277
3226
|
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
2278
|
-
sdk: { name: SDK_NAME, version: SDK_VERSION }
|
|
3227
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
3228
|
+
...this.appVersion() ? { appVersion: this.appVersion() } : {}
|
|
2279
3229
|
};
|
|
2280
3230
|
},
|
|
2281
3231
|
onDisabled: (sessionId) => {
|
|
@@ -2349,7 +3299,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2349
3299
|
seenGuides: keysOf(Object.keys(guides).filter((id) => (guides[id]?.impressions ?? 0) > 0)),
|
|
2350
3300
|
completedChecklists: keysOf(completedChecklists),
|
|
2351
3301
|
audienceIds: this.state.audienceIds,
|
|
2352
|
-
environment: this.
|
|
3302
|
+
environment: this.environment,
|
|
2353
3303
|
platform: "web",
|
|
2354
3304
|
...this.options.appVersion ? { appVersion: this.options.appVersion } : {}
|
|
2355
3305
|
});
|
|
@@ -2512,17 +3462,17 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2512
3462
|
this.evaluate("visible", { guideId: guide.id });
|
|
2513
3463
|
};
|
|
2514
3464
|
if (typeof IntersectionObserver === "undefined") return fire();
|
|
2515
|
-
io = new IntersectionObserver((
|
|
2516
|
-
if (
|
|
3465
|
+
io = new IntersectionObserver((entries2) => {
|
|
3466
|
+
if (entries2.some((e) => e.isIntersecting)) fire();
|
|
2517
3467
|
});
|
|
2518
3468
|
io.observe(el);
|
|
2519
3469
|
}).catch(() => stop());
|
|
2520
3470
|
}
|
|
2521
|
-
setTimer(fn,
|
|
3471
|
+
setTimer(fn, ms2) {
|
|
2522
3472
|
const t = setTimeout(() => {
|
|
2523
3473
|
this.timers.delete(t);
|
|
2524
3474
|
if (!this.destroyed) fn();
|
|
2525
|
-
},
|
|
3475
|
+
}, ms2);
|
|
2526
3476
|
this.timers.add(t);
|
|
2527
3477
|
}
|
|
2528
3478
|
/** Decides whether a guide can show now, otherwise queues it. */
|
|
@@ -3127,6 +4077,49 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3127
4077
|
if (!this.config?.messenger?.enabled || !this.config.messenger.ideas) return;
|
|
3128
4078
|
void this.loadMessenger().then((m) => m?.openIdeas(requestId));
|
|
3129
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
|
+
}
|
|
3130
4123
|
/** Opens a new messenger conversation, optionally pre-filling the message. */
|
|
3131
4124
|
showNewMessage(text) {
|
|
3132
4125
|
if (!this.config?.messenger?.enabled) return;
|
|
@@ -3157,12 +4150,12 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3157
4150
|
const account = this.user?.account;
|
|
3158
4151
|
return {
|
|
3159
4152
|
url: this.pageHref(),
|
|
3160
|
-
path:
|
|
4153
|
+
path: pathOf3(this.pageHref()),
|
|
3161
4154
|
deviceType: device.type,
|
|
3162
4155
|
browser: device.browser,
|
|
3163
4156
|
os: device.os,
|
|
3164
4157
|
locale: navigator.language || void 0,
|
|
3165
|
-
environment: this.
|
|
4158
|
+
environment: this.environment,
|
|
3166
4159
|
platform: "web",
|
|
3167
4160
|
...this.options.appVersion ? { appVersion: this.options.appVersion } : {},
|
|
3168
4161
|
...this.user?.traits ?? {},
|
|
@@ -3339,7 +4332,7 @@ var current2 = null;
|
|
|
3339
4332
|
function webOnly(config) {
|
|
3340
4333
|
return config.guides.every((g) => supportsPlatform(g.config, "web")) ? config : { ...config, guides: config.guides.filter((g) => supportsPlatform(g.config, "web")) };
|
|
3341
4334
|
}
|
|
3342
|
-
function
|
|
4335
|
+
function pathOf3(url) {
|
|
3343
4336
|
try {
|
|
3344
4337
|
return new URL(url, "http://localhost").pathname;
|
|
3345
4338
|
} catch {
|
|
@@ -3390,6 +4383,7 @@ export {
|
|
|
3390
4383
|
createStorage,
|
|
3391
4384
|
index_default as default,
|
|
3392
4385
|
detectDevice,
|
|
4386
|
+
environmentFromKey,
|
|
3393
4387
|
isAppUrl,
|
|
3394
4388
|
isExclusiveGuide,
|
|
3395
4389
|
isFrequencyEligible,
|