@guidelytech/sdk-core 0.10.0 → 0.11.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/dist/{capture-DVJyQ2JZ.d.cts → capture-CgN69Lxq.d.cts} +2 -2
- package/dist/{capture-369s4YYT.d.ts → capture-Csz721-A.d.ts} +2 -2
- package/dist/capture.cjs +2 -0
- package/dist/capture.cjs.map +1 -1
- package/dist/capture.d.cts +3 -3
- package/dist/capture.d.ts +3 -3
- package/dist/capture.js +1 -1
- package/dist/{chunk-C4WWSF67.js → chunk-JUSM7J5N.js} +3 -1
- package/dist/{chunk-C4WWSF67.js.map → chunk-JUSM7J5N.js.map} +1 -1
- package/dist/{chunk-GDJB466J.js → chunk-ZAF2KRJA.js} +29 -6
- package/dist/chunk-ZAF2KRJA.js.map +1 -0
- package/dist/headless.cjs +32 -4
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +6 -5
- package/dist/headless.d.ts +6 -5
- package/dist/headless.js +9 -3
- package/dist/{health-DFHUstWd.d.cts → health-sbP9JwTE.d.cts} +2 -0
- package/dist/{health-DFHUstWd.d.ts → health-sbP9JwTE.d.ts} +2 -0
- package/dist/{http-D0dCcpLN.d.cts → http-Cb6KYYFv.d.cts} +1 -1
- package/dist/{http-DJd8jRhK.d.ts → http-D047Ngxk.d.ts} +1 -1
- package/dist/index.cjs +1632 -1045
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -9
- package/dist/index.d.ts +77 -9
- package/dist/index.js +760 -201
- package/dist/index.js.map +1 -1
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +1 -1
- package/dist/recorder.d.ts +1 -1
- package/dist/recorder.js +1 -1
- package/dist/{types-B5ecgHUV.d.cts → types-BDlebXrm.d.ts} +59 -5
- package/dist/{types-BtPovimB.d.ts → types-Mtlmlj5C.d.cts} +59 -5
- package/package.json +4 -4
- package/dist/chunk-GDJB466J.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -3,11 +3,14 @@ import {
|
|
|
3
3
|
EventQueue,
|
|
4
4
|
Outbox,
|
|
5
5
|
areMutuallyExclusive,
|
|
6
|
+
countsTowardThrottle,
|
|
6
7
|
dismissedAfterMerge,
|
|
7
8
|
isExclusiveGuide,
|
|
8
9
|
isFrequencyEligible,
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
isThrottleEligible,
|
|
11
|
+
isWithinSchedule,
|
|
12
|
+
pruneLaunches
|
|
13
|
+
} from "./chunk-ZAF2KRJA.js";
|
|
11
14
|
import {
|
|
12
15
|
Api,
|
|
13
16
|
KEYS,
|
|
@@ -16,12 +19,12 @@ import {
|
|
|
16
19
|
postWithRetry,
|
|
17
20
|
readJson,
|
|
18
21
|
writeJson
|
|
19
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-JUSM7J5N.js";
|
|
20
23
|
|
|
21
24
|
// src/client.ts
|
|
22
|
-
import { evaluateRules, randomId, rolloutBucket, sanitizeProperties, supportsPlatform } from "@guidelytech/shared/runtime";
|
|
25
|
+
import { evaluateRules, localizeGuide, localizeResourceCenter, normalizeLocale, randomId as randomId2, rolloutBucket, sanitizeProperties, supportsPlatform, visibleAnnouncements, visitorLocales } from "@guidelytech/shared/runtime";
|
|
23
26
|
import {
|
|
24
|
-
closestComposed as
|
|
27
|
+
closestComposed as closestComposed4,
|
|
25
28
|
composedTarget,
|
|
26
29
|
destroyHost,
|
|
27
30
|
isChecklistComplete,
|
|
@@ -35,8 +38,82 @@ import {
|
|
|
35
38
|
import { createScope } from "@guidelytech/guide-runtime";
|
|
36
39
|
import { createFlagsClient } from "@guidelytech/flags";
|
|
37
40
|
|
|
41
|
+
// src/agent-turns.ts
|
|
42
|
+
import { AGENT_TOOL_CALLS_PER_TURN, AGENT_TURN_MAX_TEXT, AGENT_TURNS_PER_BATCH, randomId } from "@guidelytech/shared/runtime";
|
|
43
|
+
var PENDING_MAX = 500;
|
|
44
|
+
function toolCalls(calls) {
|
|
45
|
+
if (!Array.isArray(calls)) return void 0;
|
|
46
|
+
return calls.filter((c) => !!c && typeof c.name === "string" && !!c.name).slice(0, AGENT_TOOL_CALLS_PER_TURN).map((c) => ({
|
|
47
|
+
name: c.name.slice(0, 100),
|
|
48
|
+
...c.status === "ok" || c.status === "error" ? { status: c.status } : {},
|
|
49
|
+
...typeof c.latencyMs === "number" && Number.isFinite(c.latencyMs) && c.latencyMs >= 0 ? { latencyMs: Math.round(c.latencyMs) } : {}
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
function prepareAgentTurn(turn, identity, redact, onError) {
|
|
53
|
+
if (!turn || typeof turn !== "object" || !turn.conversationId || !turn.agentId || !turn.role) return null;
|
|
54
|
+
let out = {
|
|
55
|
+
...turn,
|
|
56
|
+
type: "agent.conversation",
|
|
57
|
+
turnId: turn.turnId || `t_${randomId(20)}`,
|
|
58
|
+
occurredAt: turn.occurredAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
59
|
+
anonymousId: turn.anonymousId ?? identity.anonymousId,
|
|
60
|
+
userId: turn.userId !== void 0 ? turn.userId : identity.userId,
|
|
61
|
+
accountId: turn.accountId !== void 0 ? turn.accountId : identity.accountId,
|
|
62
|
+
sessionId: turn.sessionId ?? identity.sessionId
|
|
63
|
+
};
|
|
64
|
+
if (redact) {
|
|
65
|
+
try {
|
|
66
|
+
const r = redact({ ...out, toolCalls: out.toolCalls ? out.toolCalls.map((c) => ({ ...c })) : void 0, metadata: out.metadata ? { ...out.metadata } : void 0 });
|
|
67
|
+
if (!r) return null;
|
|
68
|
+
out = { ...r, type: "agent.conversation", conversationId: out.conversationId, agentId: out.agentId, turnId: out.turnId, role: out.role };
|
|
69
|
+
} catch (err) {
|
|
70
|
+
onError?.(err);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (typeof out.text === "string" && out.text.length > AGENT_TURN_MAX_TEXT) out.text = out.text.slice(0, AGENT_TURN_MAX_TEXT);
|
|
75
|
+
if (out.text !== void 0 && typeof out.text !== "string") out.text = String(out.text);
|
|
76
|
+
const calls = toolCalls(out.toolCalls);
|
|
77
|
+
if (calls) out.toolCalls = calls;
|
|
78
|
+
else delete out.toolCalls;
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
var AgentTurns = class {
|
|
82
|
+
constructor(opts) {
|
|
83
|
+
__publicField(this, "opts", opts);
|
|
84
|
+
__publicField(this, "outbox");
|
|
85
|
+
__publicField(this, "pending", []);
|
|
86
|
+
this.outbox = new Outbox({ api: opts.api, store: opts.store, storageKey: opts.storageKey, health: opts.health, maxItems: 20, maxAgeMs: 24 * 60 * 60 * 1e3 });
|
|
87
|
+
}
|
|
88
|
+
get queued() {
|
|
89
|
+
return this.outbox.size + this.pending.length;
|
|
90
|
+
}
|
|
91
|
+
/** Queues turns to send now, or (`hold`) until consent is granted. */
|
|
92
|
+
add(turns, hold = false) {
|
|
93
|
+
if (!turns.length) return;
|
|
94
|
+
if (hold) {
|
|
95
|
+
this.pending.push(...turns);
|
|
96
|
+
if (this.pending.length > PENDING_MAX) this.pending.splice(0, this.pending.length - PENDING_MAX);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
for (let i = 0; i < turns.length; i += AGENT_TURNS_PER_BATCH) {
|
|
100
|
+
const batch = turns.slice(i, i + AGENT_TURNS_PER_BATCH);
|
|
101
|
+
this.outbox.add(`agent:${batch[0].turnId}`, "/agent-events", { turns: batch, sdk: this.opts.sdk });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Consent changed: granted sends what was held, anything else drops it. */
|
|
105
|
+
consentChanged(granted) {
|
|
106
|
+
const held = this.pending.splice(0);
|
|
107
|
+
if (granted) this.add(held);
|
|
108
|
+
}
|
|
109
|
+
destroy() {
|
|
110
|
+
this.pending = [];
|
|
111
|
+
this.outbox.destroy();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
38
115
|
// src/authoring.ts
|
|
39
|
-
import { closestComposed, highlightSelectors, previewStep, runGuide, startAuthoring } from "@guidelytech/guide-runtime";
|
|
116
|
+
import { closestComposed as closestComposed3, highlightSelectors, previewStep, runGuide, startAuthoring } from "@guidelytech/guide-runtime";
|
|
40
117
|
|
|
41
118
|
// src/snapshot.ts
|
|
42
119
|
var MAX_SNAPSHOT_BYTES = 6 * 1024 * 1024;
|
|
@@ -114,6 +191,276 @@ ${clone.outerHTML}`;
|
|
|
114
191
|
return { html, url: win.location.href, title: doc.title, width: win.innerWidth, height: win.innerHeight, capturedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
115
192
|
}
|
|
116
193
|
|
|
194
|
+
// src/tagging.ts
|
|
195
|
+
import { NOISY_DATA_ATTR as NOISY_DATA_ATTR2, SNAPSHOT_LIMITS, TAG_PROPERTY_LIMITS, featureRuleMatchesClick, pageRuleMatches, pageRulesMatch } from "@guidelytech/shared/runtime";
|
|
196
|
+
import { closestComposed as closestComposed2, isStableClass, isStableId as isStableId2, querySelectorStrategy } from "@guidelytech/guide-runtime";
|
|
197
|
+
|
|
198
|
+
// src/autocapture.ts
|
|
199
|
+
import { closestComposed, isGuidelyElement, isStableId, SHADOW_SEPARATOR, shadowHostChain } from "@guidelytech/guide-runtime";
|
|
200
|
+
var INTERACTIVE_SELECTOR = "a,button,input[type=button],input[type=submit],input[type=reset],input[type=checkbox],input[type=radio],select,summary,label,[role=button],[role=link],[role=tab],[role=menuitem],[role=checkbox],[role=switch],[role=option],[onclick],[data-guidely-track]";
|
|
201
|
+
var DEFAULT_MASK_SELECTORS = ["[data-guidely-mask]", ".guidely-mask"];
|
|
202
|
+
var PREFERRED_ATTRS = ["data-testid", "data-test-id", "data-test", "data-cy", "data-qa", "data-guidely-track", "data-tour"];
|
|
203
|
+
var NOISY_DATA_ATTR = /^data-(state|v-|react|radix|headlessui|svelte|orientation|highlighted|focus|key|index|side|align|disabled|selected|active|open|guidely-(mask|ignore|block))/i;
|
|
204
|
+
function validSelectors(list) {
|
|
205
|
+
const out = [];
|
|
206
|
+
const probe = typeof document !== "undefined" ? document.createDocumentFragment() : null;
|
|
207
|
+
for (const raw of list) {
|
|
208
|
+
const s = (raw || "").trim();
|
|
209
|
+
if (!s || out.includes(s)) continue;
|
|
210
|
+
try {
|
|
211
|
+
probe?.querySelector(s);
|
|
212
|
+
out.push(s);
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
function isIgnoredElement(el) {
|
|
219
|
+
return isGuidelyElement(el) || !!closestComposed(el, "[data-guidely-ignore]");
|
|
220
|
+
}
|
|
221
|
+
function interactiveElement(target) {
|
|
222
|
+
try {
|
|
223
|
+
return closestComposed(target, INTERACTIVE_SELECTOR);
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function esc(value) {
|
|
229
|
+
const g = globalThis;
|
|
230
|
+
return g.CSS?.escape ? g.CSS.escape(value) : value.replace(/[^\w-]/g, (c) => `\\${c}`);
|
|
231
|
+
}
|
|
232
|
+
var attr = (tag, name, value) => value ? `${tag}[${name}="${value.replace(/["\\]/g, "\\$&")}"]` : `${tag}[${name}]`;
|
|
233
|
+
function elementSelector(el) {
|
|
234
|
+
const own = ownSelector(el);
|
|
235
|
+
const chain = shadowHostChain(el);
|
|
236
|
+
return (chain ? chain + SHADOW_SEPARATOR + own : own).slice(0, 300);
|
|
237
|
+
}
|
|
238
|
+
function ownSelector(el) {
|
|
239
|
+
const tag = el.tagName.toLowerCase();
|
|
240
|
+
if (el.id && isStableId(el.id)) return `#${esc(el.id)}`;
|
|
241
|
+
for (const name of PREFERRED_ATTRS) if (el.hasAttribute(name)) return attr(tag, name, el.getAttribute(name) || "");
|
|
242
|
+
for (const a of Array.from(el.attributes)) {
|
|
243
|
+
if (a.name.startsWith("data-") && !NOISY_DATA_ATTR.test(a.name) && a.value && a.value.length <= 60) return attr(tag, a.name, a.value);
|
|
244
|
+
}
|
|
245
|
+
const label = el.getAttribute("aria-label");
|
|
246
|
+
if (label && label.length <= 80) return attr(tag, "aria-label", label);
|
|
247
|
+
const parts = [];
|
|
248
|
+
let cur = el;
|
|
249
|
+
while (cur && parts.length < 4) {
|
|
250
|
+
const t = cur.tagName.toLowerCase();
|
|
251
|
+
if (t === "html" || t === "body") break;
|
|
252
|
+
if (cur !== el && cur.id && isStableId(cur.id)) {
|
|
253
|
+
parts.unshift(`#${esc(cur.id)}`);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
const parent = cur.parentElement;
|
|
257
|
+
const node = cur;
|
|
258
|
+
const same = parent ? Array.from(parent.children).filter((c) => c.tagName === node.tagName) : [];
|
|
259
|
+
parts.unshift(same.length > 1 ? `${t}:nth-of-type(${same.indexOf(node) + 1})` : t);
|
|
260
|
+
cur = parent;
|
|
261
|
+
}
|
|
262
|
+
return parts.join(" > ") || tag;
|
|
263
|
+
}
|
|
264
|
+
function matches(el, selector, deep) {
|
|
265
|
+
if (!selector) return false;
|
|
266
|
+
try {
|
|
267
|
+
return !!closestComposed(el, selector) || deep && !!el.querySelector(selector);
|
|
268
|
+
} catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function elementText(el, mask, maskAll) {
|
|
273
|
+
if (maskAll || matches(el, mask, true)) return void 0;
|
|
274
|
+
const tag = el.tagName.toLowerCase();
|
|
275
|
+
let text = "";
|
|
276
|
+
if (tag === "input") {
|
|
277
|
+
if (!/^(button|submit|reset)$/i.test(el.type || "")) return void 0;
|
|
278
|
+
text = el.value;
|
|
279
|
+
} else if (tag === "select" || tag === "textarea") return void 0;
|
|
280
|
+
else text = el.innerText ?? el.textContent ?? "";
|
|
281
|
+
text = (text || el.getAttribute("aria-label") || el.getAttribute("title") || "").replace(/\s+/g, " ").trim();
|
|
282
|
+
return text ? text.slice(0, 100) : void 0;
|
|
283
|
+
}
|
|
284
|
+
function linkHref(el) {
|
|
285
|
+
const a = closestComposed(el, "a[href]");
|
|
286
|
+
if (!a) return void 0;
|
|
287
|
+
try {
|
|
288
|
+
const u = new URL(a.getAttribute("href") || "", location.href);
|
|
289
|
+
if (!/^https?:$/.test(u.protocol)) return void 0;
|
|
290
|
+
return u.origin === location.origin ? u.pathname : u.origin;
|
|
291
|
+
} catch {
|
|
292
|
+
return void 0;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function describeClick(el, e, opts) {
|
|
296
|
+
const props = { tag: el.tagName.toLowerCase(), selector: elementSelector(el), x: Math.round(e.pageX ?? 0) || 0, y: Math.round(e.pageY ?? 0) || 0 };
|
|
297
|
+
const text = elementText(el, opts.mask, opts.maskAll);
|
|
298
|
+
if (text) props.text = text;
|
|
299
|
+
const href = opts.collectUrls ? linkHref(el) : void 0;
|
|
300
|
+
if (href) props.href = href;
|
|
301
|
+
return props;
|
|
302
|
+
}
|
|
303
|
+
var RageClickDetector = class {
|
|
304
|
+
constructor(threshold = 3, windowMs = 1e3, radius = 30) {
|
|
305
|
+
__publicField(this, "threshold", threshold);
|
|
306
|
+
__publicField(this, "windowMs", windowMs);
|
|
307
|
+
__publicField(this, "radius", radius);
|
|
308
|
+
__publicField(this, "clicks", []);
|
|
309
|
+
__publicField(this, "fired", false);
|
|
310
|
+
}
|
|
311
|
+
/** Clicks in the current burst once it has fired (rage), otherwise 0. */
|
|
312
|
+
get burst() {
|
|
313
|
+
return this.fired ? this.clicks.length : 0;
|
|
314
|
+
}
|
|
315
|
+
/** Records a click; returns the click count when it completes a burst, otherwise 0. */
|
|
316
|
+
add(x, y, t) {
|
|
317
|
+
const first = this.clicks[0];
|
|
318
|
+
const last = this.clicks[this.clicks.length - 1];
|
|
319
|
+
if (!first || !last || t - last.t > this.windowMs || Math.hypot(x - first.x, y - first.y) > this.radius) {
|
|
320
|
+
this.clicks = [];
|
|
321
|
+
this.fired = false;
|
|
322
|
+
}
|
|
323
|
+
this.clicks = this.clicks.filter((c) => t - c.t <= this.windowMs);
|
|
324
|
+
this.clicks.push({ x, y, t });
|
|
325
|
+
if (this.fired || this.clicks.length < this.threshold) return 0;
|
|
326
|
+
this.fired = true;
|
|
327
|
+
return this.clicks.length;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// src/tagging.ts
|
|
332
|
+
var TEXT_ATTRS = /* @__PURE__ */ new Set(["aria-label", "title", "alt"]);
|
|
333
|
+
var SAFE_ATTRS = ["role", "name", "type", "aria-label", "title", "alt", "for"];
|
|
334
|
+
var STATE_CLASS = /^(is-|has-)?(active|hover|hovered|focus|focused|focus-visible|selected|open|opened|closed|disabled|visible|hidden|show|shown|collapsed|expanded|checked|pressed|loading|current)$/i;
|
|
335
|
+
function nthOfType(el) {
|
|
336
|
+
const parent = el.parentElement;
|
|
337
|
+
if (!parent) return void 0;
|
|
338
|
+
let n = 0;
|
|
339
|
+
for (const c of Array.from(parent.children)) {
|
|
340
|
+
if (c.tagName === el.tagName) n++;
|
|
341
|
+
if (c === el) return n;
|
|
342
|
+
}
|
|
343
|
+
return void 0;
|
|
344
|
+
}
|
|
345
|
+
function nodeOf(el, masked, featureAttribute) {
|
|
346
|
+
const node = { t: el.tagName.toLowerCase().slice(0, SNAPSHOT_LIMITS.tag) };
|
|
347
|
+
if (el.id && isStableId2(el.id) && el.id.length <= SNAPSHOT_LIMITS.id) node.id = el.id;
|
|
348
|
+
const classes = Array.from(el.classList).filter((c) => isStableClass(c) && !STATE_CLASS.test(c) && c.length <= SNAPSHOT_LIMITS.className);
|
|
349
|
+
if (classes.length) node.c = classes.slice(0, SNAPSHOT_LIMITS.classes);
|
|
350
|
+
const a = {};
|
|
351
|
+
const put = (name, value) => {
|
|
352
|
+
if (value === null || Object.keys(a).length >= SNAPSHOT_LIMITS.attrs || name in a) return;
|
|
353
|
+
if (masked && TEXT_ATTRS.has(name)) return;
|
|
354
|
+
a[name] = value.slice(0, SNAPSHOT_LIMITS.attrValue);
|
|
355
|
+
};
|
|
356
|
+
if (featureAttribute && el.hasAttribute(featureAttribute)) put(featureAttribute, el.getAttribute(featureAttribute));
|
|
357
|
+
for (const attr2 of Array.from(el.attributes)) {
|
|
358
|
+
if (attr2.name.startsWith("data-") && !NOISY_DATA_ATTR2.test(attr2.name) && attr2.value.length <= SNAPSHOT_LIMITS.attrValue) put(attr2.name, attr2.value);
|
|
359
|
+
}
|
|
360
|
+
for (const name of SAFE_ATTRS) if (el.hasAttribute(name)) put(name, el.getAttribute(name));
|
|
361
|
+
if (Object.keys(a).length) node.a = a;
|
|
362
|
+
const n = nthOfType(el);
|
|
363
|
+
if (n) node.n = n;
|
|
364
|
+
return node;
|
|
365
|
+
}
|
|
366
|
+
function elementSnapshot(el, opts) {
|
|
367
|
+
const masked = opts.maskAll || isMasked(el, opts.mask);
|
|
368
|
+
const snap = nodeOf(el, masked, opts.featureAttribute);
|
|
369
|
+
const p = [];
|
|
370
|
+
for (let cur = el.parentElement; cur && p.length < SNAPSHOT_LIMITS.ancestors; cur = cur.parentElement) {
|
|
371
|
+
if (cur.tagName === "BODY" || cur.tagName === "HTML") break;
|
|
372
|
+
p.push(nodeOf(cur, masked, opts.featureAttribute));
|
|
373
|
+
}
|
|
374
|
+
if (p.length) snap.p = p;
|
|
375
|
+
const h = [];
|
|
376
|
+
let root = el.getRootNode?.();
|
|
377
|
+
while (root && root.nodeType === 11 && root.host && h.length < SNAPSHOT_LIMITS.hosts) {
|
|
378
|
+
h.push(nodeOf(root.host, masked, opts.featureAttribute));
|
|
379
|
+
root = root.host.getRootNode?.();
|
|
380
|
+
}
|
|
381
|
+
if (h.length) snap.h = h;
|
|
382
|
+
return snap;
|
|
383
|
+
}
|
|
384
|
+
function isMasked(el, mask) {
|
|
385
|
+
if (!mask) return false;
|
|
386
|
+
try {
|
|
387
|
+
return !!closestComposed2(el, mask);
|
|
388
|
+
} catch {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function featureAttributeOf(target, attribute) {
|
|
393
|
+
if (!attribute) return null;
|
|
394
|
+
try {
|
|
395
|
+
const el = closestComposed2(target, `[${attribute}]`);
|
|
396
|
+
const value = el?.getAttribute(attribute)?.replace(/\s+/g, " ").trim();
|
|
397
|
+
return el && value ? { el, value: value.slice(0, 100) } : null;
|
|
398
|
+
} catch {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function readProperty(def, base, opts) {
|
|
403
|
+
let el = base;
|
|
404
|
+
if (def.selector) {
|
|
405
|
+
try {
|
|
406
|
+
el = querySelectorStrategy({ kind: "css", value: def.selector }, document)[0] ?? null;
|
|
407
|
+
} catch {
|
|
408
|
+
el = null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (!el) return void 0;
|
|
412
|
+
if (def.source === "attribute") {
|
|
413
|
+
const name = (def.attribute ?? "").toLowerCase();
|
|
414
|
+
if (!name || name === "value" || name.startsWith("on")) return void 0;
|
|
415
|
+
if (opts.maskAll || isMasked(el, opts.mask)) return void 0;
|
|
416
|
+
const v = el.getAttribute(name);
|
|
417
|
+
return v ? v.replace(/\s+/g, " ").trim().slice(0, TAG_PROPERTY_LIMITS.value) || void 0 : void 0;
|
|
418
|
+
}
|
|
419
|
+
return elementText(el, opts.mask, opts.maskAll);
|
|
420
|
+
}
|
|
421
|
+
function clickPropertyValues(config, el, click, opts) {
|
|
422
|
+
if (!config?.properties.length) return void 0;
|
|
423
|
+
const out = {};
|
|
424
|
+
const css = (selector) => {
|
|
425
|
+
try {
|
|
426
|
+
return !!closestComposed2(el, selector);
|
|
427
|
+
} catch {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const here = { url: location.href, path: location.pathname, title: document.title };
|
|
432
|
+
for (const tag of config.properties) {
|
|
433
|
+
if (tag.kind !== "feature" || !tag.featureRules) continue;
|
|
434
|
+
const rules = tag.featureRules;
|
|
435
|
+
const matches2 = (r) => featureRuleMatchesClick(r, click, css) && (!r.page?.pattern || pageRuleMatches({ type: "url", value: r.page.pattern }, here));
|
|
436
|
+
if (!rules.some((r) => !r.exclude && matches2(r)) || rules.some((r) => r.exclude && matches2(r))) continue;
|
|
437
|
+
for (const def of tag.properties) {
|
|
438
|
+
const v = readProperty(def, el, opts);
|
|
439
|
+
if (v) out[def.id] = v;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return Object.keys(out).length ? out : void 0;
|
|
443
|
+
}
|
|
444
|
+
function pagePropertyValues(config, url, opts) {
|
|
445
|
+
if (!config?.properties.length) return void 0;
|
|
446
|
+
const out = {};
|
|
447
|
+
let path = "/";
|
|
448
|
+
try {
|
|
449
|
+
path = new URL(url, location.href).pathname;
|
|
450
|
+
} catch {
|
|
451
|
+
}
|
|
452
|
+
const ctx = { url, path, title: document.title };
|
|
453
|
+
for (const tag of config.properties) {
|
|
454
|
+
if (tag.kind !== "page" || !tag.pageRules || !pageRulesMatch(tag.pageRules, ctx)) continue;
|
|
455
|
+
for (const def of tag.properties) {
|
|
456
|
+
if (!def.selector) continue;
|
|
457
|
+
const v = readProperty(def, null, opts);
|
|
458
|
+
if (v) out[def.id] = v;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return Object.keys(out).length ? out : void 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
117
464
|
// src/authoring.ts
|
|
118
465
|
async function loadSchemas() {
|
|
119
466
|
const m = await import("@guidelytech/shared");
|
|
@@ -184,6 +531,8 @@ var AuthoringBridge = class {
|
|
|
184
531
|
this.opts.onActive(true);
|
|
185
532
|
this.session = startAuthoring({
|
|
186
533
|
dashboardOrigin: this.info.dashboardOrigin,
|
|
534
|
+
// Tag mode (Pages & Features): picks become feature rules; the page itself is tagged from the dashboard.
|
|
535
|
+
message: this.info.purpose === "tag" ? "Guidely tagging \u2014 click an element to tag it as a feature \xB7 tag the page from the dashboard \xB7 Esc to finish" : void 0,
|
|
187
536
|
onPick: (pick) => this.sendPick(pick),
|
|
188
537
|
onExit: () => this.stop()
|
|
189
538
|
});
|
|
@@ -200,7 +549,7 @@ var AuthoringBridge = class {
|
|
|
200
549
|
masked(el) {
|
|
201
550
|
return this.opts.maskSelectors.some((sel) => {
|
|
202
551
|
try {
|
|
203
|
-
return !!
|
|
552
|
+
return !!closestComposed3(el, sel);
|
|
204
553
|
} catch {
|
|
205
554
|
return false;
|
|
206
555
|
}
|
|
@@ -211,6 +560,7 @@ var AuthoringBridge = class {
|
|
|
211
560
|
const { element, ...rest } = raw;
|
|
212
561
|
const pick = this.masked(element) ? { ...rest, textSnapshot: "", selectors: rest.selectors.filter((s) => s.kind !== "text") } : rest;
|
|
213
562
|
pick.url = window.location.href;
|
|
563
|
+
if (this.info.purpose === "tag") pick.elementSnapshot = elementSnapshot(element, { mask: this.opts.maskSelectors.join(","), maskAll: false, featureAttribute: null });
|
|
214
564
|
this.post({ type: "guidely:pick", sessionId: this.info.id, pick });
|
|
215
565
|
void this.opts.api.post("/authoring/session/picks", pick, { params: { token: this.token } }).catch((err) => this.opts.onError(err));
|
|
216
566
|
}
|
|
@@ -380,139 +730,6 @@ function takeCaptureToken(sessionStore) {
|
|
|
380
730
|
// src/load-capture.ts
|
|
381
731
|
var loadCapture = async () => (await import("./capture.js")).capture;
|
|
382
732
|
|
|
383
|
-
// src/autocapture.ts
|
|
384
|
-
import { closestComposed as closestComposed2, isGuidelyElement, isStableId, SHADOW_SEPARATOR, shadowHostChain } from "@guidelytech/guide-runtime";
|
|
385
|
-
var INTERACTIVE_SELECTOR = "a,button,input[type=button],input[type=submit],input[type=reset],input[type=checkbox],input[type=radio],select,summary,label,[role=button],[role=link],[role=tab],[role=menuitem],[role=checkbox],[role=switch],[role=option],[onclick],[data-guidely-track]";
|
|
386
|
-
var DEFAULT_MASK_SELECTORS = ["[data-guidely-mask]", ".guidely-mask"];
|
|
387
|
-
var PREFERRED_ATTRS = ["data-testid", "data-test-id", "data-test", "data-cy", "data-qa", "data-guidely-track", "data-tour"];
|
|
388
|
-
var NOISY_DATA_ATTR = /^data-(state|v-|react|radix|headlessui|svelte|orientation|highlighted|focus|key|index|side|align|disabled|selected|active|open|guidely-(mask|ignore|block))/i;
|
|
389
|
-
function validSelectors(list) {
|
|
390
|
-
const out = [];
|
|
391
|
-
const probe = typeof document !== "undefined" ? document.createDocumentFragment() : null;
|
|
392
|
-
for (const raw of list) {
|
|
393
|
-
const s = (raw || "").trim();
|
|
394
|
-
if (!s || out.includes(s)) continue;
|
|
395
|
-
try {
|
|
396
|
-
probe?.querySelector(s);
|
|
397
|
-
out.push(s);
|
|
398
|
-
} catch {
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return out;
|
|
402
|
-
}
|
|
403
|
-
function isIgnoredElement(el) {
|
|
404
|
-
return isGuidelyElement(el) || !!closestComposed2(el, "[data-guidely-ignore]");
|
|
405
|
-
}
|
|
406
|
-
function interactiveElement(target) {
|
|
407
|
-
try {
|
|
408
|
-
return closestComposed2(target, INTERACTIVE_SELECTOR);
|
|
409
|
-
} catch {
|
|
410
|
-
return null;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
function esc(value) {
|
|
414
|
-
const g = globalThis;
|
|
415
|
-
return g.CSS?.escape ? g.CSS.escape(value) : value.replace(/[^\w-]/g, (c) => `\\${c}`);
|
|
416
|
-
}
|
|
417
|
-
var attr = (tag, name, value) => value ? `${tag}[${name}="${value.replace(/["\\]/g, "\\$&")}"]` : `${tag}[${name}]`;
|
|
418
|
-
function elementSelector(el) {
|
|
419
|
-
const own = ownSelector(el);
|
|
420
|
-
const chain = shadowHostChain(el);
|
|
421
|
-
return (chain ? chain + SHADOW_SEPARATOR + own : own).slice(0, 300);
|
|
422
|
-
}
|
|
423
|
-
function ownSelector(el) {
|
|
424
|
-
const tag = el.tagName.toLowerCase();
|
|
425
|
-
if (el.id && isStableId(el.id)) return `#${esc(el.id)}`;
|
|
426
|
-
for (const name of PREFERRED_ATTRS) if (el.hasAttribute(name)) return attr(tag, name, el.getAttribute(name) || "");
|
|
427
|
-
for (const a of Array.from(el.attributes)) {
|
|
428
|
-
if (a.name.startsWith("data-") && !NOISY_DATA_ATTR.test(a.name) && a.value && a.value.length <= 60) return attr(tag, a.name, a.value);
|
|
429
|
-
}
|
|
430
|
-
const label = el.getAttribute("aria-label");
|
|
431
|
-
if (label && label.length <= 80) return attr(tag, "aria-label", label);
|
|
432
|
-
const parts = [];
|
|
433
|
-
let cur = el;
|
|
434
|
-
while (cur && parts.length < 4) {
|
|
435
|
-
const t = cur.tagName.toLowerCase();
|
|
436
|
-
if (t === "html" || t === "body") break;
|
|
437
|
-
if (cur !== el && cur.id && isStableId(cur.id)) {
|
|
438
|
-
parts.unshift(`#${esc(cur.id)}`);
|
|
439
|
-
break;
|
|
440
|
-
}
|
|
441
|
-
const parent = cur.parentElement;
|
|
442
|
-
const node = cur;
|
|
443
|
-
const same = parent ? Array.from(parent.children).filter((c) => c.tagName === node.tagName) : [];
|
|
444
|
-
parts.unshift(same.length > 1 ? `${t}:nth-of-type(${same.indexOf(node) + 1})` : t);
|
|
445
|
-
cur = parent;
|
|
446
|
-
}
|
|
447
|
-
return parts.join(" > ") || tag;
|
|
448
|
-
}
|
|
449
|
-
function matches(el, selector, deep) {
|
|
450
|
-
if (!selector) return false;
|
|
451
|
-
try {
|
|
452
|
-
return !!closestComposed2(el, selector) || deep && !!el.querySelector(selector);
|
|
453
|
-
} catch {
|
|
454
|
-
return false;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
function elementText(el, mask, maskAll) {
|
|
458
|
-
if (maskAll || matches(el, mask, true)) return void 0;
|
|
459
|
-
const tag = el.tagName.toLowerCase();
|
|
460
|
-
let text = "";
|
|
461
|
-
if (tag === "input") {
|
|
462
|
-
if (!/^(button|submit|reset)$/i.test(el.type || "")) return void 0;
|
|
463
|
-
text = el.value;
|
|
464
|
-
} else if (tag === "select" || tag === "textarea") return void 0;
|
|
465
|
-
else text = el.innerText ?? el.textContent ?? "";
|
|
466
|
-
text = (text || el.getAttribute("aria-label") || el.getAttribute("title") || "").replace(/\s+/g, " ").trim();
|
|
467
|
-
return text ? text.slice(0, 100) : void 0;
|
|
468
|
-
}
|
|
469
|
-
function linkHref(el) {
|
|
470
|
-
const a = closestComposed2(el, "a[href]");
|
|
471
|
-
if (!a) return void 0;
|
|
472
|
-
try {
|
|
473
|
-
const u = new URL(a.getAttribute("href") || "", location.href);
|
|
474
|
-
if (!/^https?:$/.test(u.protocol)) return void 0;
|
|
475
|
-
return u.origin === location.origin ? u.pathname : u.origin;
|
|
476
|
-
} catch {
|
|
477
|
-
return void 0;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
function describeClick(el, e, opts) {
|
|
481
|
-
const props = { tag: el.tagName.toLowerCase(), selector: elementSelector(el), x: Math.round(e.pageX ?? 0) || 0, y: Math.round(e.pageY ?? 0) || 0 };
|
|
482
|
-
const text = elementText(el, opts.mask, opts.maskAll);
|
|
483
|
-
if (text) props.text = text;
|
|
484
|
-
const href = opts.collectUrls ? linkHref(el) : void 0;
|
|
485
|
-
if (href) props.href = href;
|
|
486
|
-
return props;
|
|
487
|
-
}
|
|
488
|
-
var RageClickDetector = class {
|
|
489
|
-
constructor(threshold = 3, windowMs = 1e3, radius = 30) {
|
|
490
|
-
__publicField(this, "threshold", threshold);
|
|
491
|
-
__publicField(this, "windowMs", windowMs);
|
|
492
|
-
__publicField(this, "radius", radius);
|
|
493
|
-
__publicField(this, "clicks", []);
|
|
494
|
-
__publicField(this, "fired", false);
|
|
495
|
-
}
|
|
496
|
-
/** Clicks in the current burst once it has fired (rage), otherwise 0. */
|
|
497
|
-
get burst() {
|
|
498
|
-
return this.fired ? this.clicks.length : 0;
|
|
499
|
-
}
|
|
500
|
-
/** Records a click; returns the click count when it completes a burst, otherwise 0. */
|
|
501
|
-
add(x, y, t) {
|
|
502
|
-
const first = this.clicks[0];
|
|
503
|
-
const last = this.clicks[this.clicks.length - 1];
|
|
504
|
-
if (!first || !last || t - last.t > this.windowMs || Math.hypot(x - first.x, y - first.y) > this.radius) {
|
|
505
|
-
this.clicks = [];
|
|
506
|
-
this.fired = false;
|
|
507
|
-
}
|
|
508
|
-
this.clicks = this.clicks.filter((c) => t - c.t <= this.windowMs);
|
|
509
|
-
this.clicks.push({ x, y, t });
|
|
510
|
-
if (this.fired || this.clicks.length < this.threshold) return 0;
|
|
511
|
-
this.fired = true;
|
|
512
|
-
return this.clicks.length;
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
|
|
516
733
|
// src/context.ts
|
|
517
734
|
function detectDevice(ua, width) {
|
|
518
735
|
const u = ua || "";
|
|
@@ -522,7 +739,8 @@ function detectDevice(ua, width) {
|
|
|
522
739
|
else if (width > 0 && width < 768) type = "mobile";
|
|
523
740
|
else if (width > 0 && width < 1024) type = "tablet";
|
|
524
741
|
let browser;
|
|
525
|
-
if (/
|
|
742
|
+
if (/Electron\//i.test(u)) browser = "electron";
|
|
743
|
+
else if (/Edg\//i.test(u)) browser = "edge";
|
|
526
744
|
else if (/OPR\/|Opera/i.test(u)) browser = "opera";
|
|
527
745
|
else if (/SamsungBrowser/i.test(u)) browser = "samsung";
|
|
528
746
|
else if (/Chrome\/|CriOS/i.test(u)) browser = "chrome";
|
|
@@ -788,6 +1006,14 @@ function createMessengerTransport(opts) {
|
|
|
788
1006
|
h.onDone(d.messages.at(-1) ?? null, d.conversation);
|
|
789
1007
|
} else if (!finished) h.onError("The reply was interrupted");
|
|
790
1008
|
}
|
|
1009
|
+
async function feedback(path, body, fallback) {
|
|
1010
|
+
const res = await api.post(`/feedback${path}`, { ...body, anonymousId: opts.anonymousId(), userId: opts.userId() });
|
|
1011
|
+
if (!res.ok || !res.data) {
|
|
1012
|
+
if (res.status === 429) throw new HttpError(429, "You're doing that a lot. Please try again in a minute.");
|
|
1013
|
+
throw new HttpError(res.status, serverMessage(res.data) ?? fallback);
|
|
1014
|
+
}
|
|
1015
|
+
return res.data;
|
|
1016
|
+
}
|
|
791
1017
|
return {
|
|
792
1018
|
async listConversations() {
|
|
793
1019
|
const tokens = storedConversations(store, projectId).map((c) => c.token);
|
|
@@ -824,7 +1050,8 @@ function createMessengerTransport(opts) {
|
|
|
824
1050
|
return res.data;
|
|
825
1051
|
},
|
|
826
1052
|
async getArticle(id) {
|
|
827
|
-
const
|
|
1053
|
+
const locale = opts.locale?.();
|
|
1054
|
+
const res = await api.get(`/articles/${encodeURIComponent(id)}`, locale ? { params: { locale } } : void 0);
|
|
828
1055
|
return res.ok && res.data ? res.data.article : null;
|
|
829
1056
|
},
|
|
830
1057
|
async fileComplaint(input) {
|
|
@@ -854,7 +1081,19 @@ function createMessengerTransport(opts) {
|
|
|
854
1081
|
async getHowTo(id) {
|
|
855
1082
|
const res = await api.get(`/how-tos/${encodeURIComponent(id)}`);
|
|
856
1083
|
return res.ok && res.data ? res.data.howTo : null;
|
|
857
|
-
}
|
|
1084
|
+
},
|
|
1085
|
+
// Ideas (feature requests). The visitor's ids go in every body; the server decides what they may do.
|
|
1086
|
+
listIdeas: (query) => feedback("/requests/query", query, "Ideas couldn't be loaded."),
|
|
1087
|
+
getIdea: (id) => feedback(`/requests/${encodeURIComponent(id)}/view`, {}, "This idea couldn't be loaded."),
|
|
1088
|
+
submitIdea: (input) => feedback("/requests", { ...input, pageUrl: opts.pageUrl() }, "Your idea couldn't be sent. Please try again."),
|
|
1089
|
+
voteIdea: (id, vote) => feedback(`/requests/${encodeURIComponent(id)}/vote`, vote, "Your vote couldn't be saved."),
|
|
1090
|
+
unvoteIdea: (id) => feedback(`/requests/${encodeURIComponent(id)}/unvote`, {}, "Your vote couldn't be removed."),
|
|
1091
|
+
followIdea: (id, follow) => feedback(`/requests/${encodeURIComponent(id)}/follow`, { follow }, "That didn't work. Please try again."),
|
|
1092
|
+
commentIdea: (id, body) => feedback(`/requests/${encodeURIComponent(id)}/comments`, { body }, "Your comment couldn't be posted."),
|
|
1093
|
+
similarIdeas: async (title) => (await feedback("/similar", { title }, "Similar ideas couldn't be loaded.")).requests ?? [],
|
|
1094
|
+
ideaUpdates: () => feedback("/updates", {}, "Updates couldn't be loaded."),
|
|
1095
|
+
markIdeaUpdatesRead: (ids) => feedback("/updates/read", ids ? { ids } : {}, "Updates couldn't be marked read."),
|
|
1096
|
+
ideaPortalLink: async () => (await feedback("/portal-link", {}, "The feedback portal couldn't be opened.")).url
|
|
858
1097
|
};
|
|
859
1098
|
}
|
|
860
1099
|
|
|
@@ -984,6 +1223,48 @@ function watchNavigation(listener, debounceMs = 50) {
|
|
|
984
1223
|
};
|
|
985
1224
|
}
|
|
986
1225
|
|
|
1226
|
+
// src/page-url.ts
|
|
1227
|
+
var WEB_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "about", "data", "blob", "javascript"]);
|
|
1228
|
+
function appScheme(href) {
|
|
1229
|
+
let u;
|
|
1230
|
+
try {
|
|
1231
|
+
u = new URL(href);
|
|
1232
|
+
} catch {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
const scheme = u.protocol.replace(/:$/, "").toLowerCase();
|
|
1236
|
+
return WEB_SCHEMES.has(scheme) ? null : { u, scheme };
|
|
1237
|
+
}
|
|
1238
|
+
function normalizeAppUrl(href) {
|
|
1239
|
+
const app = appScheme(href);
|
|
1240
|
+
if (!app) return href;
|
|
1241
|
+
const { u, scheme } = app;
|
|
1242
|
+
const host = scheme === "file" ? "app" : u.host || "app";
|
|
1243
|
+
const hash = /^#!?(\/.*)$/.exec(u.hash);
|
|
1244
|
+
if (hash) {
|
|
1245
|
+
const route = hash[1];
|
|
1246
|
+
return `${scheme}://${host}${route.startsWith("/") ? route : `/${route}`}`;
|
|
1247
|
+
}
|
|
1248
|
+
let path = safeDecode(u.pathname);
|
|
1249
|
+
if (scheme === "file" || /\.html?$/i.test(path)) {
|
|
1250
|
+
const file = path.split("/").filter(Boolean).pop() ?? "";
|
|
1251
|
+
const name = file.replace(/\.html?$/i, "");
|
|
1252
|
+
path = !name || name.toLowerCase() === "index" ? "/" : `/${name}`;
|
|
1253
|
+
}
|
|
1254
|
+
if (!path.startsWith("/")) path = `/${path}`;
|
|
1255
|
+
return `${scheme}://${host}${path}${u.search}`;
|
|
1256
|
+
}
|
|
1257
|
+
function safeDecode(s) {
|
|
1258
|
+
try {
|
|
1259
|
+
return decodeURI(s);
|
|
1260
|
+
} catch {
|
|
1261
|
+
return s;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
function isAppUrl(href) {
|
|
1265
|
+
return appScheme(href) !== null;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
987
1268
|
// src/page-tracking.ts
|
|
988
1269
|
function scrollDepth() {
|
|
989
1270
|
const de = document.documentElement;
|
|
@@ -1029,12 +1310,112 @@ var PageTracker = class {
|
|
|
1029
1310
|
}
|
|
1030
1311
|
};
|
|
1031
1312
|
|
|
1313
|
+
// src/prototype-overlay.ts
|
|
1314
|
+
var PROTOTYPE_CLOSE_MESSAGE = "guidely:prototype:close";
|
|
1315
|
+
var TOKEN = /^[A-Za-z0-9_-]{16,64}$/;
|
|
1316
|
+
var OVERLAY_ATTR = "data-guidely-prototype-overlay";
|
|
1317
|
+
function matchPrototypeTestUrl(urlOrToken, apiUrl) {
|
|
1318
|
+
const value = (urlOrToken || "").trim();
|
|
1319
|
+
let api;
|
|
1320
|
+
try {
|
|
1321
|
+
api = new URL(apiUrl);
|
|
1322
|
+
} catch {
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
if (TOKEN.test(value)) return new URL(`/validate/${value}`, api.origin);
|
|
1326
|
+
let url;
|
|
1327
|
+
try {
|
|
1328
|
+
url = new URL(value, typeof location !== "undefined" ? location.href : api.origin);
|
|
1329
|
+
} catch {
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
if (url.origin !== api.origin) return null;
|
|
1333
|
+
const m = /^\/validate\/([^/]+)\/?$/.exec(url.pathname);
|
|
1334
|
+
if (!m || !TOKEN.test(m[1])) return null;
|
|
1335
|
+
return new URL(`/validate/${m[1]}`, api.origin);
|
|
1336
|
+
}
|
|
1337
|
+
var current = null;
|
|
1338
|
+
function openPrototypeOverlay(testUrl, opts) {
|
|
1339
|
+
current?.close();
|
|
1340
|
+
const doc = document;
|
|
1341
|
+
const previous = doc.activeElement;
|
|
1342
|
+
const src = new URL(testUrl.href);
|
|
1343
|
+
const hash = new URLSearchParams();
|
|
1344
|
+
hash.set("key", opts.key);
|
|
1345
|
+
if (opts.anonymousId) hash.set("anon", opts.anonymousId);
|
|
1346
|
+
if (opts.userId) hash.set("uid", opts.userId);
|
|
1347
|
+
src.hash = hash.toString();
|
|
1348
|
+
const root = doc.createElement("div");
|
|
1349
|
+
root.setAttribute(OVERLAY_ATTR, "");
|
|
1350
|
+
root.setAttribute("role", "dialog");
|
|
1351
|
+
root.setAttribute("aria-modal", "true");
|
|
1352
|
+
root.setAttribute("aria-label", "Prototype test");
|
|
1353
|
+
root.style.cssText = `position:fixed;inset:0;z-index:${opts.zIndex ?? 2147483646};background:rgba(15,23,42,.55);display:flex;align-items:stretch;justify-content:center;padding:0;margin:0;`;
|
|
1354
|
+
const frame = doc.createElement("iframe");
|
|
1355
|
+
frame.src = src.toString();
|
|
1356
|
+
frame.title = "Prototype test";
|
|
1357
|
+
frame.setAttribute("allow", "fullscreen");
|
|
1358
|
+
frame.setAttribute("referrerpolicy", "no-referrer");
|
|
1359
|
+
frame.style.cssText = "border:0;width:100%;height:100%;background:#fff;display:block;";
|
|
1360
|
+
const close = doc.createElement("button");
|
|
1361
|
+
close.type = "button";
|
|
1362
|
+
close.setAttribute("aria-label", "Close prototype test");
|
|
1363
|
+
close.textContent = "\xD7";
|
|
1364
|
+
close.style.cssText = "position:absolute;top:10px;right:10px;width:36px;height:36px;border-radius:999px;border:0;background:rgba(15,23,42,.75);color:#fff;font:600 22px/36px system-ui,sans-serif;text-align:center;cursor:pointer;padding:0;z-index:1;";
|
|
1365
|
+
root.append(frame, close);
|
|
1366
|
+
const overflow = doc.documentElement.style.overflow;
|
|
1367
|
+
doc.documentElement.style.overflow = "hidden";
|
|
1368
|
+
let closed = false;
|
|
1369
|
+
const shut = () => {
|
|
1370
|
+
if (closed) return;
|
|
1371
|
+
closed = true;
|
|
1372
|
+
window.removeEventListener("message", onMessage);
|
|
1373
|
+
doc.removeEventListener("keydown", onKey, true);
|
|
1374
|
+
doc.removeEventListener("focusin", onFocus, true);
|
|
1375
|
+
root.remove();
|
|
1376
|
+
doc.documentElement.style.overflow = overflow;
|
|
1377
|
+
if (current === overlay) current = null;
|
|
1378
|
+
try {
|
|
1379
|
+
previous?.focus?.();
|
|
1380
|
+
} catch {
|
|
1381
|
+
}
|
|
1382
|
+
opts.onClose?.();
|
|
1383
|
+
};
|
|
1384
|
+
const onMessage = (e) => {
|
|
1385
|
+
if (e.origin !== testUrl.origin || e.source !== frame.contentWindow) return;
|
|
1386
|
+
const data = e.data;
|
|
1387
|
+
if (data && typeof data === "object" && data.type === PROTOTYPE_CLOSE_MESSAGE) shut();
|
|
1388
|
+
};
|
|
1389
|
+
const onKey = (e) => {
|
|
1390
|
+
if (e.key === "Escape") {
|
|
1391
|
+
e.preventDefault();
|
|
1392
|
+
e.stopPropagation();
|
|
1393
|
+
shut();
|
|
1394
|
+
} else if (e.key === "Tab") {
|
|
1395
|
+
e.preventDefault();
|
|
1396
|
+
(doc.activeElement === close ? frame : close).focus();
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
const onFocus = (e) => {
|
|
1400
|
+
if (!root.contains(e.target)) close.focus();
|
|
1401
|
+
};
|
|
1402
|
+
close.addEventListener("click", shut);
|
|
1403
|
+
window.addEventListener("message", onMessage);
|
|
1404
|
+
doc.addEventListener("keydown", onKey, true);
|
|
1405
|
+
doc.addEventListener("focusin", onFocus, true);
|
|
1406
|
+
doc.body.appendChild(root);
|
|
1407
|
+
frame.focus();
|
|
1408
|
+
const overlay = { element: root, close: shut };
|
|
1409
|
+
current = overlay;
|
|
1410
|
+
return overlay;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1032
1413
|
// src/load-recorder.ts
|
|
1033
1414
|
var loadRecorder = async () => (await import("./recorder.js")).recorder;
|
|
1034
1415
|
|
|
1035
1416
|
// src/client.ts
|
|
1036
1417
|
var SDK_NAME = "@guidelytech/sdk-core";
|
|
1037
|
-
var SDK_VERSION = "0.
|
|
1418
|
+
var SDK_VERSION = "0.11.0";
|
|
1038
1419
|
var DEFAULT_API_URL = "https://api.guidely.app";
|
|
1039
1420
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
1040
1421
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -1060,7 +1441,7 @@ function emptyState() {
|
|
|
1060
1441
|
return { guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] };
|
|
1061
1442
|
}
|
|
1062
1443
|
function newSession(now = Date.now()) {
|
|
1063
|
-
return { id: `s_${
|
|
1444
|
+
return { id: `s_${randomId2(16)}`, startedAt: new Date(now).toISOString(), shownGuides: [], events: [], lastActivityAt: now };
|
|
1064
1445
|
}
|
|
1065
1446
|
var GuidelyClient = class _GuidelyClient {
|
|
1066
1447
|
constructor(options) {
|
|
@@ -1072,6 +1453,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1072
1453
|
__publicField(this, "queue");
|
|
1073
1454
|
/** Survey answers and guide/checklist progress, kept until the server accepts them. */
|
|
1074
1455
|
__publicField(this, "outbox");
|
|
1456
|
+
/** Agent Analytics turns (agentConversation), in their own outbox. */
|
|
1457
|
+
__publicField(this, "agentTurns");
|
|
1075
1458
|
__publicField(this, "config", null);
|
|
1076
1459
|
__publicField(this, "etag", null);
|
|
1077
1460
|
__publicField(this, "anonymousId");
|
|
@@ -1121,6 +1504,10 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1121
1504
|
__publicField(this, "flags");
|
|
1122
1505
|
/** Experiments already reported this session (`experiment_viewed` is sent once per session). */
|
|
1123
1506
|
__publicField(this, "exposures", /* @__PURE__ */ new Set());
|
|
1507
|
+
/** Language set with `setLocale()`. */
|
|
1508
|
+
__publicField(this, "locale", null);
|
|
1509
|
+
/** Language each shown guide was rendered in; added to its events as `locale`. */
|
|
1510
|
+
__publicField(this, "guideLocales", /* @__PURE__ */ new Map());
|
|
1124
1511
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
1125
1512
|
__publicField(this, "eventBus", {
|
|
1126
1513
|
on: (name, cb) => {
|
|
@@ -1155,7 +1542,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1155
1542
|
this.sessionStore = this.options.storage === "memory" ? this.store : createStorage("session");
|
|
1156
1543
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
1157
1544
|
if (!this.anonymousId) {
|
|
1158
|
-
this.anonymousId = `anon_${
|
|
1545
|
+
this.anonymousId = `anon_${randomId2(20)}`;
|
|
1159
1546
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
1160
1547
|
}
|
|
1161
1548
|
this.firstVisit = !this.store.get(KEYS.seen);
|
|
@@ -1165,7 +1552,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1165
1552
|
const session = readJson(this.sessionStore, KEYS.session, null);
|
|
1166
1553
|
this.session = session && session.id ? session : newSession();
|
|
1167
1554
|
this.persistSession();
|
|
1168
|
-
this.lastUrl =
|
|
1555
|
+
this.lastUrl = this.pageHref();
|
|
1169
1556
|
this.lastAudiences = new Set(this.state.audienceIds);
|
|
1170
1557
|
this.queue = new EventQueue({
|
|
1171
1558
|
store: this.store,
|
|
@@ -1180,6 +1567,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1180
1567
|
onError: (err) => this.reportError(err)
|
|
1181
1568
|
});
|
|
1182
1569
|
this.outbox = new Outbox({ api: this.api, store: this.store, storageKey: KEYS.outbox(this.options.projectId), health: this.api.health });
|
|
1570
|
+
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 } });
|
|
1183
1571
|
if (options.user) this.user = { ...options.user };
|
|
1184
1572
|
this.flags = this.createFlags(options.flags);
|
|
1185
1573
|
}
|
|
@@ -1256,7 +1644,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1256
1644
|
this.resumeActiveGuide();
|
|
1257
1645
|
this.evaluate("ready");
|
|
1258
1646
|
if (!this.options.disableAuthoring && !capturing) this.maybeStartAuthoring();
|
|
1259
|
-
this.pageTracker.start(
|
|
1647
|
+
this.pageTracker.start(this.pageHref());
|
|
1260
1648
|
this.touchSession();
|
|
1261
1649
|
this.syncCapture();
|
|
1262
1650
|
} catch (err) {
|
|
@@ -1289,7 +1677,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1289
1677
|
wireListeners() {
|
|
1290
1678
|
const doc = document;
|
|
1291
1679
|
const win = window;
|
|
1292
|
-
this.cleanups.push(watchNavigation((url) => this.handleNavigation(url)));
|
|
1680
|
+
this.cleanups.push(watchNavigation((url) => this.handleNavigation(this.pageHref(url))));
|
|
1293
1681
|
const onClick = (e) => this.handleDocumentClick(e);
|
|
1294
1682
|
doc.addEventListener("click", onClick, true);
|
|
1295
1683
|
this.cleanups.push(() => doc.removeEventListener("click", onClick, true));
|
|
@@ -1310,7 +1698,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1310
1698
|
const onPageShow = (e) => {
|
|
1311
1699
|
if (!e.persisted || !this.pageLeft) return;
|
|
1312
1700
|
this.pageLeft = false;
|
|
1313
|
-
this.pageTracker.start(
|
|
1701
|
+
this.pageTracker.start(this.pageHref());
|
|
1314
1702
|
};
|
|
1315
1703
|
let lastScroll = 0;
|
|
1316
1704
|
const onScroll = () => {
|
|
@@ -1359,7 +1747,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1359
1747
|
this.cleanups.push(() => clearInterval(refresh));
|
|
1360
1748
|
const poll = setInterval(() => {
|
|
1361
1749
|
if (!this.isReady || !this.config || this.editing() || doc.visibilityState === "hidden") return;
|
|
1362
|
-
const url =
|
|
1750
|
+
const url = this.pageHref();
|
|
1363
1751
|
const ctx = this.getContext();
|
|
1364
1752
|
for (const guide of this.config.guides) {
|
|
1365
1753
|
if (guide.config.trigger.type === "element_visible" && guide.config.trigger.value) this.armVisibility(guide, ctx, url);
|
|
@@ -1428,10 +1816,11 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1428
1816
|
this.queue.flushSync();
|
|
1429
1817
|
this.queue.destroy();
|
|
1430
1818
|
this.outbox.destroy();
|
|
1819
|
+
this.agentTurns.destroy();
|
|
1431
1820
|
this.flags.destroy();
|
|
1432
1821
|
this.emitter.clear();
|
|
1433
1822
|
destroyHost();
|
|
1434
|
-
if (
|
|
1823
|
+
if (current2 === this) current2 = null;
|
|
1435
1824
|
}
|
|
1436
1825
|
// ---------------------------------------------------------------------------
|
|
1437
1826
|
// Identity
|
|
@@ -1449,7 +1838,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1449
1838
|
if (this.isReady) this.evaluate("identify");
|
|
1450
1839
|
}
|
|
1451
1840
|
async syncIdentify() {
|
|
1452
|
-
const body = { anonymousId: this.anonymousId, userId: this.user?.id ?? null, traits: this.user?.traits, account: this.user?.account ? { id: this.user.account.id, traits: this.user.account.traits } : null, sessionId: this.session.id, url: this.collectUrls() ?
|
|
1841
|
+
const body = { anonymousId: this.anonymousId, userId: this.user?.id ?? null, traits: this.user?.traits, account: this.user?.account ? { id: this.user.account.id, traits: this.user.account.traits } : null, sessionId: this.session.id, url: this.collectUrls() ? this.pageHref() : void 0 };
|
|
1453
1842
|
const res = await postWithRetry(this.api, "/identify", body, 2);
|
|
1454
1843
|
if (res?.ok && res.data) this.mergeServerState(res.data);
|
|
1455
1844
|
}
|
|
@@ -1480,7 +1869,10 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1480
1869
|
checklists,
|
|
1481
1870
|
surveysSubmitted: Array.from(/* @__PURE__ */ new Set([...this.state.surveysSubmitted, ...data.surveysSubmitted || []])),
|
|
1482
1871
|
audienceIds: data.audienceIds || [],
|
|
1483
|
-
firstSeenAt: this.state.firstSeenAt ?? data.firstSeenAt
|
|
1872
|
+
firstSeenAt: this.state.firstSeenAt ?? data.firstSeenAt,
|
|
1873
|
+
// Device-local only (the server doesn't hold them).
|
|
1874
|
+
...this.state.throttle ? { throttle: this.state.throttle } : {},
|
|
1875
|
+
...this.state.announcementsSeenAt ? { announcementsSeenAt: this.state.announcementsSeenAt } : {}
|
|
1484
1876
|
};
|
|
1485
1877
|
if (data.firstSeenAt && Date.now() - Date.parse(data.firstSeenAt) > 6e4) this.firstVisit = false;
|
|
1486
1878
|
this.persistState();
|
|
@@ -1516,7 +1908,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1516
1908
|
this.stopGuide();
|
|
1517
1909
|
this.user = null;
|
|
1518
1910
|
this.store.remove(KEYS.user(this.options.projectId));
|
|
1519
|
-
this.anonymousId = `anon_${
|
|
1911
|
+
this.anonymousId = `anon_${randomId2(20)}`;
|
|
1520
1912
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
1521
1913
|
this.state = emptyState();
|
|
1522
1914
|
this.persistState();
|
|
@@ -1548,6 +1940,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1548
1940
|
if (consent === "denied") this.queue.clear();
|
|
1549
1941
|
else if (consent === "granted") void this.queue.flush();
|
|
1550
1942
|
if (consent !== "granted") this.replay?.stop({ discard: true });
|
|
1943
|
+
if (consent !== "pending") this.agentTurns.consentChanged(consent === "granted");
|
|
1551
1944
|
this.syncCapture();
|
|
1552
1945
|
}
|
|
1553
1946
|
analyticsEnabled() {
|
|
@@ -1560,17 +1953,19 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1560
1953
|
if (this.consent === "denied" || !this.options.analytics) return null;
|
|
1561
1954
|
if (name !== "page_leave" && name !== "heatmap") this.touchSession();
|
|
1562
1955
|
const event = {
|
|
1563
|
-
eventId:
|
|
1956
|
+
eventId: randomId2(24),
|
|
1564
1957
|
name,
|
|
1565
1958
|
occurredAt: nowIso(),
|
|
1566
1959
|
anonymousId: this.anonymousId,
|
|
1567
1960
|
userId: this.user?.id || null,
|
|
1568
1961
|
accountId: this.user?.account?.id || null,
|
|
1569
1962
|
sessionId: this.session.id,
|
|
1570
|
-
url: this.collectUrls() ?
|
|
1963
|
+
url: this.collectUrls() ? this.pageHref() : void 0,
|
|
1571
1964
|
...extra
|
|
1572
1965
|
};
|
|
1573
1966
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
1967
|
+
const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
|
|
1968
|
+
if (locale) event.properties = { ...event.properties ?? {}, locale };
|
|
1574
1969
|
this.queue.enqueue(event);
|
|
1575
1970
|
this.replay?.addCustomEvent("guidely", { name, customName: event.customName, guideId: event.guideId });
|
|
1576
1971
|
this.emitter.emit("track", event);
|
|
@@ -1594,9 +1989,30 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1594
1989
|
this.completeChecklistTasksBy("event", name);
|
|
1595
1990
|
if (this.isReady) this.evaluate("event", { eventName: name });
|
|
1596
1991
|
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Agent Analytics: records one or more turns of a conversation with your own AI agent or chatbot
|
|
1994
|
+
* (`{ conversationId, agentId, role, text, toolCalls?, latencyMs?, outcome?, … }`). The visitor's
|
|
1995
|
+
* anonymous id, user id, account id and session id are filled in, `agents.redact` from the init
|
|
1996
|
+
* options runs on each turn, and turns are retried until Guidely accepts them. Returns false when
|
|
1997
|
+
* nothing was queued (analytics off or consent denied, or every turn was invalid or dropped).
|
|
1998
|
+
*/
|
|
1999
|
+
agentConversation(turns) {
|
|
2000
|
+
if (this.destroyed || !this.options.analytics || this.consent === "denied" || this.config?.settings?.analyticsEnabled === false) return false;
|
|
2001
|
+
const identity = { anonymousId: this.anonymousId, userId: this.user?.id || null, accountId: this.user?.account?.id || null, sessionId: this.session.id };
|
|
2002
|
+
const prepared = (Array.isArray(turns) ? turns : [turns]).map((t) => prepareAgentTurn(t, identity, this.options.agents?.redact, (err) => this.reportError(err))).filter((t) => !!t);
|
|
2003
|
+
if (!prepared.length) return false;
|
|
2004
|
+
this.touchSession();
|
|
2005
|
+
this.agentTurns.add(prepared, this.consent === "pending");
|
|
2006
|
+
return true;
|
|
2007
|
+
}
|
|
2008
|
+
/** Alias of {@link agentConversation}. */
|
|
2009
|
+
trackAgentTurn(turns) {
|
|
2010
|
+
return this.agentConversation(turns);
|
|
2011
|
+
}
|
|
1597
2012
|
/** Records a page view and re-evaluates triggers. Called automatically on SPA navigation. */
|
|
1598
|
-
page(
|
|
2013
|
+
page(href) {
|
|
1599
2014
|
if (this.destroyed) return;
|
|
2015
|
+
const url = this.pageHref(href);
|
|
1600
2016
|
this.leavePage(url);
|
|
1601
2017
|
this.trackPageView(url);
|
|
1602
2018
|
this.handleNavigation(url, true);
|
|
@@ -1688,6 +2104,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1688
2104
|
const ref = !this.session.pageViews ? externalReferrer() : void 0;
|
|
1689
2105
|
if (ref) props.referrer = ref;
|
|
1690
2106
|
}
|
|
2107
|
+
const ep = this.captureAllowed() ? pagePropertyValues(this.config?.tagging, url, { mask: this.maskSelector(), maskAll: this.maskAllText() }) : void 0;
|
|
2108
|
+
if (ep) props.ep = ep;
|
|
1691
2109
|
this.session.pageViews = (this.session.pageViews ?? 0) + 1;
|
|
1692
2110
|
this.persistSession();
|
|
1693
2111
|
this.enqueue("page_view", { url: collect ? url : void 0, properties: props });
|
|
@@ -1713,13 +2131,20 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1713
2131
|
const heat = this.autocaptureOn("heatmaps");
|
|
1714
2132
|
if (!clicks && !rage && !heat || isIgnoredElement(target)) return;
|
|
1715
2133
|
this.touchSession();
|
|
1716
|
-
const
|
|
2134
|
+
const featureAttribute = this.config?.tagging?.featureAttribute ?? null;
|
|
2135
|
+
const feature = featureAttributeOf(target, featureAttribute);
|
|
2136
|
+
const el = interactiveElement(target) ?? feature?.el ?? null;
|
|
1717
2137
|
const opts = { mask: this.maskSelector(), maskAll: this.maskAllText(), collectUrls: this.collectUrls() };
|
|
1718
|
-
|
|
2138
|
+
const clickProps = (on) => {
|
|
2139
|
+
const p = describeClick(on, e, opts);
|
|
2140
|
+
const ep = clickPropertyValues(this.config?.tagging, on, p, opts);
|
|
2141
|
+
return { ...p, el: elementSnapshot(on, { ...opts, featureAttribute }), ...feature ? { fa: feature.value } : {}, ...ep ? { ep } : {} };
|
|
2142
|
+
};
|
|
2143
|
+
if (clicks && el) this.enqueue("click", { properties: clickProps(el) });
|
|
1719
2144
|
const count = this.rage.add(e.pageX ?? 0, e.pageY ?? 0, Date.now());
|
|
1720
|
-
if (rage && count) this.enqueue("rage_click", { properties: { ...
|
|
2145
|
+
if (rage && count) this.enqueue("rage_click", { properties: { ...clickProps(el ?? target), count } });
|
|
1721
2146
|
if (!heat) return;
|
|
1722
|
-
const url =
|
|
2147
|
+
const url = this.pageHref();
|
|
1723
2148
|
if (this.heatmap.url !== url) this.flushHeatmap();
|
|
1724
2149
|
if (this.heatmap.add(url, heatmapPoint(el ?? target, e, this.rage.burst > 0), this.rage.burst)) this.flushHeatmap();
|
|
1725
2150
|
}
|
|
@@ -1727,7 +2152,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1727
2152
|
sampleMove(e) {
|
|
1728
2153
|
const target = composedTarget(e);
|
|
1729
2154
|
if (document.visibilityState === "hidden" || !target?.closest || !this.autocaptureOn("mouseMovement") || isIgnoredElement(target)) return;
|
|
1730
|
-
const url =
|
|
2155
|
+
const url = this.pageHref();
|
|
1731
2156
|
if (this.heatmap.url !== url) this.flushHeatmap();
|
|
1732
2157
|
if (this.heatmap.move(url, movementBin(interactiveElement(target) ?? target, e))) this.flushHeatmap();
|
|
1733
2158
|
}
|
|
@@ -1803,7 +2228,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1803
2228
|
if (!this.isReady || this.destroyed) return;
|
|
1804
2229
|
if (this.config && !this.initialPageViewSent && this.config.settings?.autocapture) {
|
|
1805
2230
|
this.initialPageViewSent = true;
|
|
1806
|
-
this.trackPageView(
|
|
2231
|
+
this.trackPageView(this.pageHref());
|
|
1807
2232
|
}
|
|
1808
2233
|
if (this.config) {
|
|
1809
2234
|
for (const [props, extra] of this.earlyExceptions.splice(0)) {
|
|
@@ -1838,7 +2263,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1838
2263
|
return {
|
|
1839
2264
|
anonymousId: this.anonymousId,
|
|
1840
2265
|
userId: this.user?.id || null,
|
|
1841
|
-
url: this.collectUrls() ?
|
|
2266
|
+
url: this.collectUrls() ? this.pageHref() : void 0,
|
|
1842
2267
|
device: { type: device.type, browser: device.browser, os: device.os },
|
|
1843
2268
|
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
1844
2269
|
sdk: { name: SDK_NAME, version: SDK_VERSION }
|
|
@@ -1889,6 +2314,14 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1889
2314
|
// ---------------------------------------------------------------------------
|
|
1890
2315
|
// Targeting + triggers
|
|
1891
2316
|
// ---------------------------------------------------------------------------
|
|
2317
|
+
/** The page URL Guidely works with: `options.pageUrl`, else app-relative for `file:` / custom protocols (`normalizeAppUrl`). */
|
|
2318
|
+
pageHref(href = location.href) {
|
|
2319
|
+
try {
|
|
2320
|
+
return this.options.pageUrl ? this.options.pageUrl(href) || href : normalizeAppUrl(href);
|
|
2321
|
+
} catch {
|
|
2322
|
+
return href;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
1892
2325
|
/** Builds the targeting context for the current moment. */
|
|
1893
2326
|
getContext() {
|
|
1894
2327
|
const guides = this.state.guides;
|
|
@@ -1899,7 +2332,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1899
2332
|
userId: this.user?.id || null,
|
|
1900
2333
|
userAttributes: this.user?.traits ?? {},
|
|
1901
2334
|
accountAttributes: this.user?.account?.traits ?? {},
|
|
1902
|
-
url:
|
|
2335
|
+
url: this.pageHref(),
|
|
1903
2336
|
firstVisit: this.firstVisit,
|
|
1904
2337
|
eventsSeen: this.session.events,
|
|
1905
2338
|
completedGuides: keysOf(Object.keys(guides).filter((id) => guides[id]?.completedAt)),
|
|
@@ -1908,7 +2341,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1908
2341
|
completedChecklists: keysOf(completedChecklists),
|
|
1909
2342
|
audienceIds: this.state.audienceIds,
|
|
1910
2343
|
environment: this.options.environment,
|
|
1911
|
-
platform: "web"
|
|
2344
|
+
platform: "web",
|
|
2345
|
+
...this.options.appVersion ? { appVersion: this.options.appVersion } : {}
|
|
1912
2346
|
});
|
|
1913
2347
|
}
|
|
1914
2348
|
isTargeted(guide, ctx, url) {
|
|
@@ -1920,6 +2354,52 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1920
2354
|
ctx.rolloutSalt = guide.id;
|
|
1921
2355
|
return evaluateRules(c.targeting, ctx);
|
|
1922
2356
|
}
|
|
2357
|
+
/** Guide throttling: may this guide launch automatically now? */
|
|
2358
|
+
throttleAllows(guide) {
|
|
2359
|
+
if (!countsTowardThrottle(guide)) return true;
|
|
2360
|
+
return isThrottleEligible(this.config?.settings.throttling, this.state.throttle ?? []);
|
|
2361
|
+
}
|
|
2362
|
+
recordLaunch(guide, manual) {
|
|
2363
|
+
if (manual || !countsTowardThrottle(guide) || !this.config?.settings.throttling?.enabled) return;
|
|
2364
|
+
this.state.throttle = pruneLaunches([...this.state.throttle ?? [], nowIso()], this.config.settings.throttling);
|
|
2365
|
+
this.persistState();
|
|
2366
|
+
}
|
|
2367
|
+
/** Guides and A/B test control groups, which are evaluated the same way but never render. */
|
|
2368
|
+
candidates() {
|
|
2369
|
+
const guides = this.config?.guides ?? [];
|
|
2370
|
+
const holdouts = this.config?.guideHoldouts ?? [];
|
|
2371
|
+
return holdouts.length ? [...guides, ...holdouts.map((h) => h.guide)] : guides;
|
|
2372
|
+
}
|
|
2373
|
+
holdoutFor(guide) {
|
|
2374
|
+
return this.config?.guideHoldouts?.find((h) => h.guide.id === guide.id) ?? null;
|
|
2375
|
+
}
|
|
2376
|
+
/** The visitor's language candidates, most preferred first. */
|
|
2377
|
+
locales() {
|
|
2378
|
+
const nav = typeof navigator !== "undefined" ? [...navigator.languages ?? [], navigator.language] : [];
|
|
2379
|
+
return visitorLocales(this.locale ?? this.options.locale, this.user?.traits, nav);
|
|
2380
|
+
}
|
|
2381
|
+
defaultLocale() {
|
|
2382
|
+
return this.config?.settings.localization?.defaultLocale || "en";
|
|
2383
|
+
}
|
|
2384
|
+
/** The guide in the visitor's language (remembered so its events carry `locale`). */
|
|
2385
|
+
localize(guide) {
|
|
2386
|
+
const localized = localizeGuide(guide, this.locales(), this.defaultLocale());
|
|
2387
|
+
this.guideLocales.set(guide.id, localized.locale);
|
|
2388
|
+
return localized;
|
|
2389
|
+
}
|
|
2390
|
+
/**
|
|
2391
|
+
* Sets the language for guides and the resource center (`null` = back to the traits / browser).
|
|
2392
|
+
* Guides already on screen keep their language; the resource center re-renders.
|
|
2393
|
+
*/
|
|
2394
|
+
setLocale(locale) {
|
|
2395
|
+
this.locale = locale ? normalizeLocale(locale) || null : null;
|
|
2396
|
+
if (this.resourceCenter) {
|
|
2397
|
+
this.resourceCenter.destroy();
|
|
2398
|
+
this.resourceCenterScope?.destroy();
|
|
2399
|
+
this.resourceCenter = null;
|
|
2400
|
+
this.renderPersistentWidgets();
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
1923
2403
|
isEligible(guide, ctx, url) {
|
|
1924
2404
|
if (!this.isTargeted(guide, ctx, url)) return false;
|
|
1925
2405
|
const shownThisSession = this.session.shownGuides.includes(guide.id);
|
|
@@ -1936,13 +2416,13 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1936
2416
|
if (t.delayMs > 0) return reason === "timer" && detail.guideId === guide.id;
|
|
1937
2417
|
return reason === "ready" || reason === "navigation" || reason === "identify";
|
|
1938
2418
|
case "url_match":
|
|
1939
|
-
return (reason === "ready" || reason === "navigation" || reason === "identify") && urlMatches2(t.value,
|
|
2419
|
+
return (reason === "ready" || reason === "navigation" || reason === "identify") && urlMatches2(t.value, this.pageHref());
|
|
1940
2420
|
case "event":
|
|
1941
2421
|
return reason === "event" && !!t.value && detail.eventName === t.value;
|
|
1942
2422
|
case "element_click":
|
|
1943
2423
|
if (reason !== "click" || !t.value || !detail.target) return false;
|
|
1944
2424
|
try {
|
|
1945
|
-
return !!
|
|
2425
|
+
return !!closestComposed4(detail.target, t.value);
|
|
1946
2426
|
} catch {
|
|
1947
2427
|
return false;
|
|
1948
2428
|
}
|
|
@@ -1961,15 +2441,15 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1961
2441
|
}
|
|
1962
2442
|
evaluate(reason, detail = {}) {
|
|
1963
2443
|
if (!this.config || this.destroyed || this.editing()) return;
|
|
1964
|
-
const url =
|
|
2444
|
+
const url = this.pageHref();
|
|
1965
2445
|
const ctx = this.getContext();
|
|
1966
2446
|
if (reason === "identify") {
|
|
1967
|
-
const
|
|
1968
|
-
detail.newAudiences = new Set(Array.from(
|
|
1969
|
-
this.lastAudiences =
|
|
2447
|
+
const current3 = new Set(this.state.audienceIds);
|
|
2448
|
+
detail.newAudiences = new Set(Array.from(current3).filter((a) => !this.lastAudiences.has(a)));
|
|
2449
|
+
this.lastAudiences = current3;
|
|
1970
2450
|
}
|
|
1971
2451
|
const candidates = [];
|
|
1972
|
-
for (const guide of this.
|
|
2452
|
+
for (const guide of this.candidates()) {
|
|
1973
2453
|
const t = guide.config.trigger;
|
|
1974
2454
|
if (detail.endedGuide && detail.endedGuide.id === guide.id) continue;
|
|
1975
2455
|
if (reason === "ready" || reason === "navigation") {
|
|
@@ -1985,6 +2465,10 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1985
2465
|
if (t.type === "page_load" && t.delayMs > 0 && reason === "timer" && detail.guideId !== guide.id) continue;
|
|
1986
2466
|
if (!this.triggerFires(guide, reason, detail)) continue;
|
|
1987
2467
|
if (!this.isEligible(guide, ctx, url)) continue;
|
|
2468
|
+
if (!this.throttleAllows(guide)) {
|
|
2469
|
+
this.log("throttled", guide.key);
|
|
2470
|
+
continue;
|
|
2471
|
+
}
|
|
1988
2472
|
candidates.push(guide);
|
|
1989
2473
|
}
|
|
1990
2474
|
if (reason === "after_guide" || reason === "navigation") this.drainQueue(url, ctx);
|
|
@@ -2035,6 +2519,13 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2035
2519
|
/** Decides whether a guide can show now, otherwise queues it. */
|
|
2036
2520
|
tryShow(guide, url, manual) {
|
|
2037
2521
|
if (this.destroyed) return false;
|
|
2522
|
+
if (!manual && !this.throttleAllows(guide)) return false;
|
|
2523
|
+
const holdout = this.holdoutFor(guide);
|
|
2524
|
+
if (holdout) {
|
|
2525
|
+
if (this.active && !manual) return false;
|
|
2526
|
+
this.recordImpression(guide, manual);
|
|
2527
|
+
return true;
|
|
2528
|
+
}
|
|
2038
2529
|
if (!isExclusiveGuide(guide)) {
|
|
2039
2530
|
if (this.widgets.has(guide.id)) return false;
|
|
2040
2531
|
this.show(guide, manual);
|
|
@@ -2075,14 +2566,18 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2075
2566
|
guideState(id) {
|
|
2076
2567
|
return this.state.guides[id] ?? (this.state.guides[id] = { impressions: 0 });
|
|
2077
2568
|
}
|
|
2078
|
-
recordImpression(guide) {
|
|
2569
|
+
recordImpression(guide, manual = false) {
|
|
2079
2570
|
const s = this.guideState(guide.id);
|
|
2080
2571
|
s.impressions += 1;
|
|
2081
2572
|
s.lastShownAt = nowIso();
|
|
2082
2573
|
if (!this.session.shownGuides.includes(guide.id)) this.session.shownGuides.push(guide.id);
|
|
2574
|
+
this.recordLaunch(guide, manual);
|
|
2083
2575
|
this.persistState();
|
|
2084
2576
|
this.persistSession();
|
|
2085
|
-
this.
|
|
2577
|
+
const holdout = this.holdoutFor(guide);
|
|
2578
|
+
const experiment = holdout ? { id: holdout.experimentId, variantId: holdout.variantId } : guide.experiment;
|
|
2579
|
+
if (experiment) this.enqueue("experiment_viewed", { properties: { guideExperimentId: experiment.id, variantId: experiment.variantId, ...holdout ? {} : { guideId: guide.id } } });
|
|
2580
|
+
if (!holdout) this.syncGuideState(guide.id, { impression: true });
|
|
2086
2581
|
}
|
|
2087
2582
|
recordEnd(guide, reason) {
|
|
2088
2583
|
const s = this.guideState(guide.id);
|
|
@@ -2106,13 +2601,17 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2106
2601
|
this.reportError(err);
|
|
2107
2602
|
}
|
|
2108
2603
|
}
|
|
2109
|
-
showFlow(
|
|
2604
|
+
showFlow(raw, manual, stepId) {
|
|
2110
2605
|
if (this.editing()) return;
|
|
2606
|
+
const guide = this.localize(raw);
|
|
2111
2607
|
const exclusive = isExclusiveGuide(guide);
|
|
2112
|
-
const entry = { guide, manual, startUrl:
|
|
2608
|
+
const entry = { guide, manual, startUrl: this.pageHref() };
|
|
2113
2609
|
const controller = runGuide2(guide, {
|
|
2114
2610
|
theme: this.theme(),
|
|
2611
|
+
// Code blocks only arrive when a workspace admin allowed them (the server strips them otherwise).
|
|
2612
|
+
runCode: true,
|
|
2115
2613
|
ctx: () => this.getContext(),
|
|
2614
|
+
currentUrl: () => this.pageHref(),
|
|
2116
2615
|
startAtStep: stepId,
|
|
2117
2616
|
eventBus: this.eventBus,
|
|
2118
2617
|
// Started from Help, Scout or startGuide(): steps on other pages offer to take the user there.
|
|
@@ -2126,7 +2625,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2126
2625
|
const step = e.stepId ? guide.config.steps.find((s) => s.id === e.stepId) : void 0;
|
|
2127
2626
|
this.enqueue(e.name, { guideId: guide.id, guideVersion: guide.version, stepId: e.stepId, stepIndex: e.stepIndex, properties: e.properties });
|
|
2128
2627
|
if (e.name === "guide_started") {
|
|
2129
|
-
this.recordImpression(guide);
|
|
2628
|
+
this.recordImpression(guide, manual);
|
|
2130
2629
|
this.emitter.emit("guide:start", guide);
|
|
2131
2630
|
}
|
|
2132
2631
|
if (e.name === "step_viewed" && step) {
|
|
@@ -2173,8 +2672,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2173
2672
|
this.widgets.delete(guide.id);
|
|
2174
2673
|
}
|
|
2175
2674
|
}
|
|
2176
|
-
showChecklist(
|
|
2675
|
+
showChecklist(raw) {
|
|
2177
2676
|
if (this.editing()) return;
|
|
2677
|
+
const guide = this.localize(raw);
|
|
2178
2678
|
const config = guide.config.checklist;
|
|
2179
2679
|
if (!config || this.widgets.has(guide.id)) return;
|
|
2180
2680
|
const scope = createScope(this.theme(), guide.config.appearance);
|
|
@@ -2195,12 +2695,13 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2195
2695
|
}, checklist: widget, scope });
|
|
2196
2696
|
if (first) this.recordImpression(guide);
|
|
2197
2697
|
}
|
|
2198
|
-
showSurvey(
|
|
2698
|
+
showSurvey(raw, manual) {
|
|
2199
2699
|
if (this.editing()) return;
|
|
2700
|
+
const guide = this.localize(raw);
|
|
2200
2701
|
const config = guide.config.survey;
|
|
2201
2702
|
if (!config) return;
|
|
2202
2703
|
const scope = createScope(this.theme(), guide.config.appearance);
|
|
2203
|
-
const entry = { guide, manual, startUrl:
|
|
2704
|
+
const entry = { guide, manual, startUrl: this.pageHref(), scope };
|
|
2204
2705
|
const finish = (reason) => {
|
|
2205
2706
|
widget.destroy();
|
|
2206
2707
|
scope.destroy();
|
|
@@ -2218,7 +2719,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2218
2719
|
title: guide.name,
|
|
2219
2720
|
onDismiss: () => finish(this.state.surveysSubmitted.includes(guide.id) ? "submitted" : "dismissed"),
|
|
2220
2721
|
onSubmit: (answers) => {
|
|
2221
|
-
const responseId =
|
|
2722
|
+
const responseId = randomId2(24);
|
|
2222
2723
|
this.state.surveysSubmitted.push(guide.id);
|
|
2223
2724
|
this.persistState();
|
|
2224
2725
|
this.recordEnd(guide, "completed");
|
|
@@ -2231,7 +2732,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2231
2732
|
});
|
|
2232
2733
|
entry.survey = widget;
|
|
2233
2734
|
this.active = entry;
|
|
2234
|
-
this.recordImpression(guide);
|
|
2735
|
+
this.recordImpression(guide, manual);
|
|
2235
2736
|
this.enqueue("survey_displayed", { guideId: guide.id, guideVersion: guide.version });
|
|
2236
2737
|
this.emitter.emit("guide:start", guide);
|
|
2237
2738
|
}
|
|
@@ -2240,6 +2741,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2240
2741
|
}
|
|
2241
2742
|
navigate(url, newTab) {
|
|
2242
2743
|
try {
|
|
2744
|
+
if (this.openPrototypeTest(url)) return;
|
|
2243
2745
|
if (newTab) window.open(url, "_blank", "noopener");
|
|
2244
2746
|
else location.assign(url);
|
|
2245
2747
|
} catch (err) {
|
|
@@ -2264,7 +2766,13 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2264
2766
|
this.resourceCenterScope = scope;
|
|
2265
2767
|
this.resourceCenter = renderResourceCenter({
|
|
2266
2768
|
scope: scope.el,
|
|
2267
|
-
config: rc,
|
|
2769
|
+
config: localizeResourceCenter(rc, this.locales(), this.defaultLocale()),
|
|
2770
|
+
evaluate: (rules) => evaluateRules(rules, this.getContext()),
|
|
2771
|
+
announcementsSeenAt: () => this.state.announcementsSeenAt ?? null,
|
|
2772
|
+
onAnnouncementsSeen: () => {
|
|
2773
|
+
this.state.announcementsSeenAt = nowIso();
|
|
2774
|
+
this.persistState();
|
|
2775
|
+
},
|
|
2268
2776
|
guides: this.config.guides.map((g) => ({ id: g.id, key: g.key, name: g.name, kind: g.kind })),
|
|
2269
2777
|
search: (q) => this.searchResourceCenter(q),
|
|
2270
2778
|
audienceIds: () => this.state.audienceIds,
|
|
@@ -2309,7 +2817,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2309
2817
|
projectId: this.options.projectId,
|
|
2310
2818
|
anonymousId: () => this.anonymousId,
|
|
2311
2819
|
userId: () => this.user?.id || null,
|
|
2312
|
-
pageUrl: () => this.collectUrls() ?
|
|
2820
|
+
pageUrl: () => this.collectUrls() ? this.pageHref() : void 0,
|
|
2821
|
+
locale: () => this.rcLocale()
|
|
2313
2822
|
}),
|
|
2314
2823
|
visitor: () => this.messengerVisitor(),
|
|
2315
2824
|
brand: { logoUrl: this.theme().logoUrl ?? null, name: this.theme().brandName ?? null },
|
|
@@ -2318,7 +2827,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2318
2827
|
const seen = readJson(this.store, KEYS.newsSeen(this.options.projectId), []);
|
|
2319
2828
|
writeJson(this.store, KEYS.newsSeen(this.options.projectId), Array.from(/* @__PURE__ */ new Set([...ids, ...Array.isArray(seen) ? seen : []])).slice(0, 200));
|
|
2320
2829
|
},
|
|
2321
|
-
resourceCenter: this.config.resourceCenter,
|
|
2830
|
+
resourceCenter: this.config.resourceCenter ? localizeResourceCenter(this.config.resourceCenter, this.locales(), this.defaultLocale()) : null,
|
|
2831
|
+
evaluate: (rules) => evaluateRules(rules, this.getContext()),
|
|
2322
2832
|
audienceIds: () => this.state.audienceIds,
|
|
2323
2833
|
guides: this.config.guides.map((g) => ({ id: g.id, key: g.key, name: g.name, kind: g.kind })),
|
|
2324
2834
|
onStartGuide: (idOrKey) => this.startGuide(idOrKey),
|
|
@@ -2342,7 +2852,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2342
2852
|
/** What the messenger personalises from, computed fresh on every render. */
|
|
2343
2853
|
messengerVisitor() {
|
|
2344
2854
|
const ctx = this.getContext();
|
|
2345
|
-
const url =
|
|
2855
|
+
const url = this.pageHref();
|
|
2346
2856
|
const seen = readJson(this.store, KEYS.newsSeen(this.options.projectId), []);
|
|
2347
2857
|
const visitor = buildMessengerVisitor({
|
|
2348
2858
|
guides: this.config?.guides ?? [],
|
|
@@ -2357,7 +2867,20 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2357
2867
|
},
|
|
2358
2868
|
newsSeen: Array.isArray(seen) ? seen : []
|
|
2359
2869
|
});
|
|
2360
|
-
|
|
2870
|
+
const rc = this.config?.resourceCenter;
|
|
2871
|
+
const rcNews = rc?.announcements?.length ? visibleAnnouncements(localizeResourceCenter(rc, this.locales(), this.defaultLocale()).announcements, this.state.audienceIds).map((a) => ({
|
|
2872
|
+
id: `rc_${a.id}`,
|
|
2873
|
+
key: a.guideId ?? "",
|
|
2874
|
+
title: a.title,
|
|
2875
|
+
body: plainText(a.body),
|
|
2876
|
+
imageUrl: null,
|
|
2877
|
+
publishedAt: a.publishedAt,
|
|
2878
|
+
unread: !(Array.isArray(seen) && seen.includes(`rc_${a.id}`)),
|
|
2879
|
+
articleId: a.articleId ?? null,
|
|
2880
|
+
url: a.url ?? null
|
|
2881
|
+
})) : [];
|
|
2882
|
+
const news = rcNews.length ? [...visitor.news, ...rcNews].sort((a, b) => b.publishedAt.localeCompare(a.publishedAt)).slice(0, 10) : visitor.news;
|
|
2883
|
+
return { ...visitor, news, identified: !!this.user?.id };
|
|
2361
2884
|
}
|
|
2362
2885
|
destroyMessenger() {
|
|
2363
2886
|
this.messenger?.destroy();
|
|
@@ -2365,9 +2888,17 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2365
2888
|
this.messenger = null;
|
|
2366
2889
|
this.messengerScope = null;
|
|
2367
2890
|
}
|
|
2891
|
+
/** The resource center's language for this visitor, or null for the default language. */
|
|
2892
|
+
rcLocale() {
|
|
2893
|
+
const rc = this.config?.resourceCenter;
|
|
2894
|
+
if (!rc) return null;
|
|
2895
|
+
const locale = localizeResourceCenter(rc, this.locales(), this.defaultLocale()).locale;
|
|
2896
|
+
return locale.toLowerCase() === this.defaultLocale().toLowerCase() ? null : locale;
|
|
2897
|
+
}
|
|
2368
2898
|
async loadArticle(id) {
|
|
2369
2899
|
try {
|
|
2370
|
-
const
|
|
2900
|
+
const locale = this.rcLocale();
|
|
2901
|
+
const res = await this.api.get(`/articles/${encodeURIComponent(id)}`, locale ? { params: { locale } } : void 0);
|
|
2371
2902
|
return res.ok && res.data ? res.data.article : null;
|
|
2372
2903
|
} catch {
|
|
2373
2904
|
return null;
|
|
@@ -2432,7 +2963,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2432
2963
|
else if (kind === "guide_completed") hit = value.id === task.completionValue || value.key === task.completionValue;
|
|
2433
2964
|
else if (kind === "click") {
|
|
2434
2965
|
try {
|
|
2435
|
-
hit = !!
|
|
2966
|
+
hit = !!closestComposed4(value, task.completionValue);
|
|
2436
2967
|
} catch {
|
|
2437
2968
|
hit = false;
|
|
2438
2969
|
}
|
|
@@ -2543,6 +3074,18 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2543
3074
|
closeMessenger() {
|
|
2544
3075
|
this.messenger?.close();
|
|
2545
3076
|
}
|
|
3077
|
+
/**
|
|
3078
|
+
* Opens a Guidely prototype test (its `/validate/<token>` URL on this SDK's API origin, or the bare
|
|
3079
|
+
* token) in a full-screen overlay, attributed to the current user. Returns false, doing nothing, for
|
|
3080
|
+
* any other URL. Guide buttons that open a test URL use this automatically.
|
|
3081
|
+
*/
|
|
3082
|
+
openPrototypeTest(urlOrToken) {
|
|
3083
|
+
if (typeof document === "undefined") return false;
|
|
3084
|
+
const url = matchPrototypeTestUrl(urlOrToken, this.options.apiUrl);
|
|
3085
|
+
if (!url) return false;
|
|
3086
|
+
openPrototypeOverlay(url, { key: this.options.projectId, anonymousId: this.anonymousId, userId: this.user?.id ?? null });
|
|
3087
|
+
return true;
|
|
3088
|
+
}
|
|
2546
3089
|
/**
|
|
2547
3090
|
* Opens the complaint form in the messenger, optionally with a category picked (its id from
|
|
2548
3091
|
* Complaints → Categories). Does nothing when the project doesn't take complaints.
|
|
@@ -2551,6 +3094,14 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2551
3094
|
if (!this.config?.messenger?.enabled || !this.config.messenger.complaints?.categories.length) return;
|
|
2552
3095
|
void this.loadMessenger().then((m) => m?.openComplaint(categoryId));
|
|
2553
3096
|
}
|
|
3097
|
+
/**
|
|
3098
|
+
* Opens the Ideas tab in the messenger (feature requests and voting), or one idea by its id.
|
|
3099
|
+
* Does nothing when the project hasn't turned on Ideas.
|
|
3100
|
+
*/
|
|
3101
|
+
openIdeas(requestId) {
|
|
3102
|
+
if (!this.config?.messenger?.enabled || !this.config.messenger.ideas) return;
|
|
3103
|
+
void this.loadMessenger().then((m) => m?.openIdeas(requestId));
|
|
3104
|
+
}
|
|
2554
3105
|
/** Opens a new messenger conversation, optionally pre-filling the message. */
|
|
2555
3106
|
showNewMessage(text) {
|
|
2556
3107
|
if (!this.config?.messenger?.enabled) return;
|
|
@@ -2580,14 +3131,15 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2580
3131
|
const device = detectDevice(navigator.userAgent, window.innerWidth);
|
|
2581
3132
|
const account = this.user?.account;
|
|
2582
3133
|
return {
|
|
2583
|
-
url:
|
|
2584
|
-
path:
|
|
3134
|
+
url: this.pageHref(),
|
|
3135
|
+
path: pathOf2(this.pageHref()),
|
|
2585
3136
|
deviceType: device.type,
|
|
2586
3137
|
browser: device.browser,
|
|
2587
3138
|
os: device.os,
|
|
2588
3139
|
locale: navigator.language || void 0,
|
|
2589
3140
|
environment: this.options.environment,
|
|
2590
3141
|
platform: "web",
|
|
3142
|
+
...this.options.appVersion ? { appVersion: this.options.appVersion } : {},
|
|
2591
3143
|
...this.user?.traits ?? {},
|
|
2592
3144
|
id: this.user?.id || this.anonymousId,
|
|
2593
3145
|
anonymousId: this.anonymousId,
|
|
@@ -2747,18 +3299,18 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2747
3299
|
// ---------------------------------------------------------------------------
|
|
2748
3300
|
/** Creates (or returns the existing) client and starts initialisation. */
|
|
2749
3301
|
static init(options) {
|
|
2750
|
-
if (
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
void
|
|
2754
|
-
return
|
|
3302
|
+
if (current2 && !current2.destroyed && current2.options.projectId === options.projectId) return current2;
|
|
3303
|
+
current2?.destroy();
|
|
3304
|
+
current2 = new _GuidelyClient(options);
|
|
3305
|
+
void current2.init();
|
|
3306
|
+
return current2;
|
|
2755
3307
|
}
|
|
2756
3308
|
/** The most recently created client, if any. */
|
|
2757
3309
|
static get() {
|
|
2758
|
-
return
|
|
3310
|
+
return current2 && !current2.destroyed ? current2 : null;
|
|
2759
3311
|
}
|
|
2760
3312
|
};
|
|
2761
|
-
var
|
|
3313
|
+
var current2 = null;
|
|
2762
3314
|
function webOnly(config) {
|
|
2763
3315
|
return config.guides.every((g) => supportsPlatform(g.config, "web")) ? config : { ...config, guides: config.guides.filter((g) => supportsPlatform(g.config, "web")) };
|
|
2764
3316
|
}
|
|
@@ -2780,7 +3332,7 @@ function externalReferrer() {
|
|
|
2780
3332
|
}
|
|
2781
3333
|
function createGuidely(options) {
|
|
2782
3334
|
const client = new GuidelyClient(options);
|
|
2783
|
-
|
|
3335
|
+
current2 = client;
|
|
2784
3336
|
void client.init();
|
|
2785
3337
|
return client;
|
|
2786
3338
|
}
|
|
@@ -2802,18 +3354,25 @@ export {
|
|
|
2802
3354
|
GuidelyClient,
|
|
2803
3355
|
HEARTBEAT_MS,
|
|
2804
3356
|
KEYS,
|
|
3357
|
+
PROTOTYPE_CLOSE_MESSAGE,
|
|
2805
3358
|
SDK_NAME,
|
|
2806
3359
|
SDK_VERSION,
|
|
2807
3360
|
areMutuallyExclusive,
|
|
2808
3361
|
buildContext,
|
|
2809
3362
|
captureSnapshot,
|
|
3363
|
+
countsTowardThrottle,
|
|
2810
3364
|
createGuidely,
|
|
2811
3365
|
createStorage,
|
|
2812
3366
|
index_default as default,
|
|
2813
3367
|
detectDevice,
|
|
3368
|
+
isAppUrl,
|
|
2814
3369
|
isExclusiveGuide,
|
|
2815
3370
|
isFrequencyEligible,
|
|
3371
|
+
isThrottleEligible,
|
|
2816
3372
|
isWithinSchedule,
|
|
3373
|
+
matchPrototypeTestUrl,
|
|
3374
|
+
normalizeAppUrl,
|
|
3375
|
+
pruneLaunches,
|
|
2817
3376
|
watchNavigation
|
|
2818
3377
|
};
|
|
2819
3378
|
//# sourceMappingURL=index.js.map
|