@guidelytech/shared 0.4.0 → 0.4.1

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,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":[]}