@guidelytech/shared 0.15.0 → 0.16.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.
@@ -1,1937 +0,0 @@
1
- // src/utils.ts
2
- function fnv1a(input) {
3
- let h = 2166136261;
4
- for (let i = 0; i < input.length; i++) {
5
- h ^= input.charCodeAt(i);
6
- h = Math.imul(h, 16777619) >>> 0;
7
- }
8
- return h >>> 0;
9
- }
10
- function rolloutBucket(subject, salt) {
11
- return fnv1a(`${salt}:${subject}`) % 100;
12
- }
13
- function slugify(input) {
14
- return input.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "untitled";
15
- }
16
- var ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
17
- function randomId(size = 16) {
18
- const g = typeof globalThis !== "undefined" ? globalThis : {};
19
- let bytes;
20
- if (g.crypto && typeof g.crypto.getRandomValues === "function") {
21
- bytes = new Uint8Array(size);
22
- g.crypto.getRandomValues(bytes);
23
- } else {
24
- bytes = new Uint8Array(size);
25
- for (let i = 0; i < size; i++) bytes[i] = Math.floor(Math.random() * 256);
26
- }
27
- let out = "";
28
- for (let i = 0; i < size; i++) out += ALPHABET[bytes[i] % ALPHABET.length];
29
- return out;
30
- }
31
- function isPlainObject(v) {
32
- return typeof v === "object" && v !== null && !Array.isArray(v);
33
- }
34
- function getPath(obj, path) {
35
- if (!path) return obj;
36
- let cur = obj;
37
- for (const part of path.split(".")) {
38
- if (cur == null) return void 0;
39
- cur = cur[part];
40
- }
41
- return cur;
42
- }
43
- function clamp(n, min, max) {
44
- return Math.min(max, Math.max(min, n));
45
- }
46
- function matchUrlPattern(pattern, url) {
47
- if (!pattern) return true;
48
- if (pattern === url) return true;
49
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "__GLOBSTAR__").replace(/\*/g, "[^/]*").replace(/__GLOBSTAR__/g, ".*");
50
- const re = new RegExp(`^${escaped}$`);
51
- return re.test(url) || re.test(stripOrigin(url));
52
- }
53
- function stripOrigin(url) {
54
- try {
55
- const u = new URL(url);
56
- return u.pathname + u.search + u.hash;
57
- } catch {
58
- return url;
59
- }
60
- }
61
- function safeRegExp(source, flags = "i") {
62
- if (!source || source.length > 200) return null;
63
- try {
64
- return new RegExp(source, flags);
65
- } catch {
66
- return null;
67
- }
68
- }
69
-
70
- // src/platform.ts
71
- var PLATFORMS = ["web", "ios", "android"];
72
- var DEFAULT_PLATFORMS = ["web"];
73
- function guidePlatforms(config) {
74
- return Array.isArray(config.platforms) ? [...config.platforms] : [...DEFAULT_PLATFORMS];
75
- }
76
- function supportsPlatform(config, platform) {
77
- return guidePlatforms(config).includes(platform);
78
- }
79
- var GUIDELY_ID_ATTRIBUTE = "data-guidely-id";
80
- function guidelyIdFromSelector(value) {
81
- const v = (value ?? "").trim();
82
- if (!v) return null;
83
- if (/^[A-Za-z0-9_][A-Za-z0-9_\-:.]*$/.test(v)) return v;
84
- const hash = v.match(/^#([A-Za-z0-9_][A-Za-z0-9_\-:.]*)$/);
85
- if (hash) return hash[1];
86
- const attr = v.match(/^(?:[a-z][a-z0-9]*)?\[\s*data-(?:guidely-id|testid|tour)\s*=\s*(["']?)([^"'\]]+)\1\s*\]$/i);
87
- if (attr) return attr[2].trim();
88
- return null;
89
- }
90
- function screenPath(screen) {
91
- const s = (screen ?? "").trim();
92
- if (!s) return "/";
93
- return s.startsWith("/") ? s : `/${s}`;
94
- }
95
- function compareVersions(a, b) {
96
- const sa = a == null ? "" : String(a).trim().replace(/^v/i, "");
97
- const sb = b == null ? "" : String(b).trim().replace(/^v/i, "");
98
- if (!sa || !sb) return null;
99
- const pa = sa.split(/[.+-]/);
100
- const pb = sb.split(/[.+-]/);
101
- const n = Math.max(pa.length, pb.length);
102
- for (let i = 0; i < n; i++) {
103
- const x = pa[i] ?? "0";
104
- const y = pb[i] ?? "0";
105
- const nx = /^\d+$/.test(x) ? Number(x) : NaN;
106
- const ny = /^\d+$/.test(y) ? Number(y) : NaN;
107
- if (!Number.isNaN(nx) && !Number.isNaN(ny)) {
108
- if (nx !== ny) return nx < ny ? -1 : 1;
109
- } else if (x !== y) {
110
- return x < y ? -1 : 1;
111
- }
112
- }
113
- return 0;
114
- }
115
-
116
- // src/widgets.ts
117
- function isQuestionVisible(q, answers) {
118
- if (!q.showWhen) return true;
119
- const actual = answers[q.showWhen.questionId];
120
- if (actual === void 0) return false;
121
- const expected = q.showWhen.value;
122
- switch (q.showWhen.op) {
123
- case "equals":
124
- return Array.isArray(actual) ? actual.includes(String(expected)) : String(actual) === String(expected);
125
- case "lte":
126
- return Number(actual) <= Number(expected);
127
- case "gte":
128
- return Number(actual) >= Number(expected);
129
- case "in": {
130
- const list = Array.isArray(expected) ? expected : [String(expected)];
131
- return Array.isArray(actual) ? actual.some((a) => list.includes(a)) : list.includes(String(actual));
132
- }
133
- default:
134
- return false;
135
- }
136
- }
137
- function isChecklistComplete(config, progress) {
138
- const done = new Set(progress.completedTaskIds);
139
- return config.tasks.filter((t) => t.required).every((t) => done.has(t.id));
140
- }
141
-
142
- // src/targeting-eval.ts
143
- var RULE_OPERATORS = [
144
- "equals",
145
- "not_equals",
146
- "contains",
147
- "not_contains",
148
- "starts_with",
149
- "ends_with",
150
- "exists",
151
- "not_exists",
152
- "gt",
153
- "gte",
154
- "lt",
155
- "lte",
156
- "in",
157
- "not_in",
158
- "regex",
159
- "is_true",
160
- "is_false",
161
- "before",
162
- "after"
163
- ];
164
- var RULE_FIELDS = [
165
- "user.attribute",
166
- "account.attribute",
167
- "user.role",
168
- "user.plan",
169
- "user.identified",
170
- "url.path",
171
- "url.full",
172
- "url.query",
173
- "device.type",
174
- "device.browser",
175
- "device.os",
176
- "visitor.firstVisit",
177
- "visitor.returning",
178
- "event.seen",
179
- "guide.completed",
180
- "guide.dismissed",
181
- "guide.seen",
182
- "checklist.completed",
183
- "audience.member",
184
- "datetime.now",
185
- "datetime.dayOfWeek",
186
- "datetime.hour",
187
- "rollout.percentage",
188
- /** Guide A/B tests: the visitor's 0-99 bucket for the salt in `key` (see guide-experiments.ts). */
189
- "experiment.bucket",
190
- "environment",
191
- "locale",
192
- "app.platform",
193
- "app.screen",
194
- "app.version",
195
- "page.views",
196
- "feature.clicks"
197
- ];
198
- function isRuleGroup(r) {
199
- return r.rules !== void 0;
200
- }
201
- function emptyRuleGroup(id = "root") {
202
- return { id, op: "and", rules: [] };
203
- }
204
- function has(set, v) {
205
- if (!set) return false;
206
- return set instanceof Set ? set.has(v) : set.includes(v);
207
- }
208
- function resolveField(rule, ctx) {
209
- switch (rule.field) {
210
- case "user.attribute":
211
- return rule.key ? getPath(ctx.userAttributes, rule.key) : void 0;
212
- case "account.attribute":
213
- return rule.key ? getPath(ctx.accountAttributes, rule.key) : void 0;
214
- case "user.role":
215
- return ctx.userAttributes.role;
216
- case "user.plan":
217
- return ctx.userAttributes.plan ?? ctx.accountAttributes.plan;
218
- case "user.identified":
219
- return !!ctx.userId;
220
- case "url.path":
221
- return ctx.path;
222
- case "url.full":
223
- return ctx.url;
224
- case "url.query":
225
- return rule.key ? ctx.query[rule.key] : void 0;
226
- case "device.type":
227
- return ctx.device.type;
228
- case "device.browser":
229
- return ctx.device.browser;
230
- case "device.os":
231
- return ctx.device.os;
232
- case "visitor.firstVisit":
233
- return ctx.firstVisit;
234
- case "visitor.returning":
235
- return !ctx.firstVisit;
236
- case "event.seen":
237
- return rule.key ? has(ctx.eventsSeen, rule.key) : false;
238
- case "guide.completed":
239
- return rule.key ? has(ctx.completedGuides, rule.key) : false;
240
- case "guide.dismissed":
241
- return rule.key ? has(ctx.dismissedGuides, rule.key) : false;
242
- case "guide.seen":
243
- return rule.key ? has(ctx.seenGuides, rule.key) : false;
244
- case "checklist.completed":
245
- return rule.key ? has(ctx.completedChecklists, rule.key) : false;
246
- case "audience.member":
247
- return rule.key ? has(ctx.audienceIds, rule.key) : false;
248
- case "datetime.now":
249
- return ctx.now.toISOString();
250
- case "datetime.dayOfWeek":
251
- return ctx.now.getDay();
252
- case "datetime.hour":
253
- return ctx.now.getHours();
254
- case "rollout.percentage":
255
- return rolloutBucket(ctx.userId || ctx.anonymousId, ctx.rolloutSalt || "guidely");
256
- case "experiment.bucket":
257
- return rule.key ? rolloutBucket(ctx.userId || ctx.anonymousId, rule.key) : void 0;
258
- case "environment":
259
- return ctx.environment;
260
- case "locale":
261
- return ctx.locale;
262
- case "app.platform":
263
- return ctx.platform ?? "web";
264
- case "app.screen":
265
- return ctx.screen;
266
- case "app.version":
267
- return ctx.appVersion;
268
- case "page.views":
269
- case "feature.clicks":
270
- return rule.key ? ctx.tagUsage?.[rule.key] ?? 0 : 0;
271
- default:
272
- return void 0;
273
- }
274
- }
275
- function toNumber(v) {
276
- if (typeof v === "number") return v;
277
- if (typeof v === "string" && v.trim() !== "" && !Number.isNaN(Number(v))) return Number(v);
278
- if (v instanceof Date) return v.getTime();
279
- return null;
280
- }
281
- function toDateMs(v) {
282
- if (v instanceof Date) return v.getTime();
283
- if (typeof v === "string") {
284
- const t = Date.parse(v);
285
- return Number.isNaN(t) ? null : t;
286
- }
287
- if (typeof v === "number") return v;
288
- return null;
289
- }
290
- function compare(op, actual, expected) {
291
- switch (op) {
292
- case "exists":
293
- return actual !== void 0 && actual !== null && actual !== "";
294
- case "not_exists":
295
- return actual === void 0 || actual === null || actual === "";
296
- case "is_true":
297
- return actual === true || actual === "true";
298
- case "is_false":
299
- return actual === false || actual === "false";
300
- case "equals":
301
- if (typeof actual === "boolean") return String(actual) === String(expected);
302
- return String(actual ?? "") === String(expected ?? "");
303
- case "not_equals":
304
- return String(actual ?? "") !== String(expected ?? "");
305
- case "contains":
306
- return typeof actual === "string" && actual.toLowerCase().includes(String(expected ?? "").toLowerCase());
307
- case "not_contains":
308
- return !(typeof actual === "string" && actual.toLowerCase().includes(String(expected ?? "").toLowerCase()));
309
- case "starts_with":
310
- return typeof actual === "string" && actual.startsWith(String(expected ?? ""));
311
- case "ends_with":
312
- return typeof actual === "string" && actual.endsWith(String(expected ?? ""));
313
- case "gt":
314
- case "gte":
315
- case "lt":
316
- case "lte": {
317
- const a = toNumber(actual);
318
- const b = toNumber(expected);
319
- if (a === null || b === null) return false;
320
- return op === "gt" ? a > b : op === "gte" ? a >= b : op === "lt" ? a < b : a <= b;
321
- }
322
- case "in": {
323
- const list = Array.isArray(expected) ? expected : String(expected ?? "").split(",").map((s) => s.trim());
324
- return list.includes(String(actual ?? ""));
325
- }
326
- case "not_in": {
327
- const list = Array.isArray(expected) ? expected : String(expected ?? "").split(",").map((s) => s.trim());
328
- return !list.includes(String(actual ?? ""));
329
- }
330
- case "regex": {
331
- const re = safeRegExp(String(expected ?? ""));
332
- return !!re && typeof actual === "string" && re.test(actual);
333
- }
334
- case "before": {
335
- const a = toDateMs(actual);
336
- const b = toDateMs(expected);
337
- return a !== null && b !== null && a < b;
338
- }
339
- case "after": {
340
- const a = toDateMs(actual);
341
- const b = toDateMs(expected);
342
- return a !== null && b !== null && a > b;
343
- }
344
- default:
345
- return false;
346
- }
347
- }
348
- function evaluateRule(rule, ctx) {
349
- let result;
350
- if (rule.field === "url.path" || rule.field === "url.full" || rule.field === "app.screen") {
351
- const actual = String(resolveField(rule, ctx) ?? "");
352
- if (rule.op === "equals") result = matchUrlPattern(String(rule.value ?? ""), actual);
353
- else if (rule.op === "not_equals") result = !matchUrlPattern(String(rule.value ?? ""), actual);
354
- else result = compare(rule.op, actual, rule.value);
355
- } else if (rule.field === "app.version" && ["equals", "not_equals", "gt", "gte", "lt", "lte"].includes(rule.op)) {
356
- const c = compareVersions(resolveField(rule, ctx), rule.value);
357
- if (c === null) result = rule.op === "not_equals";
358
- else result = rule.op === "equals" ? c === 0 : rule.op === "not_equals" ? c !== 0 : rule.op === "gt" ? c > 0 : rule.op === "gte" ? c >= 0 : rule.op === "lt" ? c < 0 : c <= 0;
359
- } else {
360
- result = compare(rule.op, resolveField(rule, ctx), rule.value);
361
- }
362
- return rule.negate ? !result : result;
363
- }
364
- function evaluateRules(group, ctx) {
365
- if (!group || group.rules.length === 0) return group?.negate ? false : true;
366
- const results = group.rules.map((r) => isRuleGroup(r) ? evaluateRules(r, ctx) : evaluateRule(r, ctx));
367
- const value = group.op === "and" ? results.every(Boolean) : results.some(Boolean);
368
- return group.negate ? !value : value;
369
- }
370
- var SERVER_ASSISTED_FIELDS = ["audience.member", "guide.completed", "guide.dismissed", "guide.seen", "checklist.completed", "page.views", "feature.clicks"];
371
- var GUIDE_VISIBILITIES = ["everyone", "identified", "anonymous"];
372
- function withVisibility(targeting, visibility) {
373
- if (!visibility || visibility === "everyone") return targeting;
374
- return { id: "visibility", op: "and", rules: [{ id: "visibility-rule", field: "user.identified", op: visibility === "identified" ? "is_true" : "is_false" }, targeting] };
375
- }
376
- function describeRule(rule) {
377
- const subject = rule.key ? `${rule.field}[${rule.key}]` : rule.field;
378
- const v = Array.isArray(rule.value) ? rule.value.join(", ") : rule.value === void 0 ? "" : String(rule.value);
379
- return `${rule.negate ? "NOT " : ""}${subject} ${rule.op.replace(/_/g, " ")}${v ? ` "${v}"` : ""}`;
380
- }
381
-
382
- // src/contrast.ts
383
- var WCAG_AA_TEXT = 4.5;
384
- var WCAG_AA_LARGE = 3;
385
- function channel(c) {
386
- const s = c / 255;
387
- return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
388
- }
389
- function parseHexColor(hex) {
390
- if (!hex) return null;
391
- const m = hex.trim().match(/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i);
392
- if (!m) return null;
393
- const h = m[1].length === 3 ? m[1].split("").map((c) => c + c).join("") : m[1];
394
- return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
395
- }
396
- function relativeLuminance(hex) {
397
- const rgb = parseHexColor(hex);
398
- if (!rgb) return null;
399
- return 0.2126 * channel(rgb[0]) + 0.7152 * channel(rgb[1]) + 0.0722 * channel(rgb[2]);
400
- }
401
- function contrastRatio(a, b) {
402
- const la = relativeLuminance(a);
403
- const lb = relativeLuminance(b);
404
- if (la === null || lb === null) return null;
405
- const [hi, lo] = la > lb ? [la, lb] : [lb, la];
406
- return Math.round((hi + 0.05) / (lo + 0.05) * 100) / 100;
407
- }
408
- function readableTextOn(background) {
409
- const white = contrastRatio(background, "#ffffff") ?? 0;
410
- const dark = contrastRatio(background, "#111827") ?? 0;
411
- return white >= dark ? "#ffffff" : "#111827";
412
- }
413
- function contrastIssues(colors) {
414
- const issues = [];
415
- const { primaryColor, backgroundColor, textColor } = colors;
416
- if (textColor && backgroundColor) {
417
- const r = contrastRatio(textColor, backgroundColor);
418
- if (r !== null && r < WCAG_AA_TEXT) issues.push({ pair: "text", ratio: r, required: WCAG_AA_TEXT, message: `Text on the background has a contrast of ${r}:1; WCAG AA needs ${WCAG_AA_TEXT}:1.` });
419
- }
420
- if (primaryColor) {
421
- const label = contrastRatio(primaryColor, readableTextOn(primaryColor));
422
- if (label !== null && label < WCAG_AA_TEXT) issues.push({ pair: "button", ratio: label, required: WCAG_AA_TEXT, message: `Button labels on the primary colour have a contrast of ${label}:1; WCAG AA needs ${WCAG_AA_TEXT}:1.` });
423
- if (backgroundColor) {
424
- const link = contrastRatio(primaryColor, backgroundColor);
425
- if (link !== null && link < WCAG_AA_LARGE) issues.push({ pair: "link", ratio: link, required: WCAG_AA_LARGE, message: `The primary colour on the background (links, outlined buttons) has a contrast of ${link}:1; WCAG AA needs ${WCAG_AA_LARGE}:1.` });
426
- }
427
- }
428
- return issues;
429
- }
430
-
431
- // src/events-util.ts
432
- var SENSITIVE_PROPERTY_PATTERN = /(password|passwd|secret|token|credit|card|cvv|ssn|iban)/i;
433
- var MAX_PROPERTY_BYTES = 8 * 1024;
434
- function sanitizeProperties(props) {
435
- if (!props) return {};
436
- const out = {};
437
- let size = 0;
438
- for (const [k, v] of Object.entries(props)) {
439
- if (SENSITIVE_PROPERTY_PATTERN.test(k)) continue;
440
- if (typeof v === "function" || typeof v === "symbol") continue;
441
- const str2 = typeof v === "string" ? v : JSON.stringify(v ?? null);
442
- size += k.length + (str2?.length ?? 0);
443
- if (size > MAX_PROPERTY_BYTES) break;
444
- out[k.slice(0, 100)] = typeof v === "string" ? v.slice(0, 1e3) : v;
445
- }
446
- return out;
447
- }
448
-
449
- // src/errors.ts
450
- var MAX_STACK_FRAMES = 30;
451
- var V8 = /^\s*at (?:(.+?) \()?((?:[a-z][\w+.-]*:)?[^\s()]+?):(\d+):(\d+)\)?\s*$/i;
452
- var GECKO = /^\s*(.*?)@(.+?):(\d+):(\d+)\s*$/;
453
- function parseStack(stack, max = MAX_STACK_FRAMES) {
454
- if (!stack || typeof stack !== "string") return [];
455
- const frames = [];
456
- for (const raw of stack.split("\n")) {
457
- if (frames.length >= max) break;
458
- const line = raw.slice(0, 1e3);
459
- const m = V8.exec(line) ?? GECKO.exec(line);
460
- if (!m) continue;
461
- const fn = (m[1] || "").trim();
462
- frames.push({ fn: fn ? fn.slice(0, 200) : void 0, file: m[2]?.slice(0, 500), line: Number(m[3]), col: Number(m[4]) });
463
- }
464
- return frames;
465
- }
466
- function normalizeErrorMessage(message) {
467
- return (message || "").slice(0, 500).replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<uuid>").replace(/(["'`]).*?\1/g, "<str>").replace(/\b0x[0-9a-f]+\b/gi, "<hex>").replace(/\d+(\.\d+)?/g, "<n>").replace(/\s+/g, " ").trim();
468
- }
469
- function normalizeFrameFile(file) {
470
- if (!file) return "";
471
- let path = file;
472
- try {
473
- path = new URL(file).pathname;
474
- } catch {
475
- path = file.replace(/[?#].*$/, "");
476
- }
477
- return path.replace(/[.-][0-9a-f]{6,}(?=\.\w+$)/i, "").replace(/\/\d+\//g, "/<n>/");
478
- }
479
- var SOURCEMAP_MAX_FILE_BYTES = 25 * 1024 * 1024;
480
- var SOURCEMAP_MAX_FILES_PER_UPLOAD = 200;
481
- function fingerprintError(input) {
482
- const top = (input.frames ?? []).find((f) => f.file);
483
- const frameKey = input.original ? `src:${input.original.source}:${input.original.name || "?"}` : top ? `${normalizeFrameFile(top.file)}:${top.fn || "?"}` : "";
484
- const key = [input.type || "Error", normalizeErrorMessage(input.message || ""), frameKey].join("|");
485
- const a = fnv1a(key).toString(16).padStart(8, "0");
486
- const b = fnv1a(`guidely:${key}`).toString(16).padStart(8, "0");
487
- return a + b;
488
- }
489
-
490
- // src/complaints-limits.ts
491
- var COMPLAINT_STATUSES = ["open", "in_review", "resolved"];
492
- var COMPLAINT_EVENTS = ["complaint.filed", "complaint.status_changed", "complaint.replied", "complaint.resolved"];
493
- var COMPLAINT_FIELD_TYPES = ["text", "choice"];
494
- var MAX_COMPLAINT_FIELDS = 8;
495
- var MAX_COMPLAINT_CHOICES = 12;
496
- var MAX_COMPLAINT_LENGTH = 4e3;
497
- var MAX_COMPLAINT_ANSWER_LENGTH = 500;
498
- var MAX_COMPLAINT_ATTACHMENT_BYTES = 2 * 1024 * 1024;
499
- var complaintReference = (number) => `CMP-${number}`;
500
-
501
- // src/agents-limits.ts
502
- var AGENT_TURN_ROLES = ["user", "assistant", "tool", "system"];
503
- var AGENT_OUTCOMES = ["success", "failure", "escalated", "abandoned"];
504
- var AGENT_TOOL_STATUSES = ["ok", "error"];
505
- var AGENT_TURN_MAX_TEXT = 2e4;
506
- var AGENT_TURNS_PER_BATCH = 100;
507
- var AGENT_TOOL_CALLS_PER_TURN = 50;
508
- var AGENT_METADATA_MAX_KEYS = 30;
509
- var AGENT_METADATA_MAX_BYTES = 4096;
510
- var SCOUT_AGENT_ID = "scout";
511
-
512
- // src/network.ts
513
- var NETWORK_PLUGIN = "guidely/network@1";
514
- var WS_MAX_MESSAGES_PER_SOCKET = 200;
515
- var WS_MAX_MESSAGE_BYTES = 2048;
516
- var SENSITIVE_HEADER_PATTERN = /^(authorization|proxy-authorization|cookie|set-cookie|x-api-key|x-auth-token|x-csrf-token|x-xsrf-token|x-guidely-key|x-guidely-conversation)$/i;
517
- var SENSITIVE_PARAM_PATTERN = /(token|key|secret|password|passwd|pwd|auth|session|code|sig|signature|credential|ssn|card|cvv|iban)/i;
518
-
519
- // src/media.ts
520
- var MEDIA_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
521
- var MEDIA_VIDEO_TYPES = ["video/mp4", "video/webm"];
522
- var MEDIA_FILE_TYPES = ["application/pdf"];
523
- var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
524
- var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
525
- var MEDIA_FILE_MAX_BYTES = 50 * 1024 * 1024;
526
- var MEDIA_MAX_BYTES = Math.max(MEDIA_IMAGE_MAX_BYTES, MEDIA_VIDEO_MAX_BYTES, MEDIA_FILE_MAX_BYTES);
527
- function isMediaContentType(type) {
528
- return mediaKind(type) !== null;
529
- }
530
- function isVideoContentType(type) {
531
- return MEDIA_VIDEO_TYPES.includes(type);
532
- }
533
- function mediaKind(type) {
534
- if (MEDIA_IMAGE_TYPES.includes(type)) return "image";
535
- if (MEDIA_VIDEO_TYPES.includes(type)) return "video";
536
- if (MEDIA_FILE_TYPES.includes(type)) return "file";
537
- return null;
538
- }
539
- function mediaMaxBytes(type) {
540
- const kind = mediaKind(type);
541
- return kind === "video" ? MEDIA_VIDEO_MAX_BYTES : kind === "file" ? MEDIA_FILE_MAX_BYTES : MEDIA_IMAGE_MAX_BYTES;
542
- }
543
- var FILE_LINK_CLASS = "gd-file";
544
- var PDF_BLOCK_CLASS = "gd-pdf";
545
- var PDF_PAGES_CLASS = "gd-pdf-pages";
546
- var PDF_MAX_PAGES = 40;
547
- function isFileLink(className, href) {
548
- return !!className && className.split(/\s+/).includes(FILE_LINK_CLASS) && isSafeMediaUrl(href);
549
- }
550
- var YOUTUBE_ID = /^[A-Za-z0-9_-]{11}$/;
551
- var VIMEO_ID = /^\d{1,12}$/;
552
- var VIMEO_HASH = /^[0-9a-f]{6,20}$/i;
553
- var LOOM_ID = /^[0-9a-f]{32}$/i;
554
- var WISTIA_ID = /^[a-z0-9]{10}$/i;
555
- function parseUrl(input) {
556
- const s = input.trim();
557
- if (!s) return null;
558
- try {
559
- const u = new URL(/^[a-z][a-z0-9+.-]*:/i.test(s) ? s : `https://${s}`);
560
- if (u.protocol !== "https:" && u.protocol !== "http:") return null;
561
- if (u.username || u.password || u.port && u.port !== "443" && u.port !== "80") return null;
562
- return u;
563
- } catch {
564
- return null;
565
- }
566
- }
567
- function parseStart(v) {
568
- if (!v) return 0;
569
- if (/^\d+$/.test(v)) return Math.min(Number(v), 86400);
570
- const m = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/.exec(v);
571
- if (!m || !m[0]) return 0;
572
- return Math.min(Number(m[1] ?? 0) * 3600 + Number(m[2] ?? 0) * 60 + Number(m[3] ?? 0), 86400);
573
- }
574
- function youtube(id, start) {
575
- if (!YOUTUBE_ID.test(id)) return null;
576
- return {
577
- provider: "youtube",
578
- src: `https://www.youtube-nocookie.com/embed/${id}${start ? `?start=${start}` : ""}`,
579
- watchUrl: `https://www.youtube.com/watch?v=${id}${start ? `&t=${start}s` : ""}`,
580
- title: "YouTube video"
581
- };
582
- }
583
- function vimeo(id, hash) {
584
- if (!VIMEO_ID.test(id) || hash !== null && !VIMEO_HASH.test(hash)) return null;
585
- const h = hash ? hash.toLowerCase() : null;
586
- return { provider: "vimeo", src: `https://player.vimeo.com/video/${id}${h ? `?h=${h}` : ""}`, watchUrl: `https://vimeo.com/${id}${h ? `/${h}` : ""}`, title: "Vimeo video" };
587
- }
588
- function toEmbedUrl(input) {
589
- const u = parseUrl(input);
590
- if (!u) return null;
591
- const host = u.hostname.toLowerCase().replace(/^www\./, "");
592
- const parts = u.pathname.split("/").filter(Boolean);
593
- switch (host) {
594
- case "youtube.com":
595
- case "m.youtube.com":
596
- case "youtube-nocookie.com": {
597
- const start = parseStart(u.searchParams.get("start") ?? u.searchParams.get("t"));
598
- if (parts[0] === "watch" && parts.length === 1) return youtube(u.searchParams.get("v") ?? "", start);
599
- if ((parts[0] === "embed" || parts[0] === "shorts" || parts[0] === "live") && parts.length === 2) return youtube(parts[1], start);
600
- return null;
601
- }
602
- case "youtu.be":
603
- return parts.length === 1 ? youtube(parts[0], parseStart(u.searchParams.get("t") ?? u.searchParams.get("start"))) : null;
604
- case "vimeo.com":
605
- if (parts.length === 1) return vimeo(parts[0], null);
606
- if (parts.length === 2 && VIMEO_ID.test(parts[0])) return vimeo(parts[0], parts[1]);
607
- return null;
608
- case "player.vimeo.com":
609
- return parts[0] === "video" && parts.length === 2 ? vimeo(parts[1], u.searchParams.get("h")) : null;
610
- case "loom.com":
611
- if ((parts[0] === "share" || parts[0] === "embed") && parts.length === 2 && LOOM_ID.test(parts[1])) {
612
- const id = parts[1].toLowerCase();
613
- return { provider: "loom", src: `https://www.loom.com/embed/${id}`, watchUrl: `https://www.loom.com/share/${id}`, title: "Loom video" };
614
- }
615
- return null;
616
- default: {
617
- let id;
618
- if (host === "fast.wistia.net" || host === "fast.wistia.com") id = parts[0] === "embed" && parts[1] === "iframe" && parts.length === 3 ? parts[2] : void 0;
619
- else if (host === "wi.st" || host === "wistia.com" || /^[a-z0-9-]+\.wistia\.com$/.test(host)) id = parts[0] === "medias" && parts.length === 2 ? parts[1] : void 0;
620
- if (!id || !WISTIA_ID.test(id)) return null;
621
- return { provider: "wistia", src: `https://fast.wistia.net/embed/iframe/${id}`, watchUrl: `https://fast.wistia.net/embed/iframe/${id}`, title: "Wistia video" };
622
- }
623
- }
624
- }
625
- function isAllowedEmbedSrc(src) {
626
- if (!src) return false;
627
- return toEmbedUrl(src)?.src === src;
628
- }
629
- var EMBED_IFRAME_ATTRS = {
630
- allow: "fullscreen; picture-in-picture; encrypted-media",
631
- allowfullscreen: "",
632
- loading: "lazy",
633
- referrerpolicy: "strict-origin-when-cross-origin",
634
- sandbox: "allow-scripts allow-same-origin allow-presentation allow-popups allow-popups-to-escape-sandbox"
635
- };
636
- var VIDEO_ATTRS = { controls: "", preload: "metadata", playsinline: "" };
637
- function isSafeMediaUrl(url) {
638
- if (!url) return false;
639
- try {
640
- const u = new URL(url);
641
- return (u.protocol === "https:" || u.protocol === "http:") && !u.username && !u.password;
642
- } catch {
643
- return false;
644
- }
645
- }
646
-
647
- // src/resource-center.ts
648
- var MAX_COLLECTION_DEPTH = 3;
649
- function matchesAudiences(required, member) {
650
- if (!required || required.length === 0) return true;
651
- if (!member) return false;
652
- const has2 = member instanceof Set ? (id) => member.has(id) : (id) => member.includes(id);
653
- return required.some(has2);
654
- }
655
- function resourceCenterSections(collections, articles, member) {
656
- const byId = new Map(collections.map((c) => [c.id, c]));
657
- const children = /* @__PURE__ */ new Map();
658
- for (const c of collections) {
659
- const parent = c.parentId && c.parentId !== c.id && byId.has(c.parentId) ? c.parentId : null;
660
- const list = children.get(parent) ?? [];
661
- list.push(c);
662
- children.set(parent, list);
663
- }
664
- for (const list of children.values()) list.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name));
665
- const direct = /* @__PURE__ */ new Map();
666
- const uncategorized = [];
667
- for (const a of articles) {
668
- if (!matchesAudiences(a.audienceIds, member)) continue;
669
- if (a.collectionId && byId.has(a.collectionId)) {
670
- const list = direct.get(a.collectionId) ?? [];
671
- list.push(a);
672
- direct.set(a.collectionId, list);
673
- } else uncategorized.push(a);
674
- }
675
- const sections = [];
676
- const seen = /* @__PURE__ */ new Set();
677
- const visit = (c, path) => {
678
- if (seen.has(c.id)) return;
679
- seen.add(c.id);
680
- if (!matchesAudiences(c.audienceIds, member)) return;
681
- const here = [...path, c.name];
682
- const list = direct.get(c.id);
683
- if (list?.length) sections.push({ collection: c, path: here, depth: here.length - 1, articles: list });
684
- for (const child of children.get(c.id) ?? []) visit(child, here);
685
- };
686
- for (const root of children.get(null) ?? []) visit(root, []);
687
- if (uncategorized.length) sections.push({ collection: null, path: [], depth: 0, articles: uncategorized });
688
- return sections;
689
- }
690
- function isArticleListed(collections, article, member) {
691
- if (!matchesAudiences(article.audienceIds, member)) return false;
692
- const byId = new Map(collections.map((c) => [c.id, c]));
693
- let cur = article.collectionId ? byId.get(article.collectionId) : void 0;
694
- for (let guard = 0; cur && guard <= MAX_COLLECTION_DEPTH * 4; guard++) {
695
- if (!matchesAudiences(cur.audienceIds, member)) return false;
696
- cur = cur.parentId ? byId.get(cur.parentId) : void 0;
697
- }
698
- return true;
699
- }
700
- var ARTICLE_VISIBILITIES = ["everyone", "identified"];
701
- var LOGGED_IN_AUDIENCE = "$identified";
702
- function servedArticleAudiences(audienceIds, visibility) {
703
- return visibility === "identified" && audienceIds.length === 0 ? [LOGGED_IN_AUDIENCE] : [...audienceIds];
704
- }
705
- function visitorAudiences(audienceIds, identified) {
706
- const ids = audienceIds.filter((id) => id !== LOGGED_IN_AUDIENCE);
707
- return identified ? [...ids, LOGGED_IN_AUDIENCE] : ids;
708
- }
709
- function visibleArticleIds(collections, articles, member) {
710
- return new Set(resourceCenterSections(collections, articles, member).flatMap((s) => s.articles.map((a) => a.id)));
711
- }
712
- var SHOW_ME_CLASS = "gd-show-me";
713
- var SHOW_ME_HREF_PREFIX = "#guidely-show-me:";
714
- var SHOW_ME_KEY = /^[A-Za-z0-9_-]{1,64}$/;
715
- function showMeHref(guideKey) {
716
- return `${SHOW_ME_HREF_PREFIX}${guideKey}`;
717
- }
718
- function showMeKeyFromHref(href) {
719
- if (!href || !href.startsWith(SHOW_ME_HREF_PREFIX)) return null;
720
- const key = href.slice(SHOW_ME_HREF_PREFIX.length);
721
- return SHOW_ME_KEY.test(key) ? key : null;
722
- }
723
- function showMeKeys(html) {
724
- const out = [];
725
- for (const m of (html ?? "").matchAll(/href="#guidely-show-me:([A-Za-z0-9_-]{1,64})"/g)) if (!out.includes(m[1])) out.push(m[1]);
726
- return out.slice(0, 10);
727
- }
728
- function hasVideoMedia(html) {
729
- return /<(video|iframe)\b/i.test(html ?? "");
730
- }
731
- var words = (s) => s.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
732
- function matchesHelpQuery(text, query) {
733
- const q = query.trim().toLowerCase();
734
- if (!q) return true;
735
- const hay = text.toLowerCase();
736
- if (hay.includes(q)) return true;
737
- const terms = words(q);
738
- if (!terms.length) return false;
739
- const have = words(hay);
740
- return terms.every((t) => have.some((w) => w.startsWith(t) || t.length > 3 && t.endsWith("s") && w.startsWith(t.slice(0, -1))));
741
- }
742
- var RC_MODULE_IDS = ["announcements", "search", "checklists", "guides", "articles", "links", "faqs"];
743
- var RC_MODULE_LABELS = {
744
- announcements: "What's new",
745
- search: "Search",
746
- checklists: "Checklists",
747
- guides: "Guides & tours",
748
- articles: "Articles",
749
- links: "Links",
750
- faqs: "FAQs"
751
- };
752
- function orderedModules(modules) {
753
- const known = (modules ?? []).filter((m, i, all) => RC_MODULE_IDS.includes(m.id) && all.findIndex((x) => x.id === m.id) === i);
754
- const missing = RC_MODULE_IDS.filter((id) => !known.some((m) => m.id === id)).map((id) => ({ id, enabled: true }));
755
- return [...known, ...missing];
756
- }
757
- function visibleAnnouncements(items, member, now = Date.now()) {
758
- return (items ?? []).filter((a) => {
759
- const t = Date.parse(a.publishedAt);
760
- return !Number.isNaN(t) && t <= now && matchesAudiences(a.audienceIds, member);
761
- }).sort((a, b) => Date.parse(b.publishedAt) - Date.parse(a.publishedAt));
762
- }
763
- function unreadAnnouncements(items, lastSeenAt) {
764
- const seen = lastSeenAt ? Date.parse(lastSeenAt) : NaN;
765
- if (Number.isNaN(seen)) return items.length;
766
- return items.filter((a) => Date.parse(a.publishedAt) > seen).length;
767
- }
768
-
769
- // src/howto-limits.ts
770
- var CAPTURE_PARAM = "guidely_capture";
771
- var CAPTURE_SCREENSHOT_MAX_BYTES = 8 * 1024 * 1024;
772
-
773
- // src/page.ts
774
- var PATH_ID_PATTERN = "/([0-9]+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{12,})(?=[/?]|$)";
775
- function foldPathIds(path) {
776
- return path.replace(new RegExp(PATH_ID_PATTERN, "gi"), "/:id");
777
- }
778
- var GENERIC_SEGMENTS = /* @__PURE__ */ new Set(["", ":id", "app", "apps", "dashboard", "home", "index", "main", "new", "edit", "view", "show", "details", "detail", "overview", "list", "all", "page", "pages", "en", "v1", "v2"]);
779
- function segmentsOf(path) {
780
- const clean = foldPathIds((path.split(/[?#]/)[0] || "/").toLowerCase());
781
- return clean.split("/").map((s) => s.replace(/\.(html?|php|aspx?)$/, "")).filter((s) => !GENERIC_SEGMENTS.has(s) && /[a-z]/.test(s) && s.length <= 40);
782
- }
783
- function pageTerms(path) {
784
- const words2 = segmentsOf(path).flatMap((s) => s.split(/[-_.+\s]+/).filter((w) => w.length > 1 && /[a-z]/.test(w)));
785
- return [...new Set(words2)].slice(0, 8);
786
- }
787
- function pageLabel(path) {
788
- const last = segmentsOf(path).at(-1);
789
- if (!last) return null;
790
- const words2 = last.split(/[-_.+\s]+/).filter(Boolean);
791
- if (!words2.length) return null;
792
- const label = words2.join(" ");
793
- return label.charAt(0).toUpperCase() + label.slice(1);
794
- }
795
- function firstNameFromTraits(traits) {
796
- if (!traits) return null;
797
- const pick2 = (v) => typeof v === "string" ? v.trim() : "";
798
- const first = pick2(traits.firstName) || pick2(traits.first_name) || pick2(traits.firstname) || pick2(traits.name).split(/\s+/)[0] || "";
799
- if (!first || first.length > 40 || first.includes("@") || !/\p{L}/u.test(first)) return null;
800
- return first;
801
- }
802
-
803
- // src/tagging.ts
804
- var SNAPSHOT_LIMITS = { classes: 8, className: 60, attrs: 6, attrValue: 100, ancestors: 5, hosts: 3, tag: 40, id: 80 };
805
- var SNAPSHOT_ATTRS = ["role", "name", "type", "aria-label", "title", "alt", "for", "href"];
806
- var NOISY_DATA_ATTR = /^data-(state|v-|react|radix|headlessui|svelte|orientation|highlighted|focus|key|index|side|align|disabled|selected|active|open|rbd|dnd|sveltekit|guidely-(mask|ignore|block))/i;
807
- var DEFAULT_FEATURE_ATTRIBUTE = "data-guidely-feature";
808
- var FEATURE_ATTRIBUTE_PATTERN = /^data-[a-z0-9][a-z0-9_.-]{0,60}$/;
809
- function str(v, max) {
810
- return typeof v === "string" && v ? v.slice(0, max) : void 0;
811
- }
812
- function cleanNode(raw) {
813
- if (!raw || typeof raw !== "object") return null;
814
- const r = raw;
815
- const t = str(r.t, SNAPSHOT_LIMITS.tag)?.toLowerCase();
816
- if (!t || !/^[a-z][a-z0-9-]*$/.test(t)) return null;
817
- const node = { t };
818
- const id = str(r.id, SNAPSHOT_LIMITS.id);
819
- if (id) node.id = id;
820
- if (Array.isArray(r.c)) {
821
- const c = r.c.filter((x) => typeof x === "string" && !!x && x.length <= SNAPSHOT_LIMITS.className && !/\s/.test(x)).slice(0, SNAPSHOT_LIMITS.classes);
822
- if (c.length) node.c = c;
823
- }
824
- if (r.a && typeof r.a === "object" && !Array.isArray(r.a)) {
825
- const a = {};
826
- for (const [k, v] of Object.entries(r.a)) {
827
- if (Object.keys(a).length >= SNAPSHOT_LIMITS.attrs) break;
828
- const key = k.toLowerCase();
829
- if (typeof v !== "string" || !(key.startsWith("data-") || SNAPSHOT_ATTRS.includes(key)) || key === "value" || key.length > 60) continue;
830
- a[key] = v.slice(0, SNAPSHOT_LIMITS.attrValue);
831
- }
832
- if (Object.keys(a).length) node.a = a;
833
- }
834
- if (typeof r.n === "number" && Number.isInteger(r.n) && r.n >= 1 && r.n <= 1e4) node.n = r.n;
835
- return node;
836
- }
837
- function normalizeSnapshot(raw) {
838
- const el = cleanNode(raw);
839
- if (!el) return null;
840
- const r = raw;
841
- const out = el;
842
- if (Array.isArray(r.p)) {
843
- const p = r.p.slice(0, SNAPSHOT_LIMITS.ancestors).map(cleanNode);
844
- const cut = p.indexOf(null);
845
- const list = cut === -1 ? p : p.slice(0, cut);
846
- if (list.length) out.p = list;
847
- }
848
- if (Array.isArray(r.h)) {
849
- const h = r.h.slice(0, SNAPSHOT_LIMITS.hosts).map(cleanNode).filter((x) => !!x);
850
- if (h.length) out.h = h;
851
- }
852
- return out;
853
- }
854
- function stableStringify(v) {
855
- if (Array.isArray(v)) return `[${v.map(stableStringify).join(",")}]`;
856
- if (v && typeof v === "object") {
857
- const o = v;
858
- return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(o[k])}`).join(",")}}`;
859
- }
860
- return JSON.stringify(v ?? null);
861
- }
862
- var SelectorSyntaxError = class extends Error {
863
- };
864
- var IDENT_START = /[A-Za-z_\u00a0-\uffff-]/;
865
- var IDENT_CHAR = /[A-Za-z0-9_\u00a0-\uffff-]/;
866
- function parseCssSelector(input) {
867
- const s = input.trim();
868
- if (!s) throw new SelectorSyntaxError("Empty selector");
869
- if (s.length > 1e3) throw new SelectorSyntaxError("Selector is too long");
870
- let i = 0;
871
- const peek = () => s[i];
872
- const eof = () => i >= s.length;
873
- const fail = (msg) => {
874
- throw new SelectorSyntaxError(`${msg} at position ${i + 1}`);
875
- };
876
- const ws = () => {
877
- let any = false;
878
- while (!eof() && /\s/.test(s[i])) {
879
- i++;
880
- any = true;
881
- }
882
- return any;
883
- };
884
- const escape = () => {
885
- i++;
886
- const hex = /^[0-9a-fA-F]{1,6}/.exec(s.slice(i));
887
- if (hex) {
888
- i += hex[0].length;
889
- if (s[i] === " ") i++;
890
- return String.fromCodePoint(parseInt(hex[0], 16));
891
- }
892
- if (eof()) fail("Unfinished escape");
893
- return s[i++];
894
- };
895
- const ident = () => {
896
- let out = "";
897
- if (s[i] === "\\") out += escape();
898
- else if (!eof() && IDENT_START.test(s[i])) out += s[i++];
899
- else fail("Expected a name");
900
- while (!eof()) {
901
- if (s[i] === "\\") out += escape();
902
- else if (IDENT_CHAR.test(s[i])) out += s[i++];
903
- else break;
904
- }
905
- return out;
906
- };
907
- const quoted = () => {
908
- const q = s[i++];
909
- let out = "";
910
- while (!eof() && s[i] !== q) {
911
- if (s[i] === "\\") out += escape();
912
- else out += s[i++];
913
- }
914
- if (eof()) fail("Unterminated string");
915
- i++;
916
- return out;
917
- };
918
- const compound = () => {
919
- const c = { classes: [], attrs: [] };
920
- let any = false;
921
- if (peek() === "*") {
922
- i++;
923
- any = true;
924
- } else if (!eof() && (IDENT_START.test(peek()) || peek() === "\\")) {
925
- c.tag = ident().toLowerCase();
926
- any = true;
927
- }
928
- for (; ; ) {
929
- const ch = peek();
930
- if (ch === "#") {
931
- i++;
932
- c.id = ident();
933
- } else if (ch === ".") {
934
- i++;
935
- c.classes.push(ident());
936
- } else if (ch === "[") {
937
- i++;
938
- ws();
939
- const name = ident().toLowerCase();
940
- ws();
941
- let op = "exists";
942
- let value;
943
- let insensitive = false;
944
- if (peek() !== "]") {
945
- const m = /^(=|\^=|\$=|\*=|~=|\|=)/.exec(s.slice(i));
946
- if (!m) fail("Unsupported attribute operator");
947
- op = m[1];
948
- i += m[1].length;
949
- ws();
950
- value = peek() === '"' || peek() === "'" ? quoted() : ident();
951
- ws();
952
- if (peek() === "i" || peek() === "I") {
953
- i++;
954
- insensitive = true;
955
- ws();
956
- } else if (peek() === "s" || peek() === "S") {
957
- i++;
958
- ws();
959
- }
960
- }
961
- if (peek() !== "]") fail("Expected ]");
962
- i++;
963
- c.attrs.push({ name, op, ...value !== void 0 ? { value } : {}, ...insensitive ? { insensitive } : {} });
964
- } else if (ch === ":") {
965
- i++;
966
- if (peek() === ":") fail("Pseudo-elements are not supported");
967
- const name = ident().toLowerCase();
968
- if (name === "first-of-type") c.nth = { a: 0, b: 1 };
969
- else if (name === "nth-of-type") {
970
- if (peek() !== "(") fail("Expected (");
971
- i++;
972
- ws();
973
- const m = /^(odd|even|\d+)/i.exec(s.slice(i));
974
- if (!m) fail("Only :nth-of-type(n), odd or even are supported");
975
- i += m[0].length;
976
- ws();
977
- if (peek() !== ")") fail("Expected )");
978
- i++;
979
- const v = m[0].toLowerCase();
980
- c.nth = v === "odd" ? { a: 2, b: 1 } : v === "even" ? { a: 2, b: 0 } : { a: 0, b: Number(v) };
981
- } else fail(`Unsupported pseudo-class :${name}`);
982
- } else break;
983
- any = true;
984
- }
985
- return any ? c : null;
986
- };
987
- const list = [];
988
- for (; ; ) {
989
- ws();
990
- const sel = { compounds: [], combinators: [] };
991
- const first = compound();
992
- if (!first) fail("Expected a selector");
993
- sel.compounds.push(first);
994
- for (; ; ) {
995
- const hadWs = ws();
996
- if (eof() || peek() === ",") break;
997
- let comb = " ";
998
- if (s.startsWith(">>>", i)) {
999
- comb = ">>>";
1000
- i += 3;
1001
- ws();
1002
- } else if (peek() === ">") {
1003
- comb = ">";
1004
- i++;
1005
- ws();
1006
- } else if (peek() === "+" || peek() === "~") fail("Sibling combinators are not supported");
1007
- else if (!hadWs) fail("Unexpected character");
1008
- const next = compound();
1009
- if (!next) fail("Expected a selector after the combinator");
1010
- sel.compounds.push(next);
1011
- sel.combinators.push(comb);
1012
- }
1013
- list.push(sel);
1014
- if (eof()) break;
1015
- i++;
1016
- }
1017
- return list;
1018
- }
1019
- function cssSelectorError(selector) {
1020
- try {
1021
- parseCssSelector(selector);
1022
- return null;
1023
- } catch (e) {
1024
- return e instanceof Error ? e.message : "Invalid selector";
1025
- }
1026
- }
1027
- function attrMatches(test, node) {
1028
- let actual;
1029
- if (test.name === "id") actual = node.id;
1030
- else if (test.name === "class") actual = node.c?.join(" ");
1031
- else actual = node.a?.[test.name];
1032
- if (actual === void 0) return false;
1033
- if (test.op === "exists") return true;
1034
- let v = test.value ?? "";
1035
- if (test.insensitive) {
1036
- actual = actual.toLowerCase();
1037
- v = v.toLowerCase();
1038
- }
1039
- switch (test.op) {
1040
- case "=":
1041
- return actual === v;
1042
- case "^=":
1043
- return !!v && actual.startsWith(v);
1044
- case "$=":
1045
- return !!v && actual.endsWith(v);
1046
- case "*=":
1047
- return !!v && actual.includes(v);
1048
- case "~=":
1049
- return !!v && actual.split(/\s+/).includes(v);
1050
- case "|=":
1051
- return actual === v || actual.startsWith(`${v}-`);
1052
- default:
1053
- return false;
1054
- }
1055
- }
1056
- function compoundMatches(c, node) {
1057
- if (c.tag && c.tag !== node.t) return false;
1058
- if (c.id !== void 0 && node.id !== c.id) return false;
1059
- for (const cls of c.classes) if (!node.c?.includes(cls)) return false;
1060
- for (const a of c.attrs) if (!attrMatches(a, node)) return false;
1061
- if (c.nth) {
1062
- if (!node.n) return false;
1063
- const { a, b } = c.nth;
1064
- if (a === 0 ? node.n !== b : (node.n - b) % a !== 0 || (node.n - b) / a < 0) return false;
1065
- }
1066
- return true;
1067
- }
1068
- function chainMatches(compounds, combinators, nodes, ci = compounds.length - 1, ni = 0) {
1069
- if (!compoundMatches(compounds[ci], nodes[ni])) return false;
1070
- if (ci === 0) return true;
1071
- const comb = combinators[ci - 1];
1072
- if (comb === ">") return ni + 1 < nodes.length && chainMatches(compounds, combinators, nodes, ci - 1, ni + 1);
1073
- for (let k = ni + 1; k < nodes.length; k++) if (chainMatches(compounds, combinators, nodes, ci - 1, k)) return true;
1074
- return false;
1075
- }
1076
- function complexMatches(sel, snap) {
1077
- const segments = [{ compounds: [sel.compounds[0]], combinators: [] }];
1078
- sel.combinators.forEach((comb, k) => {
1079
- const next = sel.compounds[k + 1];
1080
- if (comb === ">>>") segments.push({ compounds: [next], combinators: [] });
1081
- else {
1082
- const seg = segments[segments.length - 1];
1083
- seg.compounds.push(next);
1084
- seg.combinators.push(comb);
1085
- }
1086
- });
1087
- const last = segments.pop();
1088
- const nodes = [snap, ...snap.p ?? []];
1089
- if (!chainMatches(last.compounds, last.combinators, nodes)) return false;
1090
- const hosts = snap.h ?? [];
1091
- let from = 0;
1092
- for (let k = segments.length - 1; k >= 0; k--) {
1093
- const target = segments[k].compounds[segments[k].compounds.length - 1];
1094
- let found = -1;
1095
- for (let j = from; j < hosts.length; j++) {
1096
- if (compoundMatches(target, hosts[j])) {
1097
- found = j;
1098
- break;
1099
- }
1100
- }
1101
- if (found === -1) return false;
1102
- from = found + 1;
1103
- }
1104
- return true;
1105
- }
1106
- var parsedCache = /* @__PURE__ */ new Map();
1107
- function matchCssSelector(selector, snap) {
1108
- let list;
1109
- if (typeof selector === "string") {
1110
- if (!parsedCache.has(selector)) {
1111
- if (parsedCache.size > 500) parsedCache.clear();
1112
- try {
1113
- parsedCache.set(selector, parseCssSelector(selector));
1114
- } catch {
1115
- parsedCache.set(selector, null);
1116
- }
1117
- }
1118
- list = parsedCache.get(selector);
1119
- } else list = selector;
1120
- return !!list && list.some((sel) => complexMatches(sel, snap));
1121
- }
1122
- function nodeOfCompound(c) {
1123
- const node = { t: c.tag ?? "*" };
1124
- if (c.id) node.id = c.id;
1125
- if (c.classes.length) node.c = c.classes.slice(0, SNAPSHOT_LIMITS.classes);
1126
- const a = {};
1127
- for (const t of c.attrs) if (t.op === "=" && t.value !== void 0) a[t.name] = t.value;
1128
- if (Object.keys(a).length) node.a = a;
1129
- if (c.nth && c.nth.a === 0) node.n = c.nth.b;
1130
- return node;
1131
- }
1132
- function snapshotFromSelector(selector, tag) {
1133
- let list;
1134
- try {
1135
- list = parseCssSelector(selector);
1136
- } catch {
1137
- return null;
1138
- }
1139
- if (list.length !== 1) return null;
1140
- const sel = list[0];
1141
- const segments = [[sel.compounds[0]]];
1142
- sel.combinators.forEach((comb, k) => {
1143
- if (comb === ">>>") segments.push([sel.compounds[k + 1]]);
1144
- else segments[segments.length - 1].push(sel.compounds[k + 1]);
1145
- });
1146
- const own = segments.pop();
1147
- const el = nodeOfCompound(own[own.length - 1]);
1148
- if (el.t === "*" && tag && /^[a-z][a-z0-9-]*$/i.test(tag)) el.t = tag.toLowerCase();
1149
- const snap = el;
1150
- const p = own.slice(0, -1).reverse().map(nodeOfCompound);
1151
- if (p.length) snap.p = p.slice(0, SNAPSHOT_LIMITS.ancestors);
1152
- const h = segments.reverse().map((seg) => nodeOfCompound(seg[seg.length - 1]));
1153
- if (h.length) snap.h = h.slice(0, SNAPSHOT_LIMITS.hosts);
1154
- return snap;
1155
- }
1156
- var PAGE_RULE_LIMITS = { rules: 20, value: 500, query: 5 };
1157
- var escapeRe = (s) => s.replace(/[.+?^${}()|[\]\\*]/g, "\\$&");
1158
- function globToRegex(pattern) {
1159
- let out = "";
1160
- for (let k = 0; k < pattern.length; k++) {
1161
- const ch = pattern[k];
1162
- if (ch === "*") {
1163
- if (pattern[k + 1] === "*") {
1164
- out += ".*";
1165
- k++;
1166
- } else out += "[^/]*";
1167
- } else if (ch === ":" && (k === 0 || pattern[k - 1] === "/") && /[A-Za-z_]/.test(pattern[k + 1] ?? "")) {
1168
- let j = k + 1;
1169
- while (j < pattern.length && /[\w]/.test(pattern[j])) j++;
1170
- out += "[^/]+";
1171
- k = j - 1;
1172
- } else out += escapeRe(ch);
1173
- }
1174
- return out;
1175
- }
1176
- function pagePattern(raw) {
1177
- let p = raw.trim();
1178
- const query = [];
1179
- const hash = p.indexOf("#");
1180
- if (hash !== -1) p = p.slice(0, hash);
1181
- const q = p.indexOf("?");
1182
- if (q !== -1) {
1183
- for (const part of p.slice(q + 1).split("&")) {
1184
- if (!part) continue;
1185
- const eq = part.indexOf("=");
1186
- const name = decodeSafe(eq === -1 ? part : part.slice(0, eq));
1187
- const value = eq === -1 ? void 0 : decodeSafe(part.slice(eq + 1));
1188
- if (!name) continue;
1189
- query.push(value === void 0 || value === "*" ? { name, op: "exists" } : { name, op: "equals", value });
1190
- }
1191
- p = p.slice(0, q);
1192
- }
1193
- let target = "path";
1194
- if (p.startsWith("//*")) p = p.slice(3);
1195
- let prefix = "";
1196
- const abs = /^(https?:)?\/\/([^/]+)(.*)$/i.exec(p);
1197
- if (abs) {
1198
- target = "url";
1199
- prefix = `https?://${globToRegex(abs[2].toLowerCase())}`;
1200
- p = abs[3] || "/";
1201
- }
1202
- if (!p.startsWith("/")) p = `/${p}`;
1203
- const body = p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
1204
- const src = body === "/" ? "/?" : `${globToRegex(body)}/?`;
1205
- return { source: `^${prefix}${src}$`, target, query: query.slice(0, PAGE_RULE_LIMITS.query) };
1206
- }
1207
- function decodeSafe(s) {
1208
- try {
1209
- return decodeURIComponent(s.replace(/\+/g, " "));
1210
- } catch {
1211
- return s;
1212
- }
1213
- }
1214
- function queryConditionRegex(c) {
1215
- const name = escapeRe(c.name);
1216
- switch (c.op) {
1217
- case "equals":
1218
- return `[?&]${name}=${escapeRe(encodeLoose(c.value ?? ""))}(&|#|$)`;
1219
- case "contains":
1220
- return `[?&]${name}=[^&#]*${escapeRe(encodeLoose(c.value ?? ""))}`;
1221
- default:
1222
- return `[?&]${name}(=|&|#|$)`;
1223
- }
1224
- }
1225
- function encodeLoose(v) {
1226
- return encodeURIComponent(v).replace(/%2F/gi, "/").replace(/%3A/gi, ":").replace(/%2C/gi, ",").replace(/%40/gi, "@");
1227
- }
1228
- function pathOnly(ctx) {
1229
- if (ctx.path) return ctx.path.split("?")[0] || "/";
1230
- if (!ctx.url) return null;
1231
- try {
1232
- return new URL(ctx.url, "http://x.invalid").pathname;
1233
- } catch {
1234
- return null;
1235
- }
1236
- }
1237
- function regexOrNull(source) {
1238
- try {
1239
- return new RegExp(source, "i");
1240
- } catch {
1241
- return null;
1242
- }
1243
- }
1244
- function pageRuleMatches(rule, ctx) {
1245
- const path = pathOnly(ctx);
1246
- if (rule.type === "title") {
1247
- const t = (ctx.title ?? "").toLowerCase();
1248
- const v = rule.value.toLowerCase();
1249
- return !!t && (rule.op === "contains" ? t.includes(v) : t === v);
1250
- }
1251
- if (rule.type === "regex") {
1252
- const re2 = regexOrNull(rule.value);
1253
- return !!re2 && path !== null && re2.test(path);
1254
- }
1255
- const pat = pagePattern(rule.value);
1256
- const re = regexOrNull(pat.source);
1257
- if (!re) return false;
1258
- let subject = path;
1259
- if (pat.target === "url") {
1260
- if (!ctx.url) return false;
1261
- subject = ctx.url.split(/[?#]/)[0];
1262
- }
1263
- if (subject === null || !re.test(subject)) return false;
1264
- const conds = [...pat.query, ...rule.query ?? []];
1265
- for (const c of conds) {
1266
- const hit = !!ctx.url && new RegExp(queryConditionRegex(c), "i").test(ctx.url);
1267
- if (c.op === "absent" ? hit : !hit) return false;
1268
- }
1269
- return true;
1270
- }
1271
- function pageRulesMatch(rules, ctx) {
1272
- const include = rules.filter((r) => !r.exclude);
1273
- if (!include.length || !include.some((r) => pageRuleMatches(r, ctx))) return false;
1274
- return !rules.some((r) => r.exclude && pageRuleMatches(r, ctx));
1275
- }
1276
- function pageRuleError(rule) {
1277
- const v = rule.value?.trim();
1278
- if (!v) return "A rule needs a value";
1279
- if (v.length > PAGE_RULE_LIMITS.value) return "The rule is too long";
1280
- if (rule.type === "regex") {
1281
- if (!regexOrNull(v)) return `"${v}" is not a valid regular expression`;
1282
- if (/\(\?[<=!]/.test(v) || /\\[1-9]/.test(v)) return "Look-arounds and back-references are not supported";
1283
- }
1284
- if (rule.type === "url" && !regexOrNull(pagePattern(v).source)) return `"${v}" is not a valid URL rule`;
1285
- for (const q of rule.query ?? []) if (!/^[A-Za-z0-9_.\-[\]]{1,60}$/.test(q.name)) return `"${q.name}" is not a query parameter name`;
1286
- return null;
1287
- }
1288
- var FEATURE_RULE_LIMITS = { rules: 20, value: 1e3 };
1289
- function globMatch(pattern, value) {
1290
- const re = new RegExp(`^${pattern.split("*").map(escapeRe).join(".*")}$`, "s");
1291
- return re.test(value);
1292
- }
1293
- function featureRuleMatchesClick(rule, click, css) {
1294
- if (!rule.selector && !rule.text && !rule.tag && !rule.href) return false;
1295
- if (rule.selector) {
1296
- const sel = click.selector ?? "";
1297
- const v = rule.selector.value;
1298
- const ok = rule.selector.op === "css" ? css(v) : rule.selector.op === "exact" ? sel === v : rule.selector.op === "prefix" ? sel.startsWith(v) : rule.selector.op === "contains" ? sel.includes(v) : globMatch(v, sel);
1299
- if (!ok) return false;
1300
- }
1301
- if (rule.text) {
1302
- const t = (click.text ?? "").toLowerCase();
1303
- const v = rule.text.value.toLowerCase();
1304
- if (rule.text.op === "contains" ? !t.includes(v) : t !== v) return false;
1305
- }
1306
- if (rule.tag && (click.tag ?? "").toLowerCase() !== rule.tag.toLowerCase()) return false;
1307
- if (rule.href) {
1308
- const h = click.href ?? "";
1309
- const v = rule.href.value;
1310
- if (rule.href.op === "equals" ? h !== v : rule.href.op === "prefix" ? !h.startsWith(v) : !h.includes(v)) return false;
1311
- }
1312
- if (rule.platform && (click.platform ?? "web") !== rule.platform) return false;
1313
- return true;
1314
- }
1315
- function featureRuleError(rule) {
1316
- if (!rule.selector && !rule.text && !rule.tag && !rule.href) return "A feature rule needs a selector, text, tag or link condition";
1317
- if (rule.selector) {
1318
- const v = rule.selector.value?.trim();
1319
- if (!v) return "The selector is empty";
1320
- if (v.length > FEATURE_RULE_LIMITS.value) return "The selector is too long";
1321
- if (rule.selector.op === "css") {
1322
- const err = cssSelectorError(v);
1323
- if (err) return `Unsupported CSS selector: ${err}`;
1324
- }
1325
- }
1326
- if (rule.text && !rule.text.value.trim()) return "The text condition is empty";
1327
- if (rule.href && !rule.href.value.trim()) return "The link condition is empty";
1328
- if (rule.tag && !/^[a-z][a-z0-9-]{0,40}$/i.test(rule.tag)) return "The tag is not an element name";
1329
- if (rule.page?.pattern) return pageRuleError({ type: "url", value: rule.page.pattern });
1330
- return null;
1331
- }
1332
- function featureCssSelectors(rules) {
1333
- const out = [];
1334
- for (const r of rules) {
1335
- if (r.exclude || !r.selector) continue;
1336
- if ((r.selector.op === "css" || r.selector.op === "exact") && !cssSelectorError(r.selector.value)) out.push(r.selector.value);
1337
- }
1338
- return out;
1339
- }
1340
- var TAG_PROPERTY_LIMITS = { perTag: 10, total: 50, value: 100 };
1341
- function propertyAttributeAllowed(name) {
1342
- const n = name.toLowerCase();
1343
- if (n === "value" || n.startsWith("on") || n === "style" || n === "srcdoc") return false;
1344
- return /^[a-z][a-z0-9_:.-]{0,60}$/.test(n);
1345
- }
1346
-
1347
- // src/localization.ts
1348
- var DEFAULT_LOCALE = "en";
1349
- var COMMON_LOCALES = [
1350
- { code: "en", name: "English" },
1351
- { code: "en-GB", name: "English (UK)" },
1352
- { code: "fr", name: "French" },
1353
- { code: "fr-CA", name: "French (Canada)" },
1354
- { code: "de", name: "German" },
1355
- { code: "es", name: "Spanish" },
1356
- { code: "es-MX", name: "Spanish (Mexico)" },
1357
- { code: "it", name: "Italian" },
1358
- { code: "pt", name: "Portuguese" },
1359
- { code: "pt-BR", name: "Portuguese (Brazil)" },
1360
- { code: "nl", name: "Dutch" },
1361
- { code: "sv", name: "Swedish" },
1362
- { code: "da", name: "Danish" },
1363
- { code: "nb", name: "Norwegian" },
1364
- { code: "fi", name: "Finnish" },
1365
- { code: "pl", name: "Polish" },
1366
- { code: "cs", name: "Czech" },
1367
- { code: "tr", name: "Turkish" },
1368
- { code: "ru", name: "Russian" },
1369
- { code: "uk", name: "Ukrainian" },
1370
- { code: "ar", name: "Arabic" },
1371
- { code: "he", name: "Hebrew" },
1372
- { code: "hi", name: "Hindi" },
1373
- { code: "ja", name: "Japanese" },
1374
- { code: "ko", name: "Korean" },
1375
- { code: "zh-Hans", name: "Chinese (Simplified)" },
1376
- { code: "zh-Hant", name: "Chinese (Traditional)" },
1377
- { code: "id", name: "Indonesian" },
1378
- { code: "th", name: "Thai" },
1379
- { code: "vi", name: "Vietnamese" }
1380
- ];
1381
- function localeName(code) {
1382
- const exact = COMMON_LOCALES.find((l) => l.code.toLowerCase() === code.toLowerCase());
1383
- if (exact) return exact.name;
1384
- const base = COMMON_LOCALES.find((l) => l.code.toLowerCase() === baseLanguage(code));
1385
- return base ? `${base.name} (${code})` : code;
1386
- }
1387
- function normalizeLocale(input) {
1388
- if (typeof input !== "string") return "";
1389
- const parts = input.trim().replace(/_/g, "-").split("-").filter(Boolean);
1390
- if (!parts.length || !/^[a-zA-Z]{2,3}$/.test(parts[0])) return "";
1391
- return parts.slice(0, 3).map((p, i) => i === 0 ? p.toLowerCase() : p.length === 4 ? p[0].toUpperCase() + p.slice(1).toLowerCase() : p.length === 2 ? p.toUpperCase() : p).join("-");
1392
- }
1393
- function baseLanguage(locale) {
1394
- return normalizeLocale(locale).split("-")[0] ?? "";
1395
- }
1396
- function resolveLocale(requested, available, defaultLocale = DEFAULT_LOCALE) {
1397
- const def = normalizeLocale(defaultLocale) || DEFAULT_LOCALE;
1398
- const avail = available.map(normalizeLocale).filter(Boolean);
1399
- const all = [def, ...avail.filter((a) => a.toLowerCase() !== def.toLowerCase())];
1400
- for (const raw of requested) {
1401
- const want = normalizeLocale(raw);
1402
- if (!want) continue;
1403
- const exact = all.find((a) => a.toLowerCase() === want.toLowerCase());
1404
- if (exact) return exact;
1405
- const base = baseLanguage(want);
1406
- const sameBase = all.find((a) => a.toLowerCase() === base) ?? all.find((a) => baseLanguage(a) === base);
1407
- if (sameBase) return sameBase;
1408
- }
1409
- return def;
1410
- }
1411
- function visitorLocales(explicit, traits, platform = []) {
1412
- const out = [];
1413
- const push = (v) => {
1414
- const n = normalizeLocale(v);
1415
- if (n && !out.includes(n)) out.push(n);
1416
- };
1417
- push(explicit);
1418
- push(traits?.language);
1419
- push(traits?.locale);
1420
- for (const p of platform) push(p);
1421
- return out;
1422
- }
1423
- function guideTextEntries(config) {
1424
- const out = [];
1425
- const add = (key, source, kind, label) => {
1426
- if (source && source.trim()) out.push({ key, source, kind, label });
1427
- };
1428
- config.steps.forEach((s, i) => {
1429
- const at = `Step ${i + 1}`;
1430
- add(`steps.${s.id}.title`, s.title, "text", `${at} \xB7 Title`);
1431
- add(`steps.${s.id}.body`, s.body, "html", `${at} \xB7 Body`);
1432
- add(`steps.${s.id}.customHtml`, s.customHtml, "html", `${at} \xB7 Custom HTML`);
1433
- add(`steps.${s.id}.imageAlt`, s.imageAlt, "text", `${at} \xB7 Image description`);
1434
- s.buttons.forEach((b) => add(`steps.${s.id}.buttons.${b.id}.label`, b.label, "text", `${at} \xB7 Button \u201C${b.label}\u201D`));
1435
- });
1436
- const c = config.checklist;
1437
- if (c) {
1438
- add("checklist.launcherLabel", c.launcherLabel, "text", "Checklist \xB7 Launcher label");
1439
- add("checklist.completionTitle", c.completionTitle, "text", "Checklist \xB7 Completion title");
1440
- add("checklist.completionBody", c.completionBody, "html", "Checklist \xB7 Completion message");
1441
- c.tasks.forEach((t, i) => {
1442
- add(`checklist.tasks.${t.id}.title`, t.title, "text", `Task ${i + 1} \xB7 Title`);
1443
- add(`checklist.tasks.${t.id}.description`, t.description, "html", `Task ${i + 1} \xB7 Description`);
1444
- });
1445
- }
1446
- const sv = config.survey;
1447
- if (sv) {
1448
- sv.questions.forEach((q, i) => {
1449
- const at = `Question ${i + 1}`;
1450
- add(`survey.questions.${q.id}.title`, q.title, "text", `${at} \xB7 Question`);
1451
- add(`survey.questions.${q.id}.description`, q.description, "text", `${at} \xB7 Description`);
1452
- add(`survey.questions.${q.id}.placeholder`, q.placeholder, "text", `${at} \xB7 Placeholder`);
1453
- add(`survey.questions.${q.id}.lowLabel`, q.lowLabel, "text", `${at} \xB7 Low label`);
1454
- add(`survey.questions.${q.id}.highLabel`, q.highLabel, "text", `${at} \xB7 High label`);
1455
- q.options.forEach((o) => add(`survey.questions.${q.id}.options.${o.id}.label`, o.label, "text", `${at} \xB7 Option \u201C${o.label}\u201D`));
1456
- });
1457
- add("survey.thankYouTitle", sv.thankYouTitle, "text", "Survey \xB7 Thank-you title");
1458
- add("survey.thankYouBody", sv.thankYouBody, "html", "Survey \xB7 Thank-you message");
1459
- }
1460
- return out;
1461
- }
1462
- function guideLocales(config) {
1463
- const t = config.localization?.translations ?? {};
1464
- return Object.keys(t).filter((l) => Object.values(t[l] ?? {}).some((v) => typeof v === "string" && v.trim() !== ""));
1465
- }
1466
- function pick(map, key, fallback) {
1467
- const v = map?.[key];
1468
- return typeof v === "string" && v.trim() !== "" ? v : fallback;
1469
- }
1470
- function applyGuideTranslations(config, map) {
1471
- if (!map || !Object.keys(map).length) return config;
1472
- return {
1473
- ...config,
1474
- steps: config.steps.map((s) => ({
1475
- ...s,
1476
- title: pick(map, `steps.${s.id}.title`, s.title),
1477
- body: pick(map, `steps.${s.id}.body`, s.body),
1478
- customHtml: pick(map, `steps.${s.id}.customHtml`, s.customHtml),
1479
- imageAlt: pick(map, `steps.${s.id}.imageAlt`, s.imageAlt),
1480
- buttons: s.buttons.map((b) => ({ ...b, label: pick(map, `steps.${s.id}.buttons.${b.id}.label`, b.label) }))
1481
- })),
1482
- checklist: config.checklist ? {
1483
- ...config.checklist,
1484
- launcherLabel: pick(map, "checklist.launcherLabel", config.checklist.launcherLabel),
1485
- completionTitle: pick(map, "checklist.completionTitle", config.checklist.completionTitle),
1486
- completionBody: pick(map, "checklist.completionBody", config.checklist.completionBody),
1487
- tasks: config.checklist.tasks.map((t) => ({ ...t, title: pick(map, `checklist.tasks.${t.id}.title`, t.title), description: pick(map, `checklist.tasks.${t.id}.description`, t.description) }))
1488
- } : config.checklist,
1489
- survey: config.survey ? {
1490
- ...config.survey,
1491
- thankYouTitle: pick(map, "survey.thankYouTitle", config.survey.thankYouTitle),
1492
- thankYouBody: pick(map, "survey.thankYouBody", config.survey.thankYouBody),
1493
- questions: config.survey.questions.map((q) => ({
1494
- ...q,
1495
- title: pick(map, `survey.questions.${q.id}.title`, q.title),
1496
- description: pick(map, `survey.questions.${q.id}.description`, q.description),
1497
- placeholder: pick(map, `survey.questions.${q.id}.placeholder`, q.placeholder),
1498
- lowLabel: pick(map, `survey.questions.${q.id}.lowLabel`, q.lowLabel),
1499
- highLabel: pick(map, `survey.questions.${q.id}.highLabel`, q.highLabel),
1500
- options: q.options.map((o) => ({ ...o, label: pick(map, `survey.questions.${q.id}.options.${o.id}.label`, o.label) }))
1501
- }))
1502
- } : config.survey
1503
- };
1504
- }
1505
- function localizeGuide(guide, requested, defaultLocale = DEFAULT_LOCALE) {
1506
- const translations = guide.config.localization?.translations ?? {};
1507
- const locale = resolveLocale(requested, guideLocales(guide.config), defaultLocale);
1508
- const key = Object.keys(translations).find((l) => normalizeLocale(l).toLowerCase() === locale.toLowerCase());
1509
- if (!key) return { ...guide, locale };
1510
- return { ...guide, locale, config: applyGuideTranslations(guide.config, translations[key]) };
1511
- }
1512
- function translationCoverage(entries, map, locale) {
1513
- const keys = new Set(entries.map((e) => e.key));
1514
- const missing = entries.filter((e) => !pick(map, e.key, void 0)).map((e) => e.key);
1515
- const stale = Object.keys(map ?? {}).filter((k) => !keys.has(k) && (map?.[k] ?? "").trim() !== "");
1516
- return { locale, total: entries.length, translated: entries.length - missing.length, missing, stale };
1517
- }
1518
- function resourceCenterTextEntries(rc) {
1519
- const out = [];
1520
- const add = (key, source, kind, label) => {
1521
- if (source && source.trim()) out.push({ key, source, kind, label });
1522
- };
1523
- add("title", rc.title, "text", "Title");
1524
- add("launcher.label", rc.launcher?.label, "text", "Launcher label");
1525
- rc.collections.forEach((c) => {
1526
- add(`collections.${c.id}.name`, c.name, "text", `Collection \u201C${c.name}\u201D`);
1527
- add(`collections.${c.id}.description`, c.description, "text", `Collection \u201C${c.name}\u201D \xB7 Description`);
1528
- });
1529
- rc.links.forEach((l) => add(`links.${l.id}.label`, l.label, "text", `Link \u201C${l.label}\u201D`));
1530
- rc.faqs.forEach((f, i) => {
1531
- add(`faqs.${f.id}.question`, f.question, "text", `FAQ ${i + 1} \xB7 Question`);
1532
- add(`faqs.${f.id}.answer`, f.answer, "html", `FAQ ${i + 1} \xB7 Answer`);
1533
- });
1534
- return out;
1535
- }
1536
- function articleTextEntries(a) {
1537
- const out = [];
1538
- if (a.title.trim()) out.push({ key: "title", source: a.title, kind: "text", label: "Title" });
1539
- if (a.summary?.trim()) out.push({ key: "summary", source: a.summary, kind: "text", label: "Summary" });
1540
- if (a.body?.trim()) out.push({ key: "body", source: a.body, kind: "html", label: "Body" });
1541
- return out;
1542
- }
1543
- function resourceCenterLocales(rc) {
1544
- const set = new Set(Object.keys(rc.translations ?? {}));
1545
- for (const a of rc.articles) for (const l of Object.keys(a.translations ?? {})) set.add(l);
1546
- for (const a of rc.announcements ?? []) for (const l of Object.keys(a.translations ?? {})) set.add(l);
1547
- return [...set];
1548
- }
1549
- function translationFor(map, locale) {
1550
- if (!map) return void 0;
1551
- const key = Object.keys(map).find((l) => normalizeLocale(l).toLowerCase() === locale.toLowerCase());
1552
- return key ? map[key] : void 0;
1553
- }
1554
- function localizeArticle(a, locale) {
1555
- const t = translationFor(a.translations, locale);
1556
- if (!t) return a;
1557
- const body = t.body?.trim() ? t.body : a.body;
1558
- return { ...a, title: t.title?.trim() ? t.title : a.title, summary: t.summary?.trim() ? t.summary : a.summary, body, ...t.bodyOmitted ? { bodyOmitted: true } : {} };
1559
- }
1560
- function localizeAnnouncement(a, locale) {
1561
- const t = translationFor(a.translations, locale);
1562
- if (!t) return a;
1563
- return { ...a, title: t.title?.trim() ? t.title : a.title, body: t.body?.trim() ? t.body : a.body };
1564
- }
1565
- function localizeResourceCenter(rc, requested, defaultLocale = DEFAULT_LOCALE) {
1566
- const locale = resolveLocale(requested, resourceCenterLocales(rc), defaultLocale);
1567
- if (locale.toLowerCase() === normalizeLocale(defaultLocale).toLowerCase()) return { ...rc, locale };
1568
- const map = translationFor(rc.translations, locale);
1569
- return {
1570
- ...rc,
1571
- locale,
1572
- title: pick(map, "title", rc.title),
1573
- launcher: { ...rc.launcher, label: pick(map, "launcher.label", rc.launcher.label) },
1574
- collections: rc.collections.map((c) => ({ ...c, name: pick(map, `collections.${c.id}.name`, c.name), description: pick(map, `collections.${c.id}.description`, c.description) })),
1575
- links: rc.links.map((l) => ({ ...l, label: pick(map, `links.${l.id}.label`, l.label) })),
1576
- faqs: rc.faqs.map((f) => ({ ...f, question: pick(map, `faqs.${f.id}.question`, f.question), answer: pick(map, `faqs.${f.id}.answer`, f.answer) })),
1577
- articles: rc.articles.map((a) => localizeArticle(a, locale)),
1578
- announcements: rc.announcements?.map((a) => localizeAnnouncement(a, locale))
1579
- };
1580
- }
1581
-
1582
- // src/guide-experiments.ts
1583
- var CONTROL_VARIANT_ID = "control";
1584
- var GUIDE_EXPERIMENT_STATUSES = ["draft", "running", "completed"];
1585
- var GUIDE_EXPERIMENT_GOALS = ["guide_completed", "event", "page_visit", "click_text"];
1586
- var GUIDE_EXPERIMENT_GOAL_LABELS = {
1587
- guide_completed: "Completes the guide",
1588
- event: "Tracks an event",
1589
- page_visit: "Visits a page",
1590
- click_text: "Clicks an element with text"
1591
- };
1592
- function experimentSalt(experimentId) {
1593
- return `gx:${experimentId}`;
1594
- }
1595
- function variantRanges(variants) {
1596
- let at = 0;
1597
- return variants.map((v) => {
1598
- const from = at;
1599
- at = Math.min(100, at + Math.max(0, Math.round(v.weight)));
1600
- return { id: v.id, from, to: at };
1601
- });
1602
- }
1603
- function assignGuideVariant(variants, experimentId, subject) {
1604
- const bucket = rolloutBucket(subject, experimentSalt(experimentId));
1605
- return variantRanges(variants).find((r) => bucket >= r.from && bucket < r.to)?.id ?? null;
1606
- }
1607
- function experimentBucketRules(salt, from, to) {
1608
- return [
1609
- { id: "experiment-from", field: "experiment.bucket", key: salt, op: "gte", value: from },
1610
- { id: "experiment-to", field: "experiment.bucket", key: salt, op: "lt", value: to }
1611
- ];
1612
- }
1613
- function withExperimentBucket(targeting, salt, from, to) {
1614
- return { id: "experiment", op: "and", rules: [...experimentBucketRules(salt, from, to), targeting] };
1615
- }
1616
- function variantProblems(variants) {
1617
- const problems = [];
1618
- if (variants.length < 2) problems.push("An A/B test needs at least two variants.");
1619
- if (variants.length > 5) problems.push("An A/B test can have at most five variants.");
1620
- const guides = variants.filter((v) => v.guideId);
1621
- if (guides.length === 0) problems.push("At least one variant must show a guide.");
1622
- if (variants.filter((v) => !v.guideId).length > 1) problems.push("Only one variant can be the control group (no guide).");
1623
- if (new Set(guides.map((v) => v.guideId)).size !== guides.length) problems.push("Each variant needs its own guide.");
1624
- if (new Set(variants.map((v) => v.id)).size !== variants.length) problems.push("Variant ids must be unique.");
1625
- if (variants.some((v) => !Number.isInteger(v.weight) || v.weight < 1)) problems.push("Every variant needs a whole-number share of at least 1%.");
1626
- const total = variants.reduce((s, v) => s + v.weight, 0);
1627
- if (total !== 100) problems.push(`Traffic shares must add up to 100% (now ${total}%).`);
1628
- return problems;
1629
- }
1630
- function evenWeights(n) {
1631
- if (n <= 0) return [];
1632
- const base = Math.floor(100 / n);
1633
- return Array.from({ length: n }, (_, i) => base + (i < 100 - base * n ? 1 : 0));
1634
- }
1635
-
1636
- // src/custom-css.ts
1637
- var MAX_CUSTOM_CSS = 2e4;
1638
- var BLOCKED_AT_RULES = /^@(import|charset|namespace|document|-moz-document)\b/i;
1639
- var ALLOWED_AT_RULES = /^@(media|supports|container|layer|keyframes|-webkit-keyframes|font-face)\b/i;
1640
- function splitBlocks(css) {
1641
- const out = [];
1642
- let i = 0;
1643
- const n = css.length;
1644
- while (i < n) {
1645
- let prelude = "";
1646
- let quote = null;
1647
- while (i < n) {
1648
- const ch = css[i];
1649
- if (quote) {
1650
- prelude += ch;
1651
- if (ch === "\\") prelude += css[++i] ?? "";
1652
- else if (ch === quote) quote = null;
1653
- } else if (ch === '"' || ch === "'") {
1654
- quote = ch;
1655
- prelude += ch;
1656
- } else if (ch === "{") break;
1657
- else if (ch === ";") {
1658
- prelude = "";
1659
- } else if (ch === "}") {
1660
- prelude = "";
1661
- } else prelude += ch;
1662
- i++;
1663
- }
1664
- if (i >= n) break;
1665
- i++;
1666
- let depth = 1;
1667
- let body = "";
1668
- quote = null;
1669
- while (i < n && depth > 0) {
1670
- const ch = css[i];
1671
- if (quote) {
1672
- body += ch;
1673
- if (ch === "\\") body += css[++i] ?? "";
1674
- else if (ch === quote) quote = null;
1675
- } else if (ch === '"' || ch === "'") {
1676
- quote = ch;
1677
- body += ch;
1678
- } else if (ch === "{") {
1679
- depth++;
1680
- body += ch;
1681
- } else if (ch === "}") {
1682
- depth--;
1683
- if (depth > 0) body += ch;
1684
- } else body += ch;
1685
- i++;
1686
- }
1687
- out.push({ prelude: prelude.trim(), body });
1688
- }
1689
- return out;
1690
- }
1691
- function splitSelectors(list) {
1692
- const out = [];
1693
- let depth = 0;
1694
- let cur = "";
1695
- for (const ch of list) {
1696
- if (ch === "(" || ch === "[") depth++;
1697
- else if (ch === ")" || ch === "]") depth = Math.max(0, depth - 1);
1698
- if (ch === "," && depth === 0) {
1699
- out.push(cur.trim());
1700
- cur = "";
1701
- } else cur += ch;
1702
- }
1703
- if (cur.trim()) out.push(cur.trim());
1704
- return out;
1705
- }
1706
- function safeUrl(raw) {
1707
- const u = raw.trim().replace(/^['"]|['"]$/g, "").trim();
1708
- return /^https:\/\//i.test(u) || /^data:image\/(png|jpe?g|gif|webp|svg\+xml)[;,]/i.test(u);
1709
- }
1710
- function cleanDeclarations(body) {
1711
- return body.replace(/url\(([^)]*)\)/gi, (m, inner) => safeUrl(inner) ? m : "none").replace(/expression\s*\(/gi, "(").replace(/javascript\s*:/gi, "").replace(/(^|[;{\s])(-moz-binding|behavior)\s*:[^;]*;?/gi, "$1").replace(/[{}]/g, "").trim();
1712
- }
1713
- function sanitizeCustomCss(input) {
1714
- if (!input) return "";
1715
- let css = String(input).slice(0, MAX_CUSTOM_CSS);
1716
- css = css.replace(/\/\*[\s\S]*?\*\//g, "").replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, "").replace(/<\/?[a-z!][^>]*>/gi, "").replace(/</g, "");
1717
- return render(splitBlocks(css), 0);
1718
- }
1719
- function render(blocks, depth) {
1720
- const parts = [];
1721
- for (const b of blocks) {
1722
- if (!b.prelude) continue;
1723
- if (b.prelude.startsWith("@")) {
1724
- if (BLOCKED_AT_RULES.test(b.prelude) || !ALLOWED_AT_RULES.test(b.prelude)) continue;
1725
- if (/^@(-webkit-)?keyframes|^@font-face/i.test(b.prelude)) {
1726
- const inner = /^@font-face/i.test(b.prelude) ? cleanDeclarations(b.body) : splitBlocks(b.body).map((k) => `${k.prelude}{${cleanDeclarations(k.body)}}`).join("");
1727
- parts.push(`${b.prelude}{${inner}}`);
1728
- } else if (depth < 3) parts.push(`${b.prelude}{${render(splitBlocks(b.body), depth + 1)}}`);
1729
- continue;
1730
- }
1731
- const selectors = splitSelectors(b.prelude).filter((s) => !/:host|::slotted|::part/i.test(s));
1732
- if (!selectors.length) continue;
1733
- parts.push(`${selectors.join(",")}{${cleanDeclarations(b.body)}}`);
1734
- }
1735
- return parts.join("\n");
1736
- }
1737
- function scopeCustomCss(css, scope) {
1738
- return scopeBlocks(splitBlocks(sanitizeCustomCss(css)), scope, 0);
1739
- }
1740
- function scopeBlocks(blocks, scope, depth) {
1741
- const parts = [];
1742
- for (const b of blocks) {
1743
- if (b.prelude.startsWith("@")) {
1744
- if (/^@(-webkit-)?keyframes|^@font-face/i.test(b.prelude)) parts.push(`${b.prelude}{${b.body}}`);
1745
- else if (depth < 3) parts.push(`${b.prelude}{${scopeBlocks(splitBlocks(b.body), scope, depth + 1)}}`);
1746
- continue;
1747
- }
1748
- const sels = splitSelectors(b.prelude).map((s) => {
1749
- const self = s.match(/^(:root|html|body|:scope|&)(?![\w-])(.*)$/i);
1750
- if (self) return `${scope}${self[2] ?? ""}`;
1751
- return `${scope} ${s}`;
1752
- });
1753
- parts.push(`${sels.join(",")}{${b.body}}`);
1754
- }
1755
- return parts.join("\n");
1756
- }
1757
-
1758
- export {
1759
- fnv1a,
1760
- rolloutBucket,
1761
- slugify,
1762
- randomId,
1763
- isPlainObject,
1764
- getPath,
1765
- clamp,
1766
- matchUrlPattern,
1767
- stripOrigin,
1768
- safeRegExp,
1769
- PLATFORMS,
1770
- DEFAULT_PLATFORMS,
1771
- guidePlatforms,
1772
- supportsPlatform,
1773
- GUIDELY_ID_ATTRIBUTE,
1774
- guidelyIdFromSelector,
1775
- screenPath,
1776
- compareVersions,
1777
- isQuestionVisible,
1778
- isChecklistComplete,
1779
- RULE_OPERATORS,
1780
- RULE_FIELDS,
1781
- isRuleGroup,
1782
- emptyRuleGroup,
1783
- compare,
1784
- evaluateRule,
1785
- evaluateRules,
1786
- SERVER_ASSISTED_FIELDS,
1787
- GUIDE_VISIBILITIES,
1788
- withVisibility,
1789
- describeRule,
1790
- WCAG_AA_TEXT,
1791
- WCAG_AA_LARGE,
1792
- parseHexColor,
1793
- relativeLuminance,
1794
- contrastRatio,
1795
- readableTextOn,
1796
- contrastIssues,
1797
- SENSITIVE_PROPERTY_PATTERN,
1798
- MAX_PROPERTY_BYTES,
1799
- sanitizeProperties,
1800
- MAX_STACK_FRAMES,
1801
- parseStack,
1802
- normalizeErrorMessage,
1803
- normalizeFrameFile,
1804
- SOURCEMAP_MAX_FILE_BYTES,
1805
- SOURCEMAP_MAX_FILES_PER_UPLOAD,
1806
- fingerprintError,
1807
- COMPLAINT_STATUSES,
1808
- COMPLAINT_EVENTS,
1809
- COMPLAINT_FIELD_TYPES,
1810
- MAX_COMPLAINT_FIELDS,
1811
- MAX_COMPLAINT_CHOICES,
1812
- MAX_COMPLAINT_LENGTH,
1813
- MAX_COMPLAINT_ANSWER_LENGTH,
1814
- MAX_COMPLAINT_ATTACHMENT_BYTES,
1815
- complaintReference,
1816
- AGENT_TURN_ROLES,
1817
- AGENT_OUTCOMES,
1818
- AGENT_TOOL_STATUSES,
1819
- AGENT_TURN_MAX_TEXT,
1820
- AGENT_TURNS_PER_BATCH,
1821
- AGENT_TOOL_CALLS_PER_TURN,
1822
- AGENT_METADATA_MAX_KEYS,
1823
- AGENT_METADATA_MAX_BYTES,
1824
- SCOUT_AGENT_ID,
1825
- NETWORK_PLUGIN,
1826
- WS_MAX_MESSAGES_PER_SOCKET,
1827
- WS_MAX_MESSAGE_BYTES,
1828
- SENSITIVE_HEADER_PATTERN,
1829
- SENSITIVE_PARAM_PATTERN,
1830
- MEDIA_IMAGE_TYPES,
1831
- MEDIA_VIDEO_TYPES,
1832
- MEDIA_FILE_TYPES,
1833
- MEDIA_IMAGE_MAX_BYTES,
1834
- MEDIA_VIDEO_MAX_BYTES,
1835
- MEDIA_FILE_MAX_BYTES,
1836
- MEDIA_MAX_BYTES,
1837
- isMediaContentType,
1838
- isVideoContentType,
1839
- mediaKind,
1840
- mediaMaxBytes,
1841
- FILE_LINK_CLASS,
1842
- PDF_BLOCK_CLASS,
1843
- PDF_PAGES_CLASS,
1844
- PDF_MAX_PAGES,
1845
- isFileLink,
1846
- toEmbedUrl,
1847
- isAllowedEmbedSrc,
1848
- EMBED_IFRAME_ATTRS,
1849
- VIDEO_ATTRS,
1850
- isSafeMediaUrl,
1851
- MAX_COLLECTION_DEPTH,
1852
- matchesAudiences,
1853
- resourceCenterSections,
1854
- isArticleListed,
1855
- ARTICLE_VISIBILITIES,
1856
- LOGGED_IN_AUDIENCE,
1857
- servedArticleAudiences,
1858
- visitorAudiences,
1859
- visibleArticleIds,
1860
- SHOW_ME_CLASS,
1861
- SHOW_ME_HREF_PREFIX,
1862
- showMeHref,
1863
- showMeKeyFromHref,
1864
- showMeKeys,
1865
- hasVideoMedia,
1866
- matchesHelpQuery,
1867
- RC_MODULE_IDS,
1868
- RC_MODULE_LABELS,
1869
- orderedModules,
1870
- visibleAnnouncements,
1871
- unreadAnnouncements,
1872
- CAPTURE_PARAM,
1873
- CAPTURE_SCREENSHOT_MAX_BYTES,
1874
- PATH_ID_PATTERN,
1875
- foldPathIds,
1876
- pageTerms,
1877
- pageLabel,
1878
- firstNameFromTraits,
1879
- SNAPSHOT_LIMITS,
1880
- SNAPSHOT_ATTRS,
1881
- NOISY_DATA_ATTR,
1882
- DEFAULT_FEATURE_ATTRIBUTE,
1883
- FEATURE_ATTRIBUTE_PATTERN,
1884
- normalizeSnapshot,
1885
- stableStringify,
1886
- SelectorSyntaxError,
1887
- parseCssSelector,
1888
- cssSelectorError,
1889
- compoundMatches,
1890
- matchCssSelector,
1891
- snapshotFromSelector,
1892
- PAGE_RULE_LIMITS,
1893
- pagePattern,
1894
- queryConditionRegex,
1895
- pageRuleMatches,
1896
- pageRulesMatch,
1897
- pageRuleError,
1898
- FEATURE_RULE_LIMITS,
1899
- featureRuleMatchesClick,
1900
- featureRuleError,
1901
- featureCssSelectors,
1902
- TAG_PROPERTY_LIMITS,
1903
- propertyAttributeAllowed,
1904
- DEFAULT_LOCALE,
1905
- COMMON_LOCALES,
1906
- localeName,
1907
- normalizeLocale,
1908
- baseLanguage,
1909
- resolveLocale,
1910
- visitorLocales,
1911
- guideTextEntries,
1912
- guideLocales,
1913
- applyGuideTranslations,
1914
- localizeGuide,
1915
- translationCoverage,
1916
- resourceCenterTextEntries,
1917
- articleTextEntries,
1918
- resourceCenterLocales,
1919
- localizeArticle,
1920
- localizeAnnouncement,
1921
- localizeResourceCenter,
1922
- CONTROL_VARIANT_ID,
1923
- GUIDE_EXPERIMENT_STATUSES,
1924
- GUIDE_EXPERIMENT_GOALS,
1925
- GUIDE_EXPERIMENT_GOAL_LABELS,
1926
- experimentSalt,
1927
- variantRanges,
1928
- assignGuideVariant,
1929
- experimentBucketRules,
1930
- withExperimentBucket,
1931
- variantProblems,
1932
- evenWeights,
1933
- MAX_CUSTOM_CSS,
1934
- sanitizeCustomCss,
1935
- scopeCustomCss
1936
- };
1937
- //# sourceMappingURL=chunk-TGIUEYXM.js.map