@guidelytech/sdk-core 0.9.0 → 0.10.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 +15 -4
- package/dist/{capture-C3faAYb7.d.cts → capture-369s4YYT.d.ts} +2 -1
- package/dist/{capture-Dqd7up2n.d.ts → capture-DVJyQ2JZ.d.cts} +2 -1
- package/dist/capture.cjs +2 -0
- package/dist/capture.cjs.map +1 -1
- package/dist/capture.d.cts +3 -2
- package/dist/capture.d.ts +3 -2
- package/dist/capture.js +4 -6
- package/dist/capture.js.map +1 -1
- package/dist/chunk-C4WWSF67.js +374 -0
- package/dist/chunk-C4WWSF67.js.map +1 -0
- package/dist/{chunk-544KSOC3.js → chunk-GDJB466J.js} +115 -18
- package/dist/chunk-GDJB466J.js.map +1 -0
- package/dist/headless.cjs +275 -40
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +43 -3
- package/dist/headless.d.ts +43 -3
- package/dist/headless.js +14 -8
- package/dist/health-DFHUstWd.d.cts +88 -0
- package/dist/health-DFHUstWd.d.ts +88 -0
- package/dist/{http-47tiAlOM.d.ts → http-D0dCcpLN.d.cts} +10 -35
- package/dist/{http-47tiAlOM.d.cts → http-DJd8jRhK.d.ts} +10 -35
- package/dist/index.cjs +342 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -5
- package/dist/index.d.ts +10 -5
- package/dist/index.js +71 -32
- package/dist/index.js.map +1 -1
- package/dist/recorder.cjs +70 -9
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +9 -0
- package/dist/recorder.d.ts +9 -0
- package/dist/recorder.js +57 -11
- package/dist/recorder.js.map +1 -1
- package/dist/{types-DnJW_4ck.d.ts → types-B5ecgHUV.d.cts} +10 -3
- package/dist/{types-zM5e3loY.d.cts → types-BtPovimB.d.ts} +10 -3
- package/package.json +4 -4
- package/dist/chunk-544KSOC3.js.map +0 -1
- package/dist/chunk-AKNE7D5M.js +0 -160
- package/dist/chunk-AKNE7D5M.js.map +0 -1
- package/dist/chunk-CYAG6ZYX.js +0 -85
- package/dist/chunk-CYAG6ZYX.js.map +0 -1
- package/dist/chunk-GDF6RCHQ.js +0 -209
- package/dist/chunk-GDF6RCHQ.js.map +0 -1
- package/dist/types-DbKQPvFt.d.cts +0 -238
- package/dist/types-DqhNl5Am.d.ts +0 -238
- package/dist/types-RyeRQd9f.d.ts +0 -226
- package/dist/types-d4eOerxi.d.cts +0 -226
package/dist/index.cjs
CHANGED
|
@@ -57,7 +57,7 @@ __export(index_exports, {
|
|
|
57
57
|
module.exports = __toCommonJS(index_exports);
|
|
58
58
|
|
|
59
59
|
// src/client.ts
|
|
60
|
-
var
|
|
60
|
+
var import_runtime15 = require("@guidelytech/shared/runtime");
|
|
61
61
|
var import_guide_runtime4 = require("@guidelytech/guide-runtime");
|
|
62
62
|
var import_guide_runtime5 = require("@guidelytech/guide-runtime");
|
|
63
63
|
var import_flags = require("@guidelytech/flags");
|
|
@@ -206,6 +206,8 @@ var KEYS = {
|
|
|
206
206
|
state: (key) => `guidely:state:${key}`,
|
|
207
207
|
user: (key) => `guidely:user:${key}`,
|
|
208
208
|
queue: (key) => `guidely:queue:${key}`,
|
|
209
|
+
/** Survey answers and guide/checklist progress not yet accepted by the server. */
|
|
210
|
+
outbox: (key) => `guidely:outbox:${key}`,
|
|
209
211
|
session: "guidely:session",
|
|
210
212
|
active: "guidely:active",
|
|
211
213
|
authoring: "guidely:authoring",
|
|
@@ -304,7 +306,7 @@ var AuthoringBridge = class {
|
|
|
304
306
|
masked(el) {
|
|
305
307
|
return this.opts.maskSelectors.some((sel) => {
|
|
306
308
|
try {
|
|
307
|
-
return !!
|
|
309
|
+
return !!(0, import_guide_runtime.closestComposed)(el, sel);
|
|
308
310
|
} catch {
|
|
309
311
|
return false;
|
|
310
312
|
}
|
|
@@ -505,11 +507,11 @@ function validSelectors(list) {
|
|
|
505
507
|
return out;
|
|
506
508
|
}
|
|
507
509
|
function isIgnoredElement(el) {
|
|
508
|
-
return (0, import_guide_runtime2.isGuidelyElement)(el) || !!
|
|
510
|
+
return (0, import_guide_runtime2.isGuidelyElement)(el) || !!(0, import_guide_runtime2.closestComposed)(el, "[data-guidely-ignore]");
|
|
509
511
|
}
|
|
510
512
|
function interactiveElement(target) {
|
|
511
513
|
try {
|
|
512
|
-
return
|
|
514
|
+
return (0, import_guide_runtime2.closestComposed)(target, INTERACTIVE_SELECTOR);
|
|
513
515
|
} catch {
|
|
514
516
|
return null;
|
|
515
517
|
}
|
|
@@ -520,14 +522,19 @@ function esc(value) {
|
|
|
520
522
|
}
|
|
521
523
|
var attr = (tag, name, value) => value ? `${tag}[${name}="${value.replace(/["\\]/g, "\\$&")}"]` : `${tag}[${name}]`;
|
|
522
524
|
function elementSelector(el) {
|
|
525
|
+
const own = ownSelector(el);
|
|
526
|
+
const chain = (0, import_guide_runtime2.shadowHostChain)(el);
|
|
527
|
+
return (chain ? chain + import_guide_runtime2.SHADOW_SEPARATOR + own : own).slice(0, 300);
|
|
528
|
+
}
|
|
529
|
+
function ownSelector(el) {
|
|
523
530
|
const tag = el.tagName.toLowerCase();
|
|
524
531
|
if (el.id && (0, import_guide_runtime2.isStableId)(el.id)) return `#${esc(el.id)}`;
|
|
525
|
-
for (const name of PREFERRED_ATTRS) if (el.hasAttribute(name)) return attr(tag, name, el.getAttribute(name) || "")
|
|
532
|
+
for (const name of PREFERRED_ATTRS) if (el.hasAttribute(name)) return attr(tag, name, el.getAttribute(name) || "");
|
|
526
533
|
for (const a of Array.from(el.attributes)) {
|
|
527
|
-
if (a.name.startsWith("data-") && !NOISY_DATA_ATTR.test(a.name) && a.value && a.value.length <= 60) return attr(tag, a.name, a.value)
|
|
534
|
+
if (a.name.startsWith("data-") && !NOISY_DATA_ATTR.test(a.name) && a.value && a.value.length <= 60) return attr(tag, a.name, a.value);
|
|
528
535
|
}
|
|
529
536
|
const label = el.getAttribute("aria-label");
|
|
530
|
-
if (label && label.length <= 80) return attr(tag, "aria-label", label)
|
|
537
|
+
if (label && label.length <= 80) return attr(tag, "aria-label", label);
|
|
531
538
|
const parts = [];
|
|
532
539
|
let cur = el;
|
|
533
540
|
while (cur && parts.length < 4) {
|
|
@@ -543,12 +550,12 @@ function elementSelector(el) {
|
|
|
543
550
|
parts.unshift(same.length > 1 ? `${t}:nth-of-type(${same.indexOf(node) + 1})` : t);
|
|
544
551
|
cur = parent;
|
|
545
552
|
}
|
|
546
|
-
return
|
|
553
|
+
return parts.join(" > ") || tag;
|
|
547
554
|
}
|
|
548
555
|
function matches(el, selector, deep) {
|
|
549
556
|
if (!selector) return false;
|
|
550
557
|
try {
|
|
551
|
-
return !!
|
|
558
|
+
return !!(0, import_guide_runtime2.closestComposed)(el, selector) || deep && !!el.querySelector(selector);
|
|
552
559
|
} catch {
|
|
553
560
|
return false;
|
|
554
561
|
}
|
|
@@ -566,7 +573,7 @@ function elementText(el, mask, maskAll) {
|
|
|
566
573
|
return text ? text.slice(0, 100) : void 0;
|
|
567
574
|
}
|
|
568
575
|
function linkHref(el) {
|
|
569
|
-
const a =
|
|
576
|
+
const a = (0, import_guide_runtime2.closestComposed)(el, "a[href]");
|
|
570
577
|
if (!a) return void 0;
|
|
571
578
|
try {
|
|
572
579
|
const u = new URL(a.getAttribute("href") || "", location.href);
|
|
@@ -898,12 +905,108 @@ function dismissedAfterMerge(local, server) {
|
|
|
898
905
|
return local?.dismissedAt ?? server.dismissedAt ?? null;
|
|
899
906
|
}
|
|
900
907
|
|
|
908
|
+
// src/health.ts
|
|
909
|
+
var MAX_RETRY_AFTER_MS = 60 * 60 * 1e3;
|
|
910
|
+
var OFFLINE_WAIT_MS = 3e4;
|
|
911
|
+
var ServerHealth = class {
|
|
912
|
+
constructor(opts = {}) {
|
|
913
|
+
__publicField(this, "failures", 0);
|
|
914
|
+
__publicField(this, "openUntil", 0);
|
|
915
|
+
__publicField(this, "lastOkAt", 0);
|
|
916
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
917
|
+
__publicField(this, "threshold");
|
|
918
|
+
__publicField(this, "baseMs");
|
|
919
|
+
__publicField(this, "maxMs");
|
|
920
|
+
__publicField(this, "now");
|
|
921
|
+
__publicField(this, "random");
|
|
922
|
+
this.threshold = opts.threshold ?? 3;
|
|
923
|
+
this.baseMs = opts.baseMs ?? 5e3;
|
|
924
|
+
this.maxMs = opts.maxMs ?? 5 * 60 * 1e3;
|
|
925
|
+
this.now = opts.now ?? Date.now;
|
|
926
|
+
this.random = opts.random ?? Math.random;
|
|
927
|
+
}
|
|
928
|
+
/** The server answered (2xx, 3xx, or a 4xx other than 429): it is up. */
|
|
929
|
+
success() {
|
|
930
|
+
const wasDown = this.failures > 0 || this.openUntil > 0;
|
|
931
|
+
this.failures = 0;
|
|
932
|
+
this.openUntil = 0;
|
|
933
|
+
this.lastOkAt = this.now();
|
|
934
|
+
if (wasDown) this.notify();
|
|
935
|
+
}
|
|
936
|
+
/** The request failed or the server is overloaded. `retryAfterMs` comes from a `Retry-After` header. */
|
|
937
|
+
failure(retryAfterMs) {
|
|
938
|
+
this.failures += 1;
|
|
939
|
+
const now = this.now();
|
|
940
|
+
if (retryAfterMs != null && retryAfterMs > 0) {
|
|
941
|
+
this.openUntil = Math.max(this.openUntil, now + Math.min(retryAfterMs, MAX_RETRY_AFTER_MS));
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
if (this.failures < this.threshold) return;
|
|
945
|
+
const cooldown = Math.min(this.maxMs, this.baseMs * 2 ** (this.failures - this.threshold));
|
|
946
|
+
this.openUntil = Math.max(this.openUntil, now + cooldown * (0.8 + this.random() * 0.4));
|
|
947
|
+
}
|
|
948
|
+
/** Milliseconds background traffic should wait before trying again (0: send now). */
|
|
949
|
+
waitMs() {
|
|
950
|
+
if (typeof navigator !== "undefined" && navigator.onLine === false) return OFFLINE_WAIT_MS;
|
|
951
|
+
return Math.max(0, this.openUntil - this.now());
|
|
952
|
+
}
|
|
953
|
+
canSend() {
|
|
954
|
+
return this.waitMs() === 0;
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* The last request got an answer. Unload beacons can't report back, so what they carry is only
|
|
958
|
+
* forgotten while this holds; otherwise it stays queued and is sent again (the server drops duplicates).
|
|
959
|
+
*/
|
|
960
|
+
get reachable() {
|
|
961
|
+
return this.failures === 0 && this.lastOkAt > 0;
|
|
962
|
+
}
|
|
963
|
+
/** The browser came back online: end the cooldown and let queued traffic try straight away. */
|
|
964
|
+
online() {
|
|
965
|
+
this.openUntil = 0;
|
|
966
|
+
this.notify();
|
|
967
|
+
}
|
|
968
|
+
/** Called when the server answers again after failures, or the browser comes back online. */
|
|
969
|
+
onRecover(cb) {
|
|
970
|
+
this.listeners.add(cb);
|
|
971
|
+
return () => this.listeners.delete(cb);
|
|
972
|
+
}
|
|
973
|
+
notify() {
|
|
974
|
+
for (const cb of Array.from(this.listeners)) {
|
|
975
|
+
try {
|
|
976
|
+
cb();
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
983
|
+
if (!value) return null;
|
|
984
|
+
const trimmed = value.trim();
|
|
985
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
986
|
+
const at = Date.parse(trimmed);
|
|
987
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
988
|
+
}
|
|
989
|
+
function isServerFailure(status) {
|
|
990
|
+
return status === 429 || status >= 500;
|
|
991
|
+
}
|
|
992
|
+
|
|
901
993
|
// src/http.ts
|
|
994
|
+
var STREAM_RESPONSE_TIMEOUT_MS = 3e4;
|
|
995
|
+
var STREAM_IDLE_TIMEOUT_MS = 6e4;
|
|
902
996
|
var Api = class {
|
|
903
|
-
constructor(baseUrl, key, fetchImpl) {
|
|
997
|
+
constructor(baseUrl, key, fetchImpl, health) {
|
|
904
998
|
__publicField(this, "baseUrl", baseUrl);
|
|
905
999
|
__publicField(this, "key", key);
|
|
906
1000
|
__publicField(this, "fetchImpl", fetchImpl);
|
|
1001
|
+
/** Reachability of the API, fed by every answer (see {@link ServerHealth}). */
|
|
1002
|
+
__publicField(this, "health");
|
|
1003
|
+
this.health = health ?? new ServerHealth();
|
|
1004
|
+
}
|
|
1005
|
+
/** Feeds an answer (or a failed request, `null`) into {@link health}. */
|
|
1006
|
+
observe(res) {
|
|
1007
|
+
if (!res) this.health.failure();
|
|
1008
|
+
else if (isServerFailure(res.status)) this.health.failure(parseRetryAfter(res.headers?.get?.("retry-after")));
|
|
1009
|
+
else this.health.success();
|
|
907
1010
|
}
|
|
908
1011
|
url(path, params = {}) {
|
|
909
1012
|
const query = Object.entries({ key: this.key, ...params }).filter((e) => e[1] != null).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
@@ -923,15 +1026,22 @@ var Api = class {
|
|
|
923
1026
|
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
924
1027
|
const timer = ac ? setTimeout(() => ac.abort(), options.timeoutMs ?? 1e4) : null;
|
|
925
1028
|
try {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1029
|
+
let res;
|
|
1030
|
+
try {
|
|
1031
|
+
res = await f(this.url(path, options.params), {
|
|
1032
|
+
method,
|
|
1033
|
+
headers,
|
|
1034
|
+
body: options.body === void 0 ? void 0 : form ? options.body : JSON.stringify(options.body),
|
|
1035
|
+
keepalive: options.keepalive,
|
|
1036
|
+
signal: ac?.signal,
|
|
1037
|
+
credentials: "omit",
|
|
1038
|
+
mode: "cors"
|
|
1039
|
+
});
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
this.observe(null);
|
|
1042
|
+
throw err;
|
|
1043
|
+
}
|
|
1044
|
+
this.observe(res);
|
|
935
1045
|
let data = null;
|
|
936
1046
|
if (res.status !== 304 && res.status !== 204) {
|
|
937
1047
|
try {
|
|
@@ -957,30 +1067,53 @@ var Api = class {
|
|
|
957
1067
|
}
|
|
958
1068
|
/**
|
|
959
1069
|
* POST that may answer with server-sent events. Each event is passed to `onEvent`; a JSON answer is
|
|
960
|
-
* returned instead.
|
|
1070
|
+
* returned instead. Scout replies can take a while, so there is no overall timeout: the response
|
|
1071
|
+
* must start within 30 s and then never go quiet for more than 60 s (the server sends keepalives),
|
|
1072
|
+
* otherwise the request is aborted and rejects.
|
|
961
1073
|
*/
|
|
962
1074
|
async stream(path, body, onEvent, headers = {}) {
|
|
963
1075
|
const f = this.fetcher;
|
|
964
1076
|
if (!f) throw new Error("fetch is not available");
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
let
|
|
1077
|
+
const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
1078
|
+
let timer = null;
|
|
1079
|
+
const arm = (ms) => {
|
|
1080
|
+
if (!ac) return;
|
|
1081
|
+
if (timer) clearTimeout(timer);
|
|
1082
|
+
timer = setTimeout(() => ac.abort(), ms);
|
|
1083
|
+
};
|
|
1084
|
+
arm(STREAM_RESPONSE_TIMEOUT_MS);
|
|
1085
|
+
try {
|
|
1086
|
+
let res;
|
|
975
1087
|
try {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1088
|
+
res = await f(this.url(path), {
|
|
1089
|
+
method: "POST",
|
|
1090
|
+
headers: { ...headers, "X-Guidely-Key": this.key, Accept: "text/event-stream, application/json", "Content-Type": "application/json" },
|
|
1091
|
+
body: JSON.stringify(body),
|
|
1092
|
+
signal: ac?.signal,
|
|
1093
|
+
credentials: "omit",
|
|
1094
|
+
mode: "cors"
|
|
1095
|
+
});
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
this.observe(null);
|
|
1098
|
+
throw err;
|
|
979
1099
|
}
|
|
980
|
-
|
|
1100
|
+
this.observe(res);
|
|
1101
|
+
const type = res.headers?.get?.("content-type") ?? "";
|
|
1102
|
+
if (!type.includes("text/event-stream") || !res.body) {
|
|
1103
|
+
let data = null;
|
|
1104
|
+
try {
|
|
1105
|
+
data = await res.json();
|
|
1106
|
+
} catch {
|
|
1107
|
+
data = null;
|
|
1108
|
+
}
|
|
1109
|
+
return { status: res.status, ok: res.ok, data, etag: null };
|
|
1110
|
+
}
|
|
1111
|
+
arm(STREAM_IDLE_TIMEOUT_MS);
|
|
1112
|
+
await readEventStream(res.body, onEvent, () => arm(STREAM_IDLE_TIMEOUT_MS));
|
|
1113
|
+
return { status: res.status, ok: res.ok, data: null, etag: null };
|
|
1114
|
+
} finally {
|
|
1115
|
+
if (timer) clearTimeout(timer);
|
|
981
1116
|
}
|
|
982
|
-
await readEventStream(res.body, onEvent);
|
|
983
|
-
return { status: res.status, ok: res.ok, data: null, etag: null };
|
|
984
1117
|
}
|
|
985
1118
|
/** `navigator.sendBeacon` when available (page unload); returns false when it could not be queued. */
|
|
986
1119
|
beacon(path, body) {
|
|
@@ -1011,7 +1144,7 @@ async function withRetry(send, attempts = 3, baseMs = 500) {
|
|
|
1011
1144
|
function postWithRetry(api, path, body, attempts = 3, baseMs = 500) {
|
|
1012
1145
|
return withRetry(() => api.post(path, body), attempts, baseMs);
|
|
1013
1146
|
}
|
|
1014
|
-
async function readEventStream(body, onEvent) {
|
|
1147
|
+
async function readEventStream(body, onEvent, onChunk) {
|
|
1015
1148
|
const reader = body.getReader();
|
|
1016
1149
|
const decoder = new TextDecoder();
|
|
1017
1150
|
let buffer = "";
|
|
@@ -1034,6 +1167,7 @@ async function readEventStream(body, onEvent) {
|
|
|
1034
1167
|
};
|
|
1035
1168
|
for (; ; ) {
|
|
1036
1169
|
const { done, value } = await reader.read();
|
|
1170
|
+
if (value) onChunk?.();
|
|
1037
1171
|
if (value) buffer += decoder.decode(value, { stream: true }).replace(/\r\n?/g, "\n");
|
|
1038
1172
|
let sep;
|
|
1039
1173
|
while ((sep = buffer.indexOf("\n\n")) >= 0) {
|
|
@@ -1187,7 +1321,7 @@ function createMessengerTransport(opts) {
|
|
|
1187
1321
|
}
|
|
1188
1322
|
|
|
1189
1323
|
// src/messenger-visitor.ts
|
|
1190
|
-
var
|
|
1324
|
+
var import_runtime11 = require("@guidelytech/shared/runtime");
|
|
1191
1325
|
var import_guide_runtime3 = require("@guidelytech/guide-runtime");
|
|
1192
1326
|
var PAGE_GUIDE_KINDS = /* @__PURE__ */ new Set(["flow", "tooltip", "modal"]);
|
|
1193
1327
|
var CATCH_ALL = /* @__PURE__ */ new Set(["", "*", "**", "/*", "/**", "/"]);
|
|
@@ -1254,7 +1388,7 @@ function buildMessengerVisitor(input) {
|
|
|
1254
1388
|
unread: !seen.has(g.id) && !(state.guides[g.id]?.impressions ?? 0)
|
|
1255
1389
|
};
|
|
1256
1390
|
});
|
|
1257
|
-
return { firstName: (0,
|
|
1391
|
+
return { firstName: (0, import_runtime11.firstNameFromTraits)(input.traits), path: pathOf(input.url), pageGuides, resumeGuide, checklist, news };
|
|
1258
1392
|
}
|
|
1259
1393
|
|
|
1260
1394
|
// src/navigation.ts
|
|
@@ -1357,6 +1491,90 @@ var PageTracker = class {
|
|
|
1357
1491
|
}
|
|
1358
1492
|
};
|
|
1359
1493
|
|
|
1494
|
+
// src/outbox.ts
|
|
1495
|
+
var Outbox = class {
|
|
1496
|
+
constructor(opts) {
|
|
1497
|
+
__publicField(this, "opts", opts);
|
|
1498
|
+
__publicField(this, "items");
|
|
1499
|
+
__publicField(this, "sending", false);
|
|
1500
|
+
__publicField(this, "attempt", 0);
|
|
1501
|
+
__publicField(this, "timer", null);
|
|
1502
|
+
__publicField(this, "destroyed", false);
|
|
1503
|
+
__publicField(this, "offRecover");
|
|
1504
|
+
__publicField(this, "maxItems");
|
|
1505
|
+
__publicField(this, "maxAgeMs");
|
|
1506
|
+
this.maxItems = opts.maxItems ?? 50;
|
|
1507
|
+
this.maxAgeMs = opts.maxAgeMs ?? 7 * 24 * 60 * 60 * 1e3;
|
|
1508
|
+
const stored = readJson(opts.store, opts.storageKey, []);
|
|
1509
|
+
const cutoff = Date.now() - this.maxAgeMs;
|
|
1510
|
+
this.items = Array.isArray(stored) ? stored.filter((i) => i && typeof i.path === "string" && typeof i.key === "string" && i.createdAt > cutoff).slice(-this.maxItems) : [];
|
|
1511
|
+
this.offRecover = opts.health?.onRecover(() => {
|
|
1512
|
+
this.attempt = 0;
|
|
1513
|
+
this.retryIn(0);
|
|
1514
|
+
}) ?? null;
|
|
1515
|
+
if (this.items.length) this.retryIn(0);
|
|
1516
|
+
}
|
|
1517
|
+
get size() {
|
|
1518
|
+
return this.items.length;
|
|
1519
|
+
}
|
|
1520
|
+
/** Queues `POST path` and sends it now (or as soon as the API is reachable). */
|
|
1521
|
+
add(key, path, body) {
|
|
1522
|
+
if (this.destroyed) return;
|
|
1523
|
+
this.items = this.items.filter((i) => i.key !== key);
|
|
1524
|
+
this.items.push({ key, path, body, createdAt: Date.now() });
|
|
1525
|
+
if (this.items.length > this.maxItems) this.items.splice(0, this.items.length - this.maxItems);
|
|
1526
|
+
this.persist();
|
|
1527
|
+
void this.flush();
|
|
1528
|
+
}
|
|
1529
|
+
persist() {
|
|
1530
|
+
writeJson(this.opts.store, this.opts.storageKey, this.items);
|
|
1531
|
+
}
|
|
1532
|
+
retryIn(ms) {
|
|
1533
|
+
if (this.destroyed) return;
|
|
1534
|
+
if (this.timer) clearTimeout(this.timer);
|
|
1535
|
+
this.timer = setTimeout(() => {
|
|
1536
|
+
this.timer = null;
|
|
1537
|
+
void this.flush();
|
|
1538
|
+
}, ms);
|
|
1539
|
+
}
|
|
1540
|
+
/** Sends queued requests in order, one at a time. */
|
|
1541
|
+
async flush() {
|
|
1542
|
+
if (this.destroyed || this.sending || !this.items.length) return;
|
|
1543
|
+
const wait = this.opts.health?.waitMs() ?? 0;
|
|
1544
|
+
if (wait > 0) return this.retryIn(wait);
|
|
1545
|
+
this.sending = true;
|
|
1546
|
+
try {
|
|
1547
|
+
while (this.items.length && !this.destroyed) {
|
|
1548
|
+
const item = this.items[0];
|
|
1549
|
+
let retry = false;
|
|
1550
|
+
try {
|
|
1551
|
+
const res = await this.opts.api.post(item.path, item.body);
|
|
1552
|
+
retry = !res.ok && (res.status === 429 || res.status >= 500);
|
|
1553
|
+
} catch (err) {
|
|
1554
|
+
retry = true;
|
|
1555
|
+
this.opts.onError?.(err);
|
|
1556
|
+
}
|
|
1557
|
+
if (retry) {
|
|
1558
|
+
this.attempt += 1;
|
|
1559
|
+
this.retryIn(Math.max(backoffMs(this.attempt - 1, 1e3, 6e4), this.opts.health?.waitMs() ?? 0));
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
this.attempt = 0;
|
|
1563
|
+
this.items = this.items.filter((i) => i !== item);
|
|
1564
|
+
this.persist();
|
|
1565
|
+
}
|
|
1566
|
+
} finally {
|
|
1567
|
+
this.sending = false;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
destroy() {
|
|
1571
|
+
this.destroyed = true;
|
|
1572
|
+
this.offRecover?.();
|
|
1573
|
+
if (this.timer) clearTimeout(this.timer);
|
|
1574
|
+
this.timer = null;
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1360
1578
|
// src/queue.ts
|
|
1361
1579
|
var EventQueue = class {
|
|
1362
1580
|
constructor(opts) {
|
|
@@ -1368,17 +1586,34 @@ var EventQueue = class {
|
|
|
1368
1586
|
__publicField(this, "inflightIds", /* @__PURE__ */ new Set());
|
|
1369
1587
|
__publicField(this, "attempt", 0);
|
|
1370
1588
|
__publicField(this, "destroyed", false);
|
|
1589
|
+
__publicField(this, "offRecover", null);
|
|
1371
1590
|
__publicField(this, "flushIntervalMs");
|
|
1372
1591
|
__publicField(this, "maxQueue");
|
|
1373
1592
|
__publicField(this, "batchSize");
|
|
1374
1593
|
__publicField(this, "maxAttempts");
|
|
1375
1594
|
this.flushIntervalMs = opts.flushIntervalMs ?? 2e3;
|
|
1376
|
-
this.maxQueue = opts.maxQueue ??
|
|
1595
|
+
this.maxQueue = opts.maxQueue ?? 500;
|
|
1377
1596
|
this.batchSize = opts.batchSize ?? 50;
|
|
1378
1597
|
this.maxAttempts = opts.maxAttempts ?? 5;
|
|
1379
1598
|
const persisted = readJson(opts.store, opts.storageKey, []);
|
|
1380
1599
|
if (Array.isArray(persisted)) this.events = persisted.filter((e) => e && typeof e.eventId === "string").slice(-this.maxQueue);
|
|
1381
1600
|
if (this.events.length) this.schedule();
|
|
1601
|
+
this.offRecover = opts.health?.onRecover(() => this.retryNow()) ?? null;
|
|
1602
|
+
}
|
|
1603
|
+
/** The API is back: drop any pending backoff and send what is queued. */
|
|
1604
|
+
retryNow() {
|
|
1605
|
+
if (this.destroyed) return;
|
|
1606
|
+
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
1607
|
+
this.retryTimer = null;
|
|
1608
|
+
this.attempt = 0;
|
|
1609
|
+
void this.flush();
|
|
1610
|
+
}
|
|
1611
|
+
retryIn(ms) {
|
|
1612
|
+
if (this.retryTimer || this.destroyed) return;
|
|
1613
|
+
this.retryTimer = setTimeout(() => {
|
|
1614
|
+
this.retryTimer = null;
|
|
1615
|
+
void this.flush();
|
|
1616
|
+
}, ms);
|
|
1382
1617
|
}
|
|
1383
1618
|
get size() {
|
|
1384
1619
|
return this.events.length;
|
|
@@ -1412,6 +1647,8 @@ var EventQueue = class {
|
|
|
1412
1647
|
clearTimeout(this.timer);
|
|
1413
1648
|
this.timer = null;
|
|
1414
1649
|
}
|
|
1650
|
+
const wait = this.opts.health?.waitMs() ?? 0;
|
|
1651
|
+
if (wait > 0) return this.retryIn(wait);
|
|
1415
1652
|
this.inflight = true;
|
|
1416
1653
|
const batch = this.events.slice(0, this.batchSize);
|
|
1417
1654
|
this.inflightIds = new Set(batch.map((e) => e.eventId));
|
|
@@ -1435,26 +1672,20 @@ var EventQueue = class {
|
|
|
1435
1672
|
if (this.events.length) this.schedule();
|
|
1436
1673
|
return;
|
|
1437
1674
|
}
|
|
1438
|
-
this.attempt
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
return;
|
|
1442
|
-
}
|
|
1443
|
-
this.retryTimer = setTimeout(() => {
|
|
1444
|
-
this.retryTimer = null;
|
|
1445
|
-
void this.flush();
|
|
1446
|
-
}, backoffMs(this.attempt - 1));
|
|
1675
|
+
this.attempt = Math.min(this.attempt + 1, this.maxAttempts);
|
|
1676
|
+
const delay = this.attempt >= this.maxAttempts ? 6e4 : backoffMs(this.attempt - 1);
|
|
1677
|
+
this.retryIn(Math.max(delay, this.opts.health?.waitMs() ?? 0));
|
|
1447
1678
|
}
|
|
1448
1679
|
/** Best-effort synchronous flush on unload using `sendBeacon`. */
|
|
1449
1680
|
flushSync() {
|
|
1450
1681
|
if (this.destroyed || !this.opts.enabled() || !this.opts.beacon) return;
|
|
1451
1682
|
const batch = this.events.filter((e) => !this.inflightIds.has(e.eventId)).slice(0, this.batchSize);
|
|
1452
1683
|
if (!batch.length) return;
|
|
1453
|
-
if (this.opts.beacon(batch)) {
|
|
1684
|
+
if (this.opts.beacon(batch) && (this.opts.health?.reachable ?? true)) {
|
|
1454
1685
|
const ids = new Set(batch.map((e) => e.eventId));
|
|
1455
1686
|
this.events = this.events.filter((e) => !ids.has(e.eventId));
|
|
1456
|
-
this.persist();
|
|
1457
1687
|
}
|
|
1688
|
+
this.persist();
|
|
1458
1689
|
}
|
|
1459
1690
|
clear() {
|
|
1460
1691
|
this.events = [];
|
|
@@ -1462,6 +1693,7 @@ var EventQueue = class {
|
|
|
1462
1693
|
}
|
|
1463
1694
|
destroy() {
|
|
1464
1695
|
this.destroyed = true;
|
|
1696
|
+
this.offRecover?.();
|
|
1465
1697
|
if (this.timer) clearTimeout(this.timer);
|
|
1466
1698
|
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
1467
1699
|
this.timer = null;
|
|
@@ -1475,11 +1707,22 @@ var loadRecorder = async () => (await import("./recorder.cjs")).recorder;
|
|
|
1475
1707
|
|
|
1476
1708
|
// src/client.ts
|
|
1477
1709
|
var SDK_NAME = "@guidelytech/sdk-core";
|
|
1478
|
-
var SDK_VERSION = "0.
|
|
1710
|
+
var SDK_VERSION = "0.10.0";
|
|
1479
1711
|
var DEFAULT_API_URL = "https://api.guidely.app";
|
|
1480
1712
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
1481
1713
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1482
1714
|
var MAX_SESSION_EXCEPTIONS = 100;
|
|
1715
|
+
var CACHED_START_MS = 1500;
|
|
1716
|
+
function settlesWithin(promise, ms) {
|
|
1717
|
+
return new Promise((resolve) => {
|
|
1718
|
+
const timer = setTimeout(() => resolve(false), ms);
|
|
1719
|
+
const done = () => {
|
|
1720
|
+
clearTimeout(timer);
|
|
1721
|
+
resolve(true);
|
|
1722
|
+
};
|
|
1723
|
+
promise.then(done, done);
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1483
1726
|
function isBrowser() {
|
|
1484
1727
|
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
1485
1728
|
}
|
|
@@ -1490,7 +1733,7 @@ function emptyState() {
|
|
|
1490
1733
|
return { guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] };
|
|
1491
1734
|
}
|
|
1492
1735
|
function newSession(now = Date.now()) {
|
|
1493
|
-
return { id: `s_${(0,
|
|
1736
|
+
return { id: `s_${(0, import_runtime15.randomId)(16)}`, startedAt: new Date(now).toISOString(), shownGuides: [], events: [], lastActivityAt: now };
|
|
1494
1737
|
}
|
|
1495
1738
|
var GuidelyClient = class _GuidelyClient {
|
|
1496
1739
|
constructor(options) {
|
|
@@ -1500,6 +1743,8 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1500
1743
|
__publicField(this, "sessionStore");
|
|
1501
1744
|
__publicField(this, "emitter");
|
|
1502
1745
|
__publicField(this, "queue");
|
|
1746
|
+
/** Survey answers and guide/checklist progress, kept until the server accepts them. */
|
|
1747
|
+
__publicField(this, "outbox");
|
|
1503
1748
|
__publicField(this, "config", null);
|
|
1504
1749
|
__publicField(this, "etag", null);
|
|
1505
1750
|
__publicField(this, "anonymousId");
|
|
@@ -1583,7 +1828,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1583
1828
|
this.sessionStore = this.options.storage === "memory" ? this.store : createStorage("session");
|
|
1584
1829
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
1585
1830
|
if (!this.anonymousId) {
|
|
1586
|
-
this.anonymousId = `anon_${(0,
|
|
1831
|
+
this.anonymousId = `anon_${(0, import_runtime15.randomId)(20)}`;
|
|
1587
1832
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
1588
1833
|
}
|
|
1589
1834
|
this.firstVisit = !this.store.get(KEYS.seen);
|
|
@@ -1599,6 +1844,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1599
1844
|
store: this.store,
|
|
1600
1845
|
storageKey: KEYS.queue(this.options.projectId),
|
|
1601
1846
|
enabled: () => this.analyticsEnabled(),
|
|
1847
|
+
health: this.api.health,
|
|
1602
1848
|
send: async (events) => {
|
|
1603
1849
|
const res = await this.api.post("/events", { events, sentAt: nowIso(), sdk: { name: SDK_NAME, version: SDK_VERSION } }, { keepalive: true });
|
|
1604
1850
|
return { ok: res.ok, retry: !(res.status >= 400 && res.status < 500 && res.status !== 429) };
|
|
@@ -1606,6 +1852,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1606
1852
|
beacon: (events) => this.api.beacon("/events", { events, sentAt: nowIso(), sdk: { name: SDK_NAME, version: SDK_VERSION } }),
|
|
1607
1853
|
onError: (err) => this.reportError(err)
|
|
1608
1854
|
});
|
|
1855
|
+
this.outbox = new Outbox({ api: this.api, store: this.store, storageKey: KEYS.outbox(this.options.projectId), health: this.api.health });
|
|
1609
1856
|
if (options.user) this.user = { ...options.user };
|
|
1610
1857
|
this.flags = this.createFlags(options.flags);
|
|
1611
1858
|
}
|
|
@@ -1663,12 +1910,15 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1663
1910
|
const cached = readJson(this.store, KEYS.config(this.options.projectId), null);
|
|
1664
1911
|
this.etag = this.store.get(KEYS.etag(this.options.projectId));
|
|
1665
1912
|
if (cached && cached.guides) this.config = webOnly(cached);
|
|
1666
|
-
|
|
1913
|
+
const before = this.config?.configVersion;
|
|
1914
|
+
const fresh = this.fetchConfig();
|
|
1915
|
+
if (!this.config) await fresh;
|
|
1916
|
+
else if (!await settlesWithin(fresh, CACHED_START_MS)) void fresh.then(() => this.applyConfigChange(before));
|
|
1667
1917
|
if (this.destroyed) return;
|
|
1668
1918
|
this.wireListeners();
|
|
1669
1919
|
if (!this.identitySettled) {
|
|
1670
|
-
|
|
1671
|
-
|
|
1920
|
+
const known = this.options.user ?? (this.user?.id ? this.user : null);
|
|
1921
|
+
if (known) await settlesWithin(this.identify(known.id, known.traits, known.account), CACHED_START_MS);
|
|
1672
1922
|
}
|
|
1673
1923
|
if (this.destroyed) return;
|
|
1674
1924
|
this.isReady = true;
|
|
@@ -1773,7 +2023,12 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1773
2023
|
win.removeEventListener("scroll", onScroll);
|
|
1774
2024
|
doc.removeEventListener("keydown", onKey, true);
|
|
1775
2025
|
});
|
|
1776
|
-
const refresh = setInterval(() =>
|
|
2026
|
+
const refresh = setInterval(() => {
|
|
2027
|
+
if (this.api.health.canSend()) void this.refresh();
|
|
2028
|
+
}, this.options.configRefreshMs);
|
|
2029
|
+
const onOnline = () => this.api.health.online();
|
|
2030
|
+
win.addEventListener("online", onOnline);
|
|
2031
|
+
this.cleanups.push(() => win.removeEventListener("online", onOnline));
|
|
1777
2032
|
this.cleanups.push(() => clearInterval(refresh));
|
|
1778
2033
|
const poll = setInterval(() => {
|
|
1779
2034
|
if (!this.isReady || !this.config || this.editing() || doc.visibilityState === "hidden") return;
|
|
@@ -1806,6 +2061,11 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1806
2061
|
if (this.destroyed) return;
|
|
1807
2062
|
const before = this.config?.configVersion;
|
|
1808
2063
|
await this.fetchConfig();
|
|
2064
|
+
this.applyConfigChange(before);
|
|
2065
|
+
}
|
|
2066
|
+
/** Re-renders after a config change (refresh, or a config that arrived after start). */
|
|
2067
|
+
applyConfigChange(before) {
|
|
2068
|
+
if (this.destroyed || !this.isReady) return;
|
|
1809
2069
|
if (this.config && this.config.configVersion !== before) {
|
|
1810
2070
|
this.renderPersistentWidgets();
|
|
1811
2071
|
this.evaluate("ready");
|
|
@@ -1840,6 +2100,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1840
2100
|
this.replay = null;
|
|
1841
2101
|
this.queue.flushSync();
|
|
1842
2102
|
this.queue.destroy();
|
|
2103
|
+
this.outbox.destroy();
|
|
1843
2104
|
this.flags.destroy();
|
|
1844
2105
|
this.emitter.clear();
|
|
1845
2106
|
(0, import_guide_runtime4.destroyHost)();
|
|
@@ -1855,7 +2116,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1855
2116
|
this.user = { id: String(userId), traits: { ...this.user?.id === String(userId) ? this.user.traits : {}, ...traits || {} }, account: account ?? (this.user?.id === String(userId) ? this.user.account : void 0) };
|
|
1856
2117
|
writeJson(this.store, KEYS.user(this.options.projectId), this.user);
|
|
1857
2118
|
this.syncFlagAttributes();
|
|
1858
|
-
this.enqueue("identify", { properties: { traits: (0,
|
|
2119
|
+
this.enqueue("identify", { properties: { traits: (0, import_runtime15.sanitizeProperties)(this.user.traits) } });
|
|
1859
2120
|
await this.syncIdentify();
|
|
1860
2121
|
this.emitter.emit("identify", { userId: this.user.id, anonymousId: this.anonymousId });
|
|
1861
2122
|
if (this.isReady) this.evaluate("identify");
|
|
@@ -1928,7 +2189,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1928
2189
|
this.stopGuide();
|
|
1929
2190
|
this.user = null;
|
|
1930
2191
|
this.store.remove(KEYS.user(this.options.projectId));
|
|
1931
|
-
this.anonymousId = `anon_${(0,
|
|
2192
|
+
this.anonymousId = `anon_${(0, import_runtime15.randomId)(20)}`;
|
|
1932
2193
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
1933
2194
|
this.state = emptyState();
|
|
1934
2195
|
this.persistState();
|
|
@@ -1972,7 +2233,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1972
2233
|
if (this.consent === "denied" || !this.options.analytics) return null;
|
|
1973
2234
|
if (name !== "page_leave" && name !== "heatmap") this.touchSession();
|
|
1974
2235
|
const event = {
|
|
1975
|
-
eventId: (0,
|
|
2236
|
+
eventId: (0, import_runtime15.randomId)(24),
|
|
1976
2237
|
name,
|
|
1977
2238
|
occurredAt: nowIso(),
|
|
1978
2239
|
anonymousId: this.anonymousId,
|
|
@@ -1982,7 +2243,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
1982
2243
|
url: this.collectUrls() ? location.href : void 0,
|
|
1983
2244
|
...extra
|
|
1984
2245
|
};
|
|
1985
|
-
if (extra.properties) event.properties = (0,
|
|
2246
|
+
if (extra.properties) event.properties = (0, import_runtime15.sanitizeProperties)(extra.properties);
|
|
1986
2247
|
this.queue.enqueue(event);
|
|
1987
2248
|
this.replay?.addCustomEvent("guidely", { name, customName: event.customName, guideId: event.guideId });
|
|
1988
2249
|
this.emitter.emit("track", event);
|
|
@@ -2042,7 +2303,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2042
2303
|
if (changed) void this.queue.flush();
|
|
2043
2304
|
}
|
|
2044
2305
|
handleDocumentClick(e) {
|
|
2045
|
-
const target = e
|
|
2306
|
+
const target = (0, import_guide_runtime4.composedTarget)(e);
|
|
2046
2307
|
if (!target || typeof target.closest !== "function") return;
|
|
2047
2308
|
this.autocaptureClick(e, target);
|
|
2048
2309
|
this.completeChecklistTasksBy("click", target);
|
|
@@ -2137,7 +2398,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2137
2398
|
}
|
|
2138
2399
|
/** Bins one pointer sample into the heatmap buffer. Not session activity. */
|
|
2139
2400
|
sampleMove(e) {
|
|
2140
|
-
const target = e
|
|
2401
|
+
const target = (0, import_guide_runtime4.composedTarget)(e);
|
|
2141
2402
|
if (document.visibilityState === "hidden" || !target?.closest || !this.autocaptureOn("mouseMovement") || isIgnoredElement(target)) return;
|
|
2142
2403
|
const url = location.href;
|
|
2143
2404
|
if (this.heatmap.url !== url) this.flushHeatmap();
|
|
@@ -2228,7 +2489,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2228
2489
|
syncReplay() {
|
|
2229
2490
|
const cfg = this.isReady && this.captureAllowed() ? this.replayConfig() : null;
|
|
2230
2491
|
if (cfg && this.session.replay === void 0) {
|
|
2231
|
-
this.session.replay = (0,
|
|
2492
|
+
this.session.replay = (0, import_runtime15.rolloutBucket)(this.session.id, "replay") < cfg.sampleRate;
|
|
2232
2493
|
this.persistSession();
|
|
2233
2494
|
}
|
|
2234
2495
|
if (!cfg || !this.session.replay) {
|
|
@@ -2262,7 +2523,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2262
2523
|
this.persistSession();
|
|
2263
2524
|
this.replay?.stop({ discard: true });
|
|
2264
2525
|
},
|
|
2265
|
-
onError: (err) => this.reportError(err)
|
|
2526
|
+
onError: (err) => this.reportError(err),
|
|
2527
|
+
health: this.api.health,
|
|
2528
|
+
stash: this.sessionStore
|
|
2266
2529
|
});
|
|
2267
2530
|
this.syncReplay();
|
|
2268
2531
|
},
|
|
@@ -2328,7 +2591,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2328
2591
|
if (!isWithinSchedule(guide)) return false;
|
|
2329
2592
|
if (guide.kind === "survey" && (this.state.surveysSubmitted.includes(guide.id) || this.state.surveysSubmitted.includes(guide.key))) return false;
|
|
2330
2593
|
ctx.rolloutSalt = guide.id;
|
|
2331
|
-
return (0,
|
|
2594
|
+
return (0, import_runtime15.evaluateRules)(c.targeting, ctx);
|
|
2332
2595
|
}
|
|
2333
2596
|
isEligible(guide, ctx, url) {
|
|
2334
2597
|
if (!this.isTargeted(guide, ctx, url)) return false;
|
|
@@ -2352,7 +2615,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2352
2615
|
case "element_click":
|
|
2353
2616
|
if (reason !== "click" || !t.value || !detail.target) return false;
|
|
2354
2617
|
try {
|
|
2355
|
-
return !!detail.target
|
|
2618
|
+
return !!(0, import_guide_runtime4.closestComposed)(detail.target, t.value);
|
|
2356
2619
|
} catch {
|
|
2357
2620
|
return false;
|
|
2358
2621
|
}
|
|
@@ -2503,7 +2766,9 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2503
2766
|
if (reason === "completed") this.completeChecklistTasksBy("guide_completed", guide);
|
|
2504
2767
|
}
|
|
2505
2768
|
syncGuideState(guideId, update) {
|
|
2506
|
-
|
|
2769
|
+
const body = { anonymousId: this.anonymousId, userId: this.user?.id || null, updates: [{ guideId, ...update }] };
|
|
2770
|
+
if (update.impression) void postWithRetry(this.api, "/guide-state", body);
|
|
2771
|
+
else this.outbox.add(`guide:${guideId}:${update.completed ? "completed" : "dismissed"}:${this.anonymousId}`, "/guide-state", body);
|
|
2507
2772
|
}
|
|
2508
2773
|
show(guide, manual, stepId) {
|
|
2509
2774
|
try {
|
|
@@ -2626,12 +2891,12 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2626
2891
|
title: guide.name,
|
|
2627
2892
|
onDismiss: () => finish(this.state.surveysSubmitted.includes(guide.id) ? "submitted" : "dismissed"),
|
|
2628
2893
|
onSubmit: (answers) => {
|
|
2629
|
-
const responseId = (0,
|
|
2894
|
+
const responseId = (0, import_runtime15.randomId)(24);
|
|
2630
2895
|
this.state.surveysSubmitted.push(guide.id);
|
|
2631
2896
|
this.persistState();
|
|
2632
2897
|
this.recordEnd(guide, "completed");
|
|
2633
2898
|
this.enqueue("survey_submitted", { guideId: guide.id, guideVersion: guide.version, properties: { responseId } });
|
|
2634
|
-
|
|
2899
|
+
this.outbox.add(`survey:${responseId}`, `/surveys/${encodeURIComponent(guide.id)}/responses`, { anonymousId: this.anonymousId, userId: this.user?.id || null, accountId: this.user?.account?.id || null, guideVersion: guide.version, answers, responseId });
|
|
2635
2900
|
this.emitter.emit("survey:submit", { guide, answers, responseId });
|
|
2636
2901
|
this.emitter.emit("guide:complete", guide);
|
|
2637
2902
|
widget.showThankYou();
|
|
@@ -2761,7 +3026,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2761
3026
|
isAudienceTargeted: (g) => {
|
|
2762
3027
|
if (!isWithinSchedule(g)) return false;
|
|
2763
3028
|
ctx.rolloutSalt = g.id;
|
|
2764
|
-
return (0,
|
|
3029
|
+
return (0, import_runtime15.evaluateRules)(g.config.targeting, ctx);
|
|
2765
3030
|
},
|
|
2766
3031
|
newsSeen: Array.isArray(seen) ? seen : []
|
|
2767
3032
|
});
|
|
@@ -2820,7 +3085,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2820
3085
|
}
|
|
2821
3086
|
this.persistState();
|
|
2822
3087
|
this.widgets.get(guide.id)?.checklist?.setProgress(this.getChecklistProgress(guide.id));
|
|
2823
|
-
|
|
3088
|
+
this.outbox.add(`checklist:${guide.id}:progress:${this.anonymousId}`, `/checklists/${encodeURIComponent(guide.id)}/progress`, { anonymousId: this.anonymousId, userId: this.user?.id || null, guideVersion: guide.version, completedTaskIds: progress.completedTaskIds, completed: !!progress.completedAt });
|
|
2824
3089
|
this.emitter.emit("checklist:task", { guide, task, progress: this.getChecklistProgress(guide.id) });
|
|
2825
3090
|
if (complete) {
|
|
2826
3091
|
this.emitter.emit("checklist:complete", guide);
|
|
@@ -2840,7 +3105,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2840
3105
|
else if (kind === "guide_completed") hit = value.id === task.completionValue || value.key === task.completionValue;
|
|
2841
3106
|
else if (kind === "click") {
|
|
2842
3107
|
try {
|
|
2843
|
-
hit = !!
|
|
3108
|
+
hit = !!(0, import_guide_runtime4.closestComposed)(value, task.completionValue);
|
|
2844
3109
|
} catch {
|
|
2845
3110
|
hit = false;
|
|
2846
3111
|
}
|
|
@@ -2860,7 +3125,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
2860
3125
|
progress.dismissedAt = nowIso();
|
|
2861
3126
|
this.persistState();
|
|
2862
3127
|
this.enqueue("checklist_dismissed", { guideId: guide.id, guideVersion: guide.version });
|
|
2863
|
-
|
|
3128
|
+
this.outbox.add(`checklist:${guide.id}:dismissed:${this.anonymousId}`, `/checklists/${encodeURIComponent(guide.id)}/progress`, { anonymousId: this.anonymousId, userId: this.user?.id || null, guideVersion: guide.version, completedTaskIds: progress.completedTaskIds, dismissed: true });
|
|
2864
3129
|
this.widgets.get(guide.id)?.destroy();
|
|
2865
3130
|
this.widgets.delete(guide.id);
|
|
2866
3131
|
}
|
|
@@ -3168,7 +3433,7 @@ var GuidelyClient = class _GuidelyClient {
|
|
|
3168
3433
|
};
|
|
3169
3434
|
var current = null;
|
|
3170
3435
|
function webOnly(config) {
|
|
3171
|
-
return config.guides.every((g) => (0,
|
|
3436
|
+
return config.guides.every((g) => (0, import_runtime15.supportsPlatform)(g.config, "web")) ? config : { ...config, guides: config.guides.filter((g) => (0, import_runtime15.supportsPlatform)(g.config, "web")) };
|
|
3172
3437
|
}
|
|
3173
3438
|
function pathOf2(url) {
|
|
3174
3439
|
try {
|