@guidelytech/shared 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/runtime.d.ts CHANGED
@@ -266,4 +266,92 @@ declare const SENSITIVE_HEADER_PATTERN: RegExp;
266
266
  /** Query parameter and JSON field names whose values are redacted. */
267
267
  declare const SENSITIVE_PARAM_PATTERN: RegExp;
268
268
 
269
- export { DEFAULT_PLATFORMS, type ExceptionProperties, GUIDELY_ID_ATTRIBUTE, MAX_PROPERTY_BYTES, MAX_STACK_FRAMES, NETWORK_PLUGIN, type NetworkPluginPayload, type NetworkRequest, type NetworkRequestType, PLATFORMS, type Platform, RULE_FIELDS, RULE_OPERATORS, type Rule, type RuleField, type RuleGroup, type RuleOperator, SENSITIVE_HEADER_PATTERN, SENSITIVE_PARAM_PATTERN, SENSITIVE_PROPERTY_PATTERN, SERVER_ASSISTED_FIELDS, SOURCEMAP_MAX_FILES_PER_UPLOAD, SOURCEMAP_MAX_FILE_BYTES, type ShowWhen, type StackFrame, type SurveyAnswer, type SurveyAnswers, type TargetingContext, WS_MAX_MESSAGES_PER_SOCKET, WS_MAX_MESSAGE_BYTES, type WebSocketMessage, clamp, compare, compareVersions, describeRule, emptyRuleGroup, evaluateRule, evaluateRules, fingerprintError, fnv1a, getPath, guidePlatforms, guidelyIdFromSelector, isChecklistComplete, isPlainObject, isQuestionVisible, isRuleGroup, matchUrlPattern, normalizeErrorMessage, normalizeFrameFile, parseStack, randomId, rolloutBucket, safeRegExp, sanitizeProperties, screenPath, slugify, stripOrigin, supportsPlatform };
269
+ /**
270
+ * Media in article bodies: uploaded images and videos, and video embeds from a fixed set of hosts.
271
+ * Sanitizers (server, browser runtime) only keep an `<iframe>` whose `src` is the canonical embed URL
272
+ * that `toEmbedUrl` produces, so authors can never point a frame anywhere else.
273
+ */
274
+ type EmbedProvider = "youtube" | "vimeo" | "loom" | "wistia";
275
+ interface VideoEmbed {
276
+ provider: EmbedProvider;
277
+ /** Canonical player URL, safe to use as an iframe `src`. */
278
+ src: string;
279
+ /** Page a viewer can open instead (native SDKs show a link rather than a player). */
280
+ watchUrl: string;
281
+ /** Accessible iframe title. */
282
+ title: string;
283
+ }
284
+ declare const MEDIA_IMAGE_TYPES: readonly ["image/png", "image/jpeg", "image/gif", "image/webp"];
285
+ declare const MEDIA_VIDEO_TYPES: readonly ["video/mp4", "video/webm"];
286
+ type MediaContentType = (typeof MEDIA_IMAGE_TYPES)[number] | (typeof MEDIA_VIDEO_TYPES)[number];
287
+ declare const MEDIA_IMAGE_MAX_BYTES: number;
288
+ declare const MEDIA_VIDEO_MAX_BYTES: number;
289
+ declare function isMediaContentType(type: string): type is MediaContentType;
290
+ declare function isVideoContentType(type: string): boolean;
291
+ declare function mediaMaxBytes(type: string): number;
292
+ /**
293
+ * Turns a pasted video page or player URL from YouTube, Vimeo, Loom or Wistia into its canonical
294
+ * embed. Returns null for anything else, including lookalike hosts.
295
+ */
296
+ declare function toEmbedUrl(input: string): VideoEmbed | null;
297
+ /** True only for a canonical embed URL (exactly what `toEmbedUrl` returns), so an iframe can't be pointed elsewhere. */
298
+ declare function isAllowedEmbedSrc(src: string | null | undefined): boolean;
299
+ /** Fixed iframe attributes for an embed; sanitizers overwrite whatever the author's HTML carried. */
300
+ declare const EMBED_IFRAME_ATTRS: Readonly<Record<string, string>>;
301
+ /** Fixed `<video>` attributes: player controls, no autoplay or loop. */
302
+ declare const VIDEO_ATTRS: Readonly<Record<string, string>>;
303
+ /** `<video src>` / `poster` must be plain http(s): no data:, blob: or relative URLs (bodies render on customer sites). */
304
+ declare function isSafeMediaUrl(url: string | null | undefined): boolean;
305
+
306
+ /**
307
+ * Resource center structure shared by every SDK: nested collections and audience targeting.
308
+ *
309
+ * Audience targeting decides what the help widget lists; it is not access control. The published
310
+ * config (and every article body) is the same for all visitors of an environment, and end-user
311
+ * identities are self-asserted, so never rely on it to keep an article private.
312
+ */
313
+ /** Collections nest at most this deep (a collection, its sections, and their sub-sections). */
314
+ declare const MAX_COLLECTION_DEPTH = 3;
315
+ interface RcCollectionLike {
316
+ id: string;
317
+ name: string;
318
+ order: number;
319
+ /** Parent collection; null/absent for a top-level collection. */
320
+ parentId?: string | null;
321
+ /** Shown only to members of any of these audiences; empty/absent means everyone. */
322
+ audienceIds?: string[];
323
+ }
324
+ interface RcArticleLike {
325
+ id: string;
326
+ collectionId?: string | null;
327
+ audienceIds?: string[];
328
+ }
329
+ interface RcSection<C extends RcCollectionLike, A extends RcArticleLike> {
330
+ /** null for articles outside any (known) collection. */
331
+ collection: C | null;
332
+ /** Collection names from the top level down, e.g. ["Billing", "Invoices"]. */
333
+ path: string[];
334
+ /** 0 for a top-level collection. */
335
+ depth: number;
336
+ articles: A[];
337
+ }
338
+ type Membership = ReadonlySet<string> | readonly string[] | undefined | null;
339
+ /** True when an item targeted at `required` audiences is shown to someone in `member`. */
340
+ declare function matchesAudiences(required: readonly string[] | undefined | null, member: Membership): boolean;
341
+ /**
342
+ * Article sections in display order: each collection (depth first, siblings by `order`) with the
343
+ * visible articles filed directly in it, then the uncategorized articles. A collection the viewer
344
+ * isn't targeted by hides its whole subtree, and sections without visible articles are left out
345
+ * (their sub-collections still appear). Unknown parents count as top level, so an old config or a
346
+ * dangling reference never hides content.
347
+ */
348
+ declare function resourceCenterSections<C extends RcCollectionLike, A extends RcArticleLike>(collections: readonly C[], articles: readonly A[], member?: Membership): Array<RcSection<C, A>>;
349
+ /**
350
+ * Whether one article is listed for a viewer: its own audiences and those of every collection above
351
+ * it must match. For articles outside a loaded list (e.g. server search results).
352
+ */
353
+ declare function isArticleListed(collections: readonly RcCollectionLike[], article: RcArticleLike, member?: Membership): boolean;
354
+ /** Ids of the articles a viewer sees (for filtering server search results the same way). */
355
+ declare function visibleArticleIds(collections: readonly RcCollectionLike[], articles: readonly RcArticleLike[], member?: Membership): Set<string>;
356
+
357
+ export { DEFAULT_PLATFORMS, EMBED_IFRAME_ATTRS, type EmbedProvider, type ExceptionProperties, GUIDELY_ID_ATTRIBUTE, MAX_COLLECTION_DEPTH, MAX_PROPERTY_BYTES, MAX_STACK_FRAMES, MEDIA_IMAGE_MAX_BYTES, MEDIA_IMAGE_TYPES, MEDIA_VIDEO_MAX_BYTES, MEDIA_VIDEO_TYPES, type MediaContentType, NETWORK_PLUGIN, type NetworkPluginPayload, type NetworkRequest, type NetworkRequestType, PLATFORMS, type Platform, RULE_FIELDS, RULE_OPERATORS, type RcArticleLike, type RcCollectionLike, type RcSection, type Rule, type RuleField, type RuleGroup, type RuleOperator, SENSITIVE_HEADER_PATTERN, SENSITIVE_PARAM_PATTERN, SENSITIVE_PROPERTY_PATTERN, SERVER_ASSISTED_FIELDS, SOURCEMAP_MAX_FILES_PER_UPLOAD, SOURCEMAP_MAX_FILE_BYTES, type ShowWhen, type StackFrame, type SurveyAnswer, type SurveyAnswers, type TargetingContext, VIDEO_ATTRS, type VideoEmbed, WS_MAX_MESSAGES_PER_SOCKET, WS_MAX_MESSAGE_BYTES, type WebSocketMessage, clamp, compare, compareVersions, describeRule, emptyRuleGroup, evaluateRule, evaluateRules, fingerprintError, fnv1a, getPath, guidePlatforms, guidelyIdFromSelector, isAllowedEmbedSrc, isArticleListed, isChecklistComplete, isMediaContentType, isPlainObject, isQuestionVisible, isRuleGroup, isSafeMediaUrl, isVideoContentType, matchUrlPattern, matchesAudiences, mediaMaxBytes, normalizeErrorMessage, normalizeFrameFile, parseStack, randomId, resourceCenterSections, rolloutBucket, safeRegExp, sanitizeProperties, screenPath, slugify, stripOrigin, supportsPlatform, toEmbedUrl, visibleArticleIds };
package/dist/runtime.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import {
2
2
  DEFAULT_PLATFORMS,
3
+ EMBED_IFRAME_ATTRS,
3
4
  GUIDELY_ID_ATTRIBUTE,
5
+ MAX_COLLECTION_DEPTH,
4
6
  MAX_PROPERTY_BYTES,
5
7
  MAX_STACK_FRAMES,
8
+ MEDIA_IMAGE_MAX_BYTES,
9
+ MEDIA_IMAGE_TYPES,
10
+ MEDIA_VIDEO_MAX_BYTES,
11
+ MEDIA_VIDEO_TYPES,
6
12
  NETWORK_PLUGIN,
7
13
  PLATFORMS,
8
14
  RULE_FIELDS,
@@ -13,6 +19,7 @@ import {
13
19
  SERVER_ASSISTED_FIELDS,
14
20
  SOURCEMAP_MAX_FILES_PER_UPLOAD,
15
21
  SOURCEMAP_MAX_FILE_BYTES,
22
+ VIDEO_ATTRS,
16
23
  WS_MAX_MESSAGES_PER_SOCKET,
17
24
  WS_MAX_MESSAGE_BYTES,
18
25
  clamp,
@@ -27,28 +34,44 @@ import {
27
34
  getPath,
28
35
  guidePlatforms,
29
36
  guidelyIdFromSelector,
37
+ isAllowedEmbedSrc,
38
+ isArticleListed,
30
39
  isChecklistComplete,
40
+ isMediaContentType,
31
41
  isPlainObject,
32
42
  isQuestionVisible,
33
43
  isRuleGroup,
44
+ isSafeMediaUrl,
45
+ isVideoContentType,
34
46
  matchUrlPattern,
47
+ matchesAudiences,
48
+ mediaMaxBytes,
35
49
  normalizeErrorMessage,
36
50
  normalizeFrameFile,
37
51
  parseStack,
38
52
  randomId,
53
+ resourceCenterSections,
39
54
  rolloutBucket,
40
55
  safeRegExp,
41
56
  sanitizeProperties,
42
57
  screenPath,
43
58
  slugify,
44
59
  stripOrigin,
45
- supportsPlatform
46
- } from "./chunk-QUKMOTT4.js";
60
+ supportsPlatform,
61
+ toEmbedUrl,
62
+ visibleArticleIds
63
+ } from "./chunk-5IQVBZO6.js";
47
64
  export {
48
65
  DEFAULT_PLATFORMS,
66
+ EMBED_IFRAME_ATTRS,
49
67
  GUIDELY_ID_ATTRIBUTE,
68
+ MAX_COLLECTION_DEPTH,
50
69
  MAX_PROPERTY_BYTES,
51
70
  MAX_STACK_FRAMES,
71
+ MEDIA_IMAGE_MAX_BYTES,
72
+ MEDIA_IMAGE_TYPES,
73
+ MEDIA_VIDEO_MAX_BYTES,
74
+ MEDIA_VIDEO_TYPES,
52
75
  NETWORK_PLUGIN,
53
76
  PLATFORMS,
54
77
  RULE_FIELDS,
@@ -59,6 +82,7 @@ export {
59
82
  SERVER_ASSISTED_FIELDS,
60
83
  SOURCEMAP_MAX_FILES_PER_UPLOAD,
61
84
  SOURCEMAP_MAX_FILE_BYTES,
85
+ VIDEO_ATTRS,
62
86
  WS_MAX_MESSAGES_PER_SOCKET,
63
87
  WS_MAX_MESSAGE_BYTES,
64
88
  clamp,
@@ -73,21 +97,31 @@ export {
73
97
  getPath,
74
98
  guidePlatforms,
75
99
  guidelyIdFromSelector,
100
+ isAllowedEmbedSrc,
101
+ isArticleListed,
76
102
  isChecklistComplete,
103
+ isMediaContentType,
77
104
  isPlainObject,
78
105
  isQuestionVisible,
79
106
  isRuleGroup,
107
+ isSafeMediaUrl,
108
+ isVideoContentType,
80
109
  matchUrlPattern,
110
+ matchesAudiences,
111
+ mediaMaxBytes,
81
112
  normalizeErrorMessage,
82
113
  normalizeFrameFile,
83
114
  parseStack,
84
115
  randomId,
116
+ resourceCenterSections,
85
117
  rolloutBucket,
86
118
  safeRegExp,
87
119
  sanitizeProperties,
88
120
  screenPath,
89
121
  slugify,
90
122
  stripOrigin,
91
- supportsPlatform
123
+ supportsPlatform,
124
+ toEmbedUrl,
125
+ visibleArticleIds
92
126
  };
93
127
  //# sourceMappingURL=runtime.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guidelytech/shared",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared schemas, types and pure utilities for Guidely",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,322 +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/targeting-eval.ts
71
- var RULE_OPERATORS = [
72
- "equals",
73
- "not_equals",
74
- "contains",
75
- "not_contains",
76
- "starts_with",
77
- "ends_with",
78
- "exists",
79
- "not_exists",
80
- "gt",
81
- "gte",
82
- "lt",
83
- "lte",
84
- "in",
85
- "not_in",
86
- "regex",
87
- "is_true",
88
- "is_false",
89
- "before",
90
- "after"
91
- ];
92
- var RULE_FIELDS = [
93
- "user.attribute",
94
- "account.attribute",
95
- "user.role",
96
- "user.plan",
97
- "url.path",
98
- "url.full",
99
- "url.query",
100
- "device.type",
101
- "device.browser",
102
- "device.os",
103
- "visitor.firstVisit",
104
- "visitor.returning",
105
- "event.seen",
106
- "guide.completed",
107
- "guide.dismissed",
108
- "guide.seen",
109
- "checklist.completed",
110
- "audience.member",
111
- "datetime.now",
112
- "datetime.dayOfWeek",
113
- "datetime.hour",
114
- "rollout.percentage",
115
- "environment",
116
- "locale"
117
- ];
118
- function isRuleGroup(r) {
119
- return r.rules !== void 0;
120
- }
121
- function emptyRuleGroup(id = "root") {
122
- return { id, op: "and", rules: [] };
123
- }
124
- function has(set, v) {
125
- if (!set) return false;
126
- return set instanceof Set ? set.has(v) : set.includes(v);
127
- }
128
- function resolveField(rule, ctx) {
129
- switch (rule.field) {
130
- case "user.attribute":
131
- return rule.key ? getPath(ctx.userAttributes, rule.key) : void 0;
132
- case "account.attribute":
133
- return rule.key ? getPath(ctx.accountAttributes, rule.key) : void 0;
134
- case "user.role":
135
- return ctx.userAttributes.role;
136
- case "user.plan":
137
- return ctx.userAttributes.plan ?? ctx.accountAttributes.plan;
138
- case "url.path":
139
- return ctx.path;
140
- case "url.full":
141
- return ctx.url;
142
- case "url.query":
143
- return rule.key ? ctx.query[rule.key] : void 0;
144
- case "device.type":
145
- return ctx.device.type;
146
- case "device.browser":
147
- return ctx.device.browser;
148
- case "device.os":
149
- return ctx.device.os;
150
- case "visitor.firstVisit":
151
- return ctx.firstVisit;
152
- case "visitor.returning":
153
- return !ctx.firstVisit;
154
- case "event.seen":
155
- return rule.key ? has(ctx.eventsSeen, rule.key) : false;
156
- case "guide.completed":
157
- return rule.key ? has(ctx.completedGuides, rule.key) : false;
158
- case "guide.dismissed":
159
- return rule.key ? has(ctx.dismissedGuides, rule.key) : false;
160
- case "guide.seen":
161
- return rule.key ? has(ctx.seenGuides, rule.key) : false;
162
- case "checklist.completed":
163
- return rule.key ? has(ctx.completedChecklists, rule.key) : false;
164
- case "audience.member":
165
- return rule.key ? has(ctx.audienceIds, rule.key) : false;
166
- case "datetime.now":
167
- return ctx.now.toISOString();
168
- case "datetime.dayOfWeek":
169
- return ctx.now.getDay();
170
- case "datetime.hour":
171
- return ctx.now.getHours();
172
- case "rollout.percentage":
173
- return rolloutBucket(ctx.userId || ctx.anonymousId, ctx.rolloutSalt || "guidely");
174
- case "environment":
175
- return ctx.environment;
176
- case "locale":
177
- return ctx.locale;
178
- default:
179
- return void 0;
180
- }
181
- }
182
- function toNumber(v) {
183
- if (typeof v === "number") return v;
184
- if (typeof v === "string" && v.trim() !== "" && !Number.isNaN(Number(v))) return Number(v);
185
- if (v instanceof Date) return v.getTime();
186
- return null;
187
- }
188
- function toDateMs(v) {
189
- if (v instanceof Date) return v.getTime();
190
- if (typeof v === "string") {
191
- const t = Date.parse(v);
192
- return Number.isNaN(t) ? null : t;
193
- }
194
- if (typeof v === "number") return v;
195
- return null;
196
- }
197
- function compare(op, actual, expected) {
198
- switch (op) {
199
- case "exists":
200
- return actual !== void 0 && actual !== null && actual !== "";
201
- case "not_exists":
202
- return actual === void 0 || actual === null || actual === "";
203
- case "is_true":
204
- return actual === true || actual === "true";
205
- case "is_false":
206
- return actual === false || actual === "false";
207
- case "equals":
208
- if (typeof actual === "boolean") return String(actual) === String(expected);
209
- return String(actual ?? "") === String(expected ?? "");
210
- case "not_equals":
211
- return String(actual ?? "") !== String(expected ?? "");
212
- case "contains":
213
- return typeof actual === "string" && actual.toLowerCase().includes(String(expected ?? "").toLowerCase());
214
- case "not_contains":
215
- return !(typeof actual === "string" && actual.toLowerCase().includes(String(expected ?? "").toLowerCase()));
216
- case "starts_with":
217
- return typeof actual === "string" && actual.startsWith(String(expected ?? ""));
218
- case "ends_with":
219
- return typeof actual === "string" && actual.endsWith(String(expected ?? ""));
220
- case "gt":
221
- case "gte":
222
- case "lt":
223
- case "lte": {
224
- const a = toNumber(actual);
225
- const b = toNumber(expected);
226
- if (a === null || b === null) return false;
227
- return op === "gt" ? a > b : op === "gte" ? a >= b : op === "lt" ? a < b : a <= b;
228
- }
229
- case "in": {
230
- const list = Array.isArray(expected) ? expected : String(expected ?? "").split(",").map((s) => s.trim());
231
- return list.includes(String(actual ?? ""));
232
- }
233
- case "not_in": {
234
- const list = Array.isArray(expected) ? expected : String(expected ?? "").split(",").map((s) => s.trim());
235
- return !list.includes(String(actual ?? ""));
236
- }
237
- case "regex": {
238
- const re = safeRegExp(String(expected ?? ""));
239
- return !!re && typeof actual === "string" && re.test(actual);
240
- }
241
- case "before": {
242
- const a = toDateMs(actual);
243
- const b = toDateMs(expected);
244
- return a !== null && b !== null && a < b;
245
- }
246
- case "after": {
247
- const a = toDateMs(actual);
248
- const b = toDateMs(expected);
249
- return a !== null && b !== null && a > b;
250
- }
251
- default:
252
- return false;
253
- }
254
- }
255
- function evaluateRule(rule, ctx) {
256
- let result;
257
- if (rule.field === "url.path" || rule.field === "url.full") {
258
- const actual = String(resolveField(rule, ctx) ?? "");
259
- if (rule.op === "equals") result = matchUrlPattern(String(rule.value ?? ""), actual);
260
- else if (rule.op === "not_equals") result = !matchUrlPattern(String(rule.value ?? ""), actual);
261
- else result = compare(rule.op, actual, rule.value);
262
- } else {
263
- result = compare(rule.op, resolveField(rule, ctx), rule.value);
264
- }
265
- return rule.negate ? !result : result;
266
- }
267
- function evaluateRules(group, ctx) {
268
- if (!group || group.rules.length === 0) return group?.negate ? false : true;
269
- const results = group.rules.map((r) => isRuleGroup(r) ? evaluateRules(r, ctx) : evaluateRule(r, ctx));
270
- const value = group.op === "and" ? results.every(Boolean) : results.some(Boolean);
271
- return group.negate ? !value : value;
272
- }
273
- var SERVER_ASSISTED_FIELDS = ["audience.member", "guide.completed", "guide.dismissed", "guide.seen", "checklist.completed"];
274
- function describeRule(rule) {
275
- const subject = rule.key ? `${rule.field}[${rule.key}]` : rule.field;
276
- const v = Array.isArray(rule.value) ? rule.value.join(", ") : rule.value === void 0 ? "" : String(rule.value);
277
- return `${rule.negate ? "NOT " : ""}${subject} ${rule.op.replace(/_/g, " ")}${v ? ` "${v}"` : ""}`;
278
- }
279
-
280
- // src/events-util.ts
281
- var SENSITIVE_PROPERTY_PATTERN = /(password|passwd|secret|token|credit|card|cvv|ssn|iban)/i;
282
- var MAX_PROPERTY_BYTES = 8 * 1024;
283
- function sanitizeProperties(props) {
284
- if (!props) return {};
285
- const out = {};
286
- let size = 0;
287
- for (const [k, v] of Object.entries(props)) {
288
- if (SENSITIVE_PROPERTY_PATTERN.test(k)) continue;
289
- if (typeof v === "function" || typeof v === "symbol") continue;
290
- const str = typeof v === "string" ? v : JSON.stringify(v ?? null);
291
- size += k.length + (str?.length ?? 0);
292
- if (size > MAX_PROPERTY_BYTES) break;
293
- out[k.slice(0, 100)] = typeof v === "string" ? v.slice(0, 1e3) : v;
294
- }
295
- return out;
296
- }
297
-
298
- export {
299
- fnv1a,
300
- rolloutBucket,
301
- slugify,
302
- randomId,
303
- isPlainObject,
304
- getPath,
305
- clamp,
306
- matchUrlPattern,
307
- stripOrigin,
308
- safeRegExp,
309
- RULE_OPERATORS,
310
- RULE_FIELDS,
311
- isRuleGroup,
312
- emptyRuleGroup,
313
- compare,
314
- evaluateRule,
315
- evaluateRules,
316
- SERVER_ASSISTED_FIELDS,
317
- describeRule,
318
- SENSITIVE_PROPERTY_PATTERN,
319
- MAX_PROPERTY_BYTES,
320
- sanitizeProperties
321
- };
322
- //# sourceMappingURL=chunk-OFBIZEM5.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/targeting-eval.ts","../src/events-util.ts"],"sourcesContent":["/** FNV-1a 32-bit hash; deterministic across SDK and server for rollout bucketing. */\nexport function fnv1a(input: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return h >>> 0;\n}\n\n/** Deterministic 0-99 bucket for percentage rollouts. */\nexport function rolloutBucket(subject: string, salt: string): number {\n return fnv1a(`${salt}:${subject}`) % 100;\n}\n\nexport function slugify(input: string): string {\n return input\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 64) || \"untitled\";\n}\n\nconst ALPHABET = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n/** URL-safe random id. Uses crypto when available, falls back to Math.random (ids are not secrets). */\nexport function randomId(size = 16): string {\n const g: any = typeof globalThis !== \"undefined\" ? globalThis : {};\n let bytes: Uint8Array;\n if (g.crypto && typeof g.crypto.getRandomValues === \"function\") {\n bytes = new Uint8Array(size);\n g.crypto.getRandomValues(bytes);\n } else {\n bytes = new Uint8Array(size);\n for (let i = 0; i < size; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n let out = \"\";\n for (let i = 0; i < size; i++) out += ALPHABET[(bytes[i] as number) % ALPHABET.length];\n return out;\n}\n\nexport function isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n/** Safe nested getter: get(obj, \"account.plan\") */\nexport function getPath(obj: unknown, path: string): unknown {\n if (!path) return obj;\n let cur: any = obj;\n for (const part of path.split(\".\")) {\n if (cur == null) return undefined;\n cur = cur[part];\n }\n return cur;\n}\n\nexport function clamp(n: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, n));\n}\n\n/** Glob-style URL pattern match: `*` matches any chars except `/`, `**` matches anything. */\nexport function matchUrlPattern(pattern: string, url: string): boolean {\n if (!pattern) return true;\n if (pattern === url) return true;\n const escaped = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*\\*/g, \"__GLOBSTAR__\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/__GLOBSTAR__/g, \".*\");\n const re = new RegExp(`^${escaped}$`);\n return re.test(url) || re.test(stripOrigin(url));\n}\n\nexport function stripOrigin(url: string): string {\n try {\n const u = new URL(url);\n return u.pathname + u.search + u.hash;\n } catch {\n return url;\n }\n}\n\n/** Safe regexp construction with a length cap to limit ReDoS exposure. */\nexport function safeRegExp(source: string, flags = \"i\"): RegExp | null {\n if (!source || source.length > 200) return null;\n try {\n return new RegExp(source, flags);\n } catch {\n return null;\n }\n}\n","/**\n * Zod-free targeting evaluation. Imported by the browser SDK via `@guidelytech/shared/runtime`\n * so the runtime bundle does not pull in zod. Schemas live in ./targeting.ts.\n */\nimport { getPath, matchUrlPattern, rolloutBucket, safeRegExp } from \"./utils\";\n\nexport const RULE_OPERATORS = [\n \"equals\",\n \"not_equals\",\n \"contains\",\n \"not_contains\",\n \"starts_with\",\n \"ends_with\",\n \"exists\",\n \"not_exists\",\n \"gt\",\n \"gte\",\n \"lt\",\n \"lte\",\n \"in\",\n \"not_in\",\n \"regex\",\n \"is_true\",\n \"is_false\",\n \"before\",\n \"after\",\n] as const;\nexport type RuleOperator = (typeof RULE_OPERATORS)[number];\n\nexport const RULE_FIELDS = [\n \"user.attribute\",\n \"account.attribute\",\n \"user.role\",\n \"user.plan\",\n \"url.path\",\n \"url.full\",\n \"url.query\",\n \"device.type\",\n \"device.browser\",\n \"device.os\",\n \"visitor.firstVisit\",\n \"visitor.returning\",\n \"event.seen\",\n \"guide.completed\",\n \"guide.dismissed\",\n \"guide.seen\",\n \"checklist.completed\",\n \"audience.member\",\n \"datetime.now\",\n \"datetime.dayOfWeek\",\n \"datetime.hour\",\n \"rollout.percentage\",\n \"environment\",\n \"locale\",\n] as const;\nexport type RuleField = (typeof RULE_FIELDS)[number];\n\nexport interface Rule {\n id: string;\n field: RuleField;\n /** For attribute/query/event fields: the attribute key, query param name, event name, guide id... */\n key?: string;\n op: RuleOperator;\n value?: string | number | boolean | string[];\n /** When true, the rule is negated (exclusion rule). */\n negate?: boolean;\n}\n\nexport type RuleGroup = {\n id: string;\n op: \"and\" | \"or\";\n rules: Array<Rule | RuleGroup>;\n negate?: boolean;\n};\n\nexport function isRuleGroup(r: Rule | RuleGroup): r is RuleGroup {\n return (r as RuleGroup).rules !== undefined;\n}\n\nexport function emptyRuleGroup(id = \"root\"): RuleGroup {\n return { id, op: \"and\", rules: [] };\n}\n\n/** Everything targeting evaluation can see. Built by the SDK (browser) or the server. */\nexport interface TargetingContext {\n userId?: string | null;\n anonymousId: string;\n userAttributes: Record<string, unknown>;\n accountAttributes: Record<string, unknown>;\n url: string;\n path: string;\n query: Record<string, string>;\n device: { type: \"desktop\" | \"tablet\" | \"mobile\"; browser?: string; os?: string };\n firstVisit: boolean;\n eventsSeen: Set<string> | string[];\n completedGuides: Set<string> | string[];\n dismissedGuides: Set<string> | string[];\n seenGuides: Set<string> | string[];\n completedChecklists: Set<string> | string[];\n audienceIds: Set<string> | string[];\n now: Date;\n environment: string;\n locale?: string;\n /** Salt for rollout (guide id). Set per guide before evaluating. */\n rolloutSalt?: string;\n}\n\nfunction has(set: Set<string> | string[] | undefined, v: string): boolean {\n if (!set) return false;\n return set instanceof Set ? set.has(v) : set.includes(v);\n}\n\nfunction resolveField(rule: Rule, ctx: TargetingContext): unknown {\n switch (rule.field) {\n case \"user.attribute\":\n return rule.key ? getPath(ctx.userAttributes, rule.key) : undefined;\n case \"account.attribute\":\n return rule.key ? getPath(ctx.accountAttributes, rule.key) : undefined;\n case \"user.role\":\n return ctx.userAttributes.role;\n case \"user.plan\":\n return ctx.userAttributes.plan ?? ctx.accountAttributes.plan;\n case \"url.path\":\n return ctx.path;\n case \"url.full\":\n return ctx.url;\n case \"url.query\":\n return rule.key ? ctx.query[rule.key] : undefined;\n case \"device.type\":\n return ctx.device.type;\n case \"device.browser\":\n return ctx.device.browser;\n case \"device.os\":\n return ctx.device.os;\n case \"visitor.firstVisit\":\n return ctx.firstVisit;\n case \"visitor.returning\":\n return !ctx.firstVisit;\n case \"event.seen\":\n return rule.key ? has(ctx.eventsSeen, rule.key) : false;\n case \"guide.completed\":\n return rule.key ? has(ctx.completedGuides, rule.key) : false;\n case \"guide.dismissed\":\n return rule.key ? has(ctx.dismissedGuides, rule.key) : false;\n case \"guide.seen\":\n return rule.key ? has(ctx.seenGuides, rule.key) : false;\n case \"checklist.completed\":\n return rule.key ? has(ctx.completedChecklists, rule.key) : false;\n case \"audience.member\":\n return rule.key ? has(ctx.audienceIds, rule.key) : false;\n case \"datetime.now\":\n return ctx.now.toISOString();\n case \"datetime.dayOfWeek\":\n return ctx.now.getDay();\n case \"datetime.hour\":\n return ctx.now.getHours();\n case \"rollout.percentage\":\n return rolloutBucket(ctx.userId || ctx.anonymousId, ctx.rolloutSalt || \"guidely\");\n case \"environment\":\n return ctx.environment;\n case \"locale\":\n return ctx.locale;\n default:\n return undefined;\n }\n}\n\nfunction toNumber(v: unknown): number | null {\n if (typeof v === \"number\") return v;\n if (typeof v === \"string\" && v.trim() !== \"\" && !Number.isNaN(Number(v))) return Number(v);\n if (v instanceof Date) return v.getTime();\n return null;\n}\n\nfunction toDateMs(v: unknown): number | null {\n if (v instanceof Date) return v.getTime();\n if (typeof v === \"string\") {\n const t = Date.parse(v);\n return Number.isNaN(t) ? null : t;\n }\n if (typeof v === \"number\") return v;\n return null;\n}\n\nexport function compare(op: RuleOperator, actual: unknown, expected: Rule[\"value\"]): boolean {\n switch (op) {\n case \"exists\":\n return actual !== undefined && actual !== null && actual !== \"\";\n case \"not_exists\":\n return actual === undefined || actual === null || actual === \"\";\n case \"is_true\":\n return actual === true || actual === \"true\";\n case \"is_false\":\n return actual === false || actual === \"false\";\n case \"equals\":\n if (typeof actual === \"boolean\") return String(actual) === String(expected);\n return String(actual ?? \"\") === String(expected ?? \"\");\n case \"not_equals\":\n return String(actual ?? \"\") !== String(expected ?? \"\");\n case \"contains\":\n return typeof actual === \"string\" && actual.toLowerCase().includes(String(expected ?? \"\").toLowerCase());\n case \"not_contains\":\n return !(typeof actual === \"string\" && actual.toLowerCase().includes(String(expected ?? \"\").toLowerCase()));\n case \"starts_with\":\n return typeof actual === \"string\" && actual.startsWith(String(expected ?? \"\"));\n case \"ends_with\":\n return typeof actual === \"string\" && actual.endsWith(String(expected ?? \"\"));\n case \"gt\":\n case \"gte\":\n case \"lt\":\n case \"lte\": {\n const a = toNumber(actual);\n const b = toNumber(expected);\n if (a === null || b === null) return false;\n return op === \"gt\" ? a > b : op === \"gte\" ? a >= b : op === \"lt\" ? a < b : a <= b;\n }\n case \"in\": {\n const list = Array.isArray(expected) ? expected : String(expected ?? \"\").split(\",\").map((s) => s.trim());\n return list.includes(String(actual ?? \"\"));\n }\n case \"not_in\": {\n const list = Array.isArray(expected) ? expected : String(expected ?? \"\").split(\",\").map((s) => s.trim());\n return !list.includes(String(actual ?? \"\"));\n }\n case \"regex\": {\n const re = safeRegExp(String(expected ?? \"\"));\n return !!re && typeof actual === \"string\" && re.test(actual);\n }\n case \"before\": {\n const a = toDateMs(actual);\n const b = toDateMs(expected);\n return a !== null && b !== null && a < b;\n }\n case \"after\": {\n const a = toDateMs(actual);\n const b = toDateMs(expected);\n return a !== null && b !== null && a > b;\n }\n default:\n return false;\n }\n}\n\nexport function evaluateRule(rule: Rule, ctx: TargetingContext): boolean {\n let result: boolean;\n if (rule.field === \"url.path\" || rule.field === \"url.full\") {\n const actual = String(resolveField(rule, ctx) ?? \"\");\n if (rule.op === \"equals\") result = matchUrlPattern(String(rule.value ?? \"\"), actual);\n else if (rule.op === \"not_equals\") result = !matchUrlPattern(String(rule.value ?? \"\"), actual);\n else result = compare(rule.op, actual, rule.value);\n } else {\n result = compare(rule.op, resolveField(rule, ctx), rule.value);\n }\n return rule.negate ? !result : result;\n}\n\n/** Evaluate a rule tree. An empty group matches everyone. */\nexport function evaluateRules(group: RuleGroup | null | undefined, ctx: TargetingContext): boolean {\n if (!group || group.rules.length === 0) return group?.negate ? false : true;\n const results = group.rules.map((r) => (isRuleGroup(r) ? evaluateRules(r, ctx) : evaluateRule(r, ctx)));\n const value = group.op === \"and\" ? results.every(Boolean) : results.some(Boolean);\n return group.negate ? !value : value;\n}\n\n/** Fields whose evaluation requires server-held data. Used by docs/UI to annotate rules. */\nexport const SERVER_ASSISTED_FIELDS: RuleField[] = [\"audience.member\", \"guide.completed\", \"guide.dismissed\", \"guide.seen\", \"checklist.completed\"];\n\nexport function describeRule(rule: Rule): string {\n const subject = rule.key ? `${rule.field}[${rule.key}]` : rule.field;\n const v = Array.isArray(rule.value) ? rule.value.join(\", \") : rule.value === undefined ? \"\" : String(rule.value);\n return `${rule.negate ? \"NOT \" : \"\"}${subject} ${rule.op.replace(/_/g, \" \")}${v ? ` \"${v}\"` : \"\"}`;\n}\n","/** Reserved property names that are stripped from `properties` to avoid collecting sensitive data. */\nexport const SENSITIVE_PROPERTY_PATTERN = /(password|passwd|secret|token|credit|card|cvv|ssn|iban)/i;\n\nexport const MAX_PROPERTY_BYTES = 8 * 1024;\n\nexport function sanitizeProperties(props: Record<string, unknown> | undefined): Record<string, unknown> {\n if (!props) return {};\n const out: Record<string, unknown> = {};\n let size = 0;\n for (const [k, v] of Object.entries(props)) {\n if (SENSITIVE_PROPERTY_PATTERN.test(k)) continue;\n if (typeof v === \"function\" || typeof v === \"symbol\") continue;\n const str = typeof v === \"string\" ? v : JSON.stringify(v ?? null);\n size += k.length + (str?.length ?? 0);\n if (size > MAX_PROPERTY_BYTES) break;\n out[k.slice(0, 100)] = typeof v === \"string\" ? v.slice(0, 1000) : v;\n }\n return out;\n}\n"],"mappings":"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","names":[]}