@drip-apex/sdk 0.1.0 → 0.3.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.
Files changed (246) hide show
  1. package/README.md +5 -0
  2. package/dist/bundle-report.json +584 -226
  3. package/dist/define.cjs +123 -0
  4. package/dist/define.cjs.map +7 -0
  5. package/dist/define.mjs +102 -0
  6. package/dist/define.mjs.map +7 -0
  7. package/dist/drip.cjs +19283 -0
  8. package/dist/drip.cjs.map +7 -0
  9. package/dist/drip.debug.js +1 -1
  10. package/dist/drip.debug.js.map +1 -1
  11. package/dist/drip.heatmap.js +1 -1
  12. package/dist/drip.heatmap.js.map +1 -1
  13. package/dist/drip.js +1 -1
  14. package/dist/drip.js.map +1 -1
  15. package/dist/drip.live.js +1 -1
  16. package/dist/drip.live.js.map +1 -1
  17. package/dist/drip.mjs +19267 -0
  18. package/dist/drip.mjs.map +7 -0
  19. package/dist/drip.replay-recorder.js +2 -0
  20. package/dist/drip.replay-recorder.js.map +1 -0
  21. package/dist/editor.js +147 -142
  22. package/dist/editor.js.map +4 -4
  23. package/dist/flags.cjs +1788 -0
  24. package/dist/flags.cjs.map +7 -0
  25. package/dist/flags.mjs +1765 -0
  26. package/dist/flags.mjs.map +7 -0
  27. package/dist/index.mjs +12 -0
  28. package/dist/index.mjs.map +2 -2
  29. package/dist/metafiles/define.cjs.meta.json +28 -0
  30. package/dist/metafiles/define.mjs.meta.json +32 -0
  31. package/dist/metafiles/drip.cjs.meta.json +3193 -0
  32. package/dist/metafiles/drip.debug.meta.json +2859 -574
  33. package/dist/metafiles/drip.heatmap.meta.json +2732 -460
  34. package/dist/metafiles/drip.live.meta.json +2732 -460
  35. package/dist/metafiles/drip.meta.json +2859 -574
  36. package/dist/metafiles/drip.mjs.meta.json +3235 -0
  37. package/dist/metafiles/drip.replay-recorder.meta.json +82 -0
  38. package/dist/metafiles/editor.meta.json +67 -27
  39. package/dist/metafiles/flags.cjs.meta.json +220 -0
  40. package/dist/metafiles/flags.mjs.meta.json +225 -0
  41. package/dist/metafiles/index.meta.json +2717 -491
  42. package/dist/metafiles/index.mjs.meta.json +14 -4
  43. package/dist/react/index.js +16636 -13533
  44. package/dist/react/index.js.map +4 -4
  45. package/dist/sri.json +20 -15
  46. package/dist/types/activation-runtime.d.ts +91 -0
  47. package/dist/types/analytics-runtime.d.ts +166 -0
  48. package/dist/types/antiflicker.d.ts +9 -3
  49. package/dist/types/assignment-cookie-sync.d.ts +31 -0
  50. package/dist/types/assignment-runtime.d.ts +257 -0
  51. package/dist/types/assignment-store.d.ts +4 -8
  52. package/dist/types/bucket.d.ts +1 -2
  53. package/dist/types/compact-store.d.ts +1 -0
  54. package/dist/types/config-runtime.d.ts +313 -0
  55. package/dist/types/consent-evaluation.d.ts +28 -0
  56. package/dist/types/css.d.ts +5 -3
  57. package/dist/types/debug-loader.d.ts +2 -0
  58. package/dist/types/decide-assignments.d.ts +31 -0
  59. package/dist/types/delivery-timing.d.ts +8 -0
  60. package/dist/types/early-interaction-capture.d.ts +79 -0
  61. package/dist/types/early-reassert.d.ts +32 -0
  62. package/dist/types/edge-runtime.d.ts +11 -0
  63. package/dist/types/editor/api.d.ts +3 -0
  64. package/dist/types/editor/controller.d.ts +3 -1
  65. package/dist/types/editor/publication-result.d.ts +11 -0
  66. package/dist/types/editor/types.d.ts +2 -1
  67. package/dist/types/editor/view-kit.d.ts +1 -1
  68. package/dist/types/endpoint.d.ts +8 -0
  69. package/dist/types/exclusion-groups.d.ts +4 -7
  70. package/dist/types/flags.d.ts +2 -0
  71. package/dist/types/goal-eligibility.d.ts +4 -0
  72. package/dist/types/goals.d.ts +10 -1
  73. package/dist/types/guards.d.ts +7 -7
  74. package/dist/types/handover.d.ts +12 -1
  75. package/dist/types/hash.d.ts +8 -4
  76. package/dist/types/hydration.d.ts +2 -1
  77. package/dist/types/index.d.ts +24 -128
  78. package/dist/types/init-test-helpers.d.ts +48 -0
  79. package/dist/types/inline-bootstrap.d.ts +26 -0
  80. package/dist/types/js.d.ts +3 -1
  81. package/dist/types/money.d.ts +12 -0
  82. package/dist/types/mutate.d.ts +45 -6
  83. package/dist/types/performance.d.ts +2 -0
  84. package/dist/types/persistence-decoders.d.ts +12 -0
  85. package/dist/types/public-api.d.ts +209 -0
  86. package/dist/types/qa-assistant-state.d.ts +34 -0
  87. package/dist/types/react/index.d.ts +28 -3
  88. package/dist/types/reassert-sentinel.d.ts +6 -0
  89. package/dist/types/redirect.d.ts +48 -0
  90. package/dist/types/replay-recorder-entry.d.ts +1 -0
  91. package/dist/types/replay-signal-state.d.ts +6 -0
  92. package/dist/types/replay-signals.d.ts +13 -0
  93. package/dist/types/replay.d.ts +110 -0
  94. package/dist/types/router.d.ts +12 -2
  95. package/dist/types/runtime-state.d.ts +166 -0
  96. package/dist/types/sanitize.d.ts +1 -0
  97. package/dist/types/shadow.d.ts +3 -0
  98. package/dist/types/shopify-cart.d.ts +25 -1
  99. package/dist/types/storefront-vitals.d.ts +24 -0
  100. package/dist/types/targeting-diagnostics.d.ts +1 -0
  101. package/dist/types/targeting.d.ts +16 -2
  102. package/dist/types/track.d.ts +15 -1
  103. package/dist/types/types.d.ts +115 -9
  104. package/dist/types/wait.d.ts +14 -1
  105. package/dist/types/weekday-schedule.d.ts +3 -0
  106. package/package.json +17 -8
  107. package/src/activation-runtime.ts +2737 -0
  108. package/src/analytics-runtime.ts +2175 -0
  109. package/src/antiflicker.test.ts +216 -44
  110. package/src/antiflicker.ts +103 -28
  111. package/src/artifact-concurrency.test.ts +1091 -0
  112. package/src/assignment-cookie-sync.test.ts +202 -0
  113. package/src/assignment-cookie-sync.ts +189 -0
  114. package/src/assignment-runtime.ts +3204 -0
  115. package/src/assignment-store.test.ts +56 -0
  116. package/src/assignment-store.ts +7 -38
  117. package/src/bucket.ts +3 -18
  118. package/src/commerce-analytics-lifecycle.test.ts +75 -15
  119. package/src/commerce-config-lifecycle.test.ts +81 -1
  120. package/src/commerce-price-preserve.test.ts +133 -0
  121. package/src/commerce-price.test.ts +97 -0
  122. package/src/commerce-price.ts +74 -20
  123. package/src/commerce-shipping.test.ts +53 -0
  124. package/src/commerce-shipping.ts +5 -11
  125. package/src/compact-store.ts +15 -0
  126. package/src/config-fetch.test.ts +480 -9
  127. package/src/config-runtime.ts +2071 -0
  128. package/src/consent-evaluation-borlabs.test.ts +41 -0
  129. package/src/consent-evaluation-cookiescript.test.ts +50 -0
  130. package/src/consent-evaluation.ts +151 -0
  131. package/src/css.test.ts +30 -0
  132. package/src/css.ts +120 -6
  133. package/src/cwv.test.ts +60 -77
  134. package/src/debug-loader.test.ts +214 -0
  135. package/src/debug-loader.ts +41 -0
  136. package/src/decide-assignments.test.ts +196 -0
  137. package/src/decide-assignments.ts +204 -0
  138. package/src/delivery-timing.test.ts +104 -0
  139. package/src/delivery-timing.ts +89 -0
  140. package/src/early-interaction-capture.test.ts +451 -0
  141. package/src/early-interaction-capture.ts +230 -0
  142. package/src/early-reassert.test.ts +207 -0
  143. package/src/early-reassert.ts +152 -0
  144. package/src/edge-adoption.test.ts +435 -0
  145. package/src/edge-runtime.ts +30 -0
  146. package/src/editor/api.test.ts +109 -0
  147. package/src/editor/api.ts +34 -3
  148. package/src/editor/controller.test.ts +297 -14
  149. package/src/editor/controller.ts +69 -5
  150. package/src/editor/index.test.ts +2 -0
  151. package/src/editor/index.ts +1 -0
  152. package/src/editor/publication-result.ts +58 -0
  153. package/src/editor/types.ts +10 -1
  154. package/src/editor/view-kit.ts +2 -1
  155. package/src/editor/view.test.ts +108 -0
  156. package/src/editor/view.ts +49 -7
  157. package/src/endpoint.test.ts +15 -0
  158. package/src/endpoint.ts +10 -0
  159. package/src/exclusion-groups.ts +8 -18
  160. package/src/flags.test.ts +88 -1
  161. package/src/flags.ts +29 -29
  162. package/src/global-runtime-reconciler.test.ts +271 -0
  163. package/src/goal-eligibility.test.ts +29 -0
  164. package/src/goal-eligibility.ts +17 -0
  165. package/src/goals.test.ts +144 -1
  166. package/src/goals.ts +97 -18
  167. package/src/guards.test.ts +99 -1
  168. package/src/guards.ts +34 -15
  169. package/src/handover-cache-order.test.ts +6 -3
  170. package/src/handover.test.ts +154 -8
  171. package/src/handover.ts +122 -12
  172. package/src/hash.ts +29 -54
  173. package/src/holdout.test.ts +26 -0
  174. package/src/hydration.test.ts +73 -2
  175. package/src/hydration.ts +18 -9
  176. package/src/index.ts +130 -7287
  177. package/src/init-analytics.test.ts +1694 -0
  178. package/src/init-anti-flicker.test.ts +1211 -0
  179. package/src/init-cart-attributes.test.ts +2003 -0
  180. package/src/init-config-resolution.test.ts +999 -0
  181. package/src/init-consent.test.ts +971 -0
  182. package/src/init-empty.test.ts +11 -5406
  183. package/src/init-integrations.test.ts +846 -0
  184. package/src/init-page-activation.test.ts +5064 -0
  185. package/src/init-qa-loading.test.ts +740 -0
  186. package/src/init-redirects.test.ts +2156 -0
  187. package/src/init-test-helpers.ts +287 -0
  188. package/src/inline-bootstrap.test.ts +184 -0
  189. package/src/inline-bootstrap.ts +73 -0
  190. package/src/js.test.ts +51 -1
  191. package/src/js.ts +86 -2
  192. package/src/money.test.ts +55 -1
  193. package/src/money.ts +104 -2
  194. package/src/money.web-parity.contract.test.ts +85 -0
  195. package/src/mutate.test.ts +474 -10
  196. package/src/mutate.ts +739 -230
  197. package/src/page-trigger.test.ts +218 -8
  198. package/src/performance.ts +10 -0
  199. package/src/persistence-decoders.ts +18 -0
  200. package/src/public-api.ts +1828 -0
  201. package/src/qa-assistant-state.test.ts +249 -3
  202. package/src/qa-assistant-state.ts +146 -6
  203. package/src/qa-assistant-view.test.ts +220 -2
  204. package/src/qa-assistant-view.ts +102 -5
  205. package/src/react/index.test.tsx +37 -0
  206. package/src/react/index.tsx +19 -3
  207. package/src/reassert-sentinel.test.ts +155 -1
  208. package/src/reassert-sentinel.ts +168 -8
  209. package/src/redirect.test.ts +541 -0
  210. package/src/redirect.ts +572 -0
  211. package/src/replay-recorder-entry.ts +115 -0
  212. package/src/replay-signal-state.ts +100 -0
  213. package/src/replay-signals.test.ts +249 -0
  214. package/src/replay-signals.ts +151 -0
  215. package/src/replay.test.ts +764 -0
  216. package/src/replay.ts +514 -0
  217. package/src/router.test.ts +61 -2
  218. package/src/router.ts +50 -12
  219. package/src/runtime-state.ts +507 -0
  220. package/src/runtime.test.ts +174 -7
  221. package/src/runtime.ts +88 -14
  222. package/src/sanitize.test.ts +34 -0
  223. package/src/sanitize.ts +23 -12
  224. package/src/shadow.test.ts +187 -0
  225. package/src/shadow.ts +99 -0
  226. package/src/shopify-cart.test.ts +733 -23
  227. package/src/shopify-cart.ts +402 -49
  228. package/src/storefront-vitals.test.ts +217 -0
  229. package/src/storefront-vitals.ts +206 -0
  230. package/src/surface-signals.ts +6 -0
  231. package/src/targeting-conformance.test.ts +9 -0
  232. package/src/targeting-diagnostics.ts +38 -8
  233. package/src/targeting-parity-corpus.json +559 -0
  234. package/src/targeting-parity-corpus.test.ts +18 -0
  235. package/src/targeting.detailed.test.ts +105 -0
  236. package/src/targeting.test.ts +211 -0
  237. package/src/targeting.ts +88 -207
  238. package/src/track.test.ts +147 -0
  239. package/src/track.ts +125 -26
  240. package/src/types.ts +120 -13
  241. package/src/wait.test.ts +23 -0
  242. package/src/wait.ts +129 -13
  243. package/src/weekday-delivery.test.ts +56 -0
  244. package/src/weekday-schedule-parity-corpus.json +92 -0
  245. package/src/weekday-schedule.test.ts +69 -0
  246. package/src/weekday-schedule.ts +20 -0
package/dist/flags.cjs ADDED
@@ -0,0 +1,1788 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/flags.ts
21
+ var flags_exports = {};
22
+ __export(flags_exports, {
23
+ defineFlagExperiment: () => defineFlagExperiment,
24
+ evaluateFlag: () => evaluateFlag,
25
+ evaluateFlagAssignment: () => evaluateFlagAssignment,
26
+ evaluateFlagValue: () => evaluateFlagValue
27
+ });
28
+ module.exports = __toCommonJS(flags_exports);
29
+
30
+ // ../shared-runtime/src/bucketing.ts
31
+ function murmurhash3_32_gc(key, seed = 0) {
32
+ let remainder = key.length & 3;
33
+ let bytes = key.length - remainder;
34
+ let h1 = seed;
35
+ let c1 = 3432918353;
36
+ let c2 = 461845907;
37
+ let i = 0;
38
+ while (i < bytes) {
39
+ let k12 = key.charCodeAt(i) & 255 | (key.charCodeAt(++i) & 255) << 8 | (key.charCodeAt(++i) & 255) << 16 | (key.charCodeAt(++i) & 255) << 24;
40
+ ++i;
41
+ k12 = Math.imul(k12, c1);
42
+ k12 = k12 << 15 | k12 >>> 17;
43
+ k12 = Math.imul(k12, c2);
44
+ h1 ^= k12;
45
+ h1 = h1 << 13 | h1 >>> 19;
46
+ h1 = Math.imul(h1, 5) + 3864292196;
47
+ }
48
+ let k1 = 0;
49
+ switch (remainder) {
50
+ case 3:
51
+ k1 ^= (key.charCodeAt(i + 2) & 255) << 16;
52
+ case 2:
53
+ k1 ^= (key.charCodeAt(i + 1) & 255) << 8;
54
+ case 1:
55
+ k1 ^= key.charCodeAt(i) & 255;
56
+ k1 = Math.imul(k1, c1);
57
+ k1 = k1 << 15 | k1 >>> 17;
58
+ k1 = Math.imul(k1, c2);
59
+ h1 ^= k1;
60
+ }
61
+ h1 ^= key.length;
62
+ h1 ^= h1 >>> 16;
63
+ h1 = Math.imul(h1, 2246822507);
64
+ h1 ^= h1 >>> 13;
65
+ h1 = Math.imul(h1, 3266489909);
66
+ h1 ^= h1 >>> 16;
67
+ return h1 >>> 0;
68
+ }
69
+ function hashToFloat(seed, value) {
70
+ const hash = murmurhash3_32_gc(seed + value, 1);
71
+ return hash % 1e4 / 1e4;
72
+ }
73
+ function variationHashToFloat(experimentId, value) {
74
+ return hashToFloat(`${experimentId}:variation`, value);
75
+ }
76
+ function inTrafficAllocation(hashValue, allocation = 1) {
77
+ if (allocation <= 0) return false;
78
+ if (allocation >= 1) return true;
79
+ return hashValue < allocation;
80
+ }
81
+ function chooseVariation(hashValue, weights) {
82
+ if (!weights.length) return -1;
83
+ const total = weights.reduce((sum, w) => sum + (w > 0 ? w : 0), 0);
84
+ if (total <= 0) return 0;
85
+ const n = hashValue * total;
86
+ let acc = 0;
87
+ for (let i = 0; i < weights.length; i++) {
88
+ acc += weights[i] > 0 ? weights[i] : 0;
89
+ if (n < acc) return i;
90
+ }
91
+ return weights.length - 1;
92
+ }
93
+
94
+ // src/define.ts
95
+ var ExperimentDefinitionError = class extends Error {
96
+ constructor(slug, issues) {
97
+ super(`Invalid experiment "${slug}": ${issues.join("; ")}`);
98
+ this.name = "ExperimentDefinitionError";
99
+ this.issues = issues;
100
+ }
101
+ };
102
+ function validate(def) {
103
+ const issues = [];
104
+ if (!def.slug || typeof def.slug !== "string") {
105
+ issues.push("slug is required and must be a non-empty string");
106
+ } else if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(def.slug) && def.slug.length > 1) {
107
+ issues.push("slug must be lowercase alphanumeric with hyphens (e.g. 'hero-banner-v2')");
108
+ }
109
+ if (!def.name || typeof def.name !== "string") {
110
+ issues.push("name is required");
111
+ }
112
+ if (!Array.isArray(def.variations) || def.variations.length < 2) {
113
+ issues.push("at least 2 variations are required");
114
+ } else {
115
+ const controls = def.variations.filter((v) => v.isControl);
116
+ if (controls.length === 0) {
117
+ issues.push("exactly one variation must be marked as control (isControl: true)");
118
+ } else if (controls.length > 1) {
119
+ issues.push("only one variation can be the control");
120
+ }
121
+ const totalWeight = def.variations.reduce((sum, v) => sum + v.weight, 0);
122
+ if (Math.abs(totalWeight - 1) > 0.01) {
123
+ issues.push(`variation weights should sum to 1 (got ${totalWeight.toFixed(3)})`);
124
+ }
125
+ const ids = /* @__PURE__ */ new Set();
126
+ for (const v of def.variations) {
127
+ if (!v.id) issues.push("every variation must have an id");
128
+ if (ids.has(v.id)) issues.push(`duplicate variation id: "${v.id}"`);
129
+ ids.add(v.id);
130
+ if (v.weight < 0 || v.weight > 1) issues.push(`variation "${v.id}" weight must be between 0 and 1`);
131
+ }
132
+ }
133
+ if (def.trafficAllocation !== void 0) {
134
+ if (def.trafficAllocation < 0 || def.trafficAllocation > 1) {
135
+ issues.push("trafficAllocation must be between 0 and 1");
136
+ }
137
+ }
138
+ if (def.goals) {
139
+ const goalIds = /* @__PURE__ */ new Set();
140
+ for (const g of def.goals) {
141
+ if (!g.id) issues.push("every goal must have an id");
142
+ if (goalIds.has(g.id)) issues.push(`duplicate goal id: "${g.id}"`);
143
+ goalIds.add(g.id);
144
+ if (g.type === "click" && !g.selector) {
145
+ issues.push(`click goal "${g.id}" requires a selector`);
146
+ }
147
+ }
148
+ }
149
+ return issues;
150
+ }
151
+ function defineExperiment(def) {
152
+ const issues = validate(def);
153
+ if (issues.length > 0) {
154
+ throw new ExperimentDefinitionError(def.slug ?? "(unnamed)", issues);
155
+ }
156
+ const experiment = {
157
+ id: def.slug,
158
+ // slug is used as the id for code-first experiments
159
+ slug: def.slug,
160
+ name: def.name,
161
+ trafficAllocation: def.trafficAllocation ?? 1,
162
+ targeting: def.targeting,
163
+ hashAttribute: def.hashAttribute,
164
+ variations: def.variations.map((v) => ({
165
+ id: v.id,
166
+ name: v.name,
167
+ weight: v.weight,
168
+ mutations: v.mutations ?? [],
169
+ value: v.value,
170
+ css: v.css,
171
+ js: v.js,
172
+ resetJs: v.resetJs,
173
+ externalCssUrl: v.externalCssUrl,
174
+ externalJsUrl: v.externalJsUrl,
175
+ isControl: v.isControl ?? false
176
+ })),
177
+ goals: def.goals?.map((g) => ({
178
+ id: g.id,
179
+ type: g.type,
180
+ selector: g.selector,
181
+ urlPattern: g.urlPattern,
182
+ countOnce: g.countOnce
183
+ }))
184
+ };
185
+ return Object.freeze(experiment);
186
+ }
187
+
188
+ // src/edge-runtime.ts
189
+ function edgeRuntimeActive() {
190
+ return globalThis.__dripEdgeInit === true;
191
+ }
192
+
193
+ // src/hash.ts
194
+ var UID_KEY = "drip_uid";
195
+ var storageMode = "localStorage";
196
+ var encodeStorageValues = false;
197
+ var cookieDomain = "";
198
+ var memoryUserId = null;
199
+ function bytesToHex(bytes) {
200
+ let out = "";
201
+ for (let i = 0; i < bytes.length; i++) {
202
+ out += bytes[i].toString(16).padStart(2, "0");
203
+ }
204
+ return out;
205
+ }
206
+ function generateId() {
207
+ if (globalThis.crypto && globalThis.crypto.getRandomValues) {
208
+ const bytes = new Uint8Array(16);
209
+ globalThis.crypto.getRandomValues(bytes);
210
+ return bytesToHex(bytes);
211
+ }
212
+ return Math.random().toString(36).slice(2) + Date.now().toString(36);
213
+ }
214
+ function encodeStoredValue(value) {
215
+ if (!encodeStorageValues) return value;
216
+ try {
217
+ return "b64:" + btoa(value).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
218
+ } catch {
219
+ return value;
220
+ }
221
+ }
222
+ function decodeStoredValue(value) {
223
+ if (!value) return null;
224
+ if (!value.startsWith("b64:")) return value;
225
+ try {
226
+ const raw = value.slice(4).replace(/-/g, "+").replace(/_/g, "/");
227
+ const padded = raw + "=".repeat((4 - raw.length % 4) % 4);
228
+ return atob(padded);
229
+ } catch {
230
+ return null;
231
+ }
232
+ }
233
+ function browserStorage() {
234
+ try {
235
+ if (storageMode === "sessionStorage") return globalThis.sessionStorage ?? null;
236
+ if (storageMode === "localStorage") return globalThis.localStorage ?? null;
237
+ return null;
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ function readStorage() {
243
+ if (storageMode === "memory") return memoryUserId;
244
+ if (storageMode === "cookie") return null;
245
+ try {
246
+ return decodeStoredValue(browserStorage()?.getItem(UID_KEY) || null);
247
+ } catch {
248
+ return null;
249
+ }
250
+ }
251
+ function writeStorage(value) {
252
+ if (storageMode === "memory") {
253
+ memoryUserId = value;
254
+ return;
255
+ }
256
+ if (storageMode === "cookie") return;
257
+ try {
258
+ browserStorage()?.setItem(UID_KEY, encodeStoredValue(value));
259
+ } catch {
260
+ }
261
+ }
262
+ function readCookie() {
263
+ if (!globalThis.document?.cookie) return null;
264
+ const match = globalThis.document.cookie.match(
265
+ new RegExp("(^| )" + UID_KEY + "=([^;]+)")
266
+ );
267
+ return match ? decodeStoredValue(decodeURIComponent(match[2])) : null;
268
+ }
269
+ function writeCookie(value) {
270
+ if (!globalThis.document) return;
271
+ const expires = new Date(Date.now() + 365 * 864e5).toUTCString();
272
+ const domain = cookieDomain ? "; domain=" + cookieDomain : "";
273
+ globalThis.document.cookie = UID_KEY + "=" + encodeURIComponent(encodeStoredValue(value)) + "; expires=" + expires + "; path=/" + domain;
274
+ }
275
+ function getUserId(provided) {
276
+ if (provided) return provided;
277
+ if (edgeRuntimeActive()) {
278
+ const edgeUserId = readCookie();
279
+ if (edgeUserId) {
280
+ if (readStorage() !== edgeUserId) writeStorage(edgeUserId);
281
+ return edgeUserId;
282
+ }
283
+ }
284
+ const existing = readStorage() || readCookie();
285
+ if (existing) return existing;
286
+ const created = generateId();
287
+ writeStorage(created);
288
+ if (storageMode !== "memory") writeCookie(created);
289
+ return created;
290
+ }
291
+
292
+ // src/data-layer-normalize.ts
293
+ function isPlainObject(value) {
294
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
295
+ const prototype = Object.getPrototypeOf(value);
296
+ return prototype === Object.prototype || prototype === null;
297
+ }
298
+ function normalizeDataLayerEntry(item) {
299
+ if (!item || typeof item !== "object" || Array.isArray(item)) return null;
300
+ const record = item;
301
+ if (typeof record.event === "string") {
302
+ return item;
303
+ }
304
+ if (record[0] !== "event" || typeof record[1] !== "string") return null;
305
+ const params = isPlainObject(record[2]) ? record[2] : {};
306
+ return { ...params, event: record[1] };
307
+ }
308
+
309
+ // src/runtime-tools.ts
310
+ function noopCleanup() {
311
+ return () => {
312
+ };
313
+ }
314
+ function readDocument(env) {
315
+ return env.document ?? (typeof document !== "undefined" ? document : void 0);
316
+ }
317
+ function readWindow(env) {
318
+ return env.window ?? (typeof window !== "undefined" ? window : void 0);
319
+ }
320
+ function readLocation(env) {
321
+ return env.location ?? (typeof location !== "undefined" ? location : void 0);
322
+ }
323
+ function safeCallback(callback, value) {
324
+ try {
325
+ callback(value);
326
+ } catch {
327
+ }
328
+ }
329
+ function isElement(value) {
330
+ return typeof Element !== "undefined" && value instanceof Element;
331
+ }
332
+ function isDocument(value) {
333
+ return typeof Document !== "undefined" && value instanceof Document;
334
+ }
335
+ function collectMatches(root, selector) {
336
+ const matches = [];
337
+ if (isElement(root) && root.matches(selector)) {
338
+ matches.push(root);
339
+ }
340
+ try {
341
+ root.querySelectorAll(selector).forEach((element) => matches.push(element));
342
+ } catch {
343
+ }
344
+ return matches;
345
+ }
346
+ function observeRootFor(root, doc) {
347
+ if (isDocument(root)) {
348
+ return root.documentElement ?? root.body;
349
+ }
350
+ if (isElement(root)) return root;
351
+ return doc.documentElement ?? doc.body;
352
+ }
353
+ function normalizeInViewOptions(options) {
354
+ if (typeof options === "boolean") return { once: !options };
355
+ return options ?? {};
356
+ }
357
+ function normalizeMatcher(matcher) {
358
+ return Array.isArray(matcher) ? matcher : Object.entries(matcher);
359
+ }
360
+ function matchesDataLayerEntry(entry, matcher) {
361
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
362
+ const record = entry;
363
+ return normalizeMatcher(matcher).every(([key, expected]) => {
364
+ if (expected instanceof RegExp) {
365
+ return expected.test(String(record[key] ?? ""));
366
+ }
367
+ return record[key] === expected;
368
+ });
369
+ }
370
+ function createApexRuntimeTools(env = {}) {
371
+ const tools = {
372
+ waitForElement(selector, callback, options = {}) {
373
+ const doc = readDocument(env);
374
+ if (!doc || !selector.trim()) return noopCleanup();
375
+ const root = options.root ?? doc;
376
+ const seen = /* @__PURE__ */ new WeakSet();
377
+ let active = true;
378
+ let observer = null;
379
+ let timeoutId = null;
380
+ const stop = () => {
381
+ active = false;
382
+ observer?.disconnect();
383
+ observer = null;
384
+ if (timeoutId !== null) {
385
+ readWindow(env)?.clearTimeout(timeoutId);
386
+ timeoutId = null;
387
+ }
388
+ };
389
+ const handle = (scope) => {
390
+ if (!active) return;
391
+ for (const element of collectMatches(scope, selector)) {
392
+ if (seen.has(element)) continue;
393
+ seen.add(element);
394
+ safeCallback(callback, element);
395
+ if (options.once !== false) {
396
+ stop();
397
+ return;
398
+ }
399
+ }
400
+ };
401
+ handle(root);
402
+ if (!active || typeof MutationObserver === "undefined") return stop;
403
+ const observeTarget = observeRootFor(root, doc);
404
+ if (!observeTarget) return stop;
405
+ observer = new MutationObserver((mutations) => {
406
+ for (const mutation of mutations) {
407
+ mutation.addedNodes.forEach((node) => {
408
+ if (isElement(node)) handle(node);
409
+ });
410
+ }
411
+ });
412
+ observer.observe(observeTarget, { childList: true, subtree: true });
413
+ if (options.timeoutMs && options.timeoutMs > 0) {
414
+ timeoutId = readWindow(env)?.setTimeout(stop, options.timeoutMs) ?? null;
415
+ }
416
+ return stop;
417
+ },
418
+ elementIsInView(selector, callback, rawOptions) {
419
+ const doc = readDocument(env);
420
+ const win = readWindow(env);
421
+ if (!doc || !win || !selector.trim()) return noopCleanup();
422
+ const options = normalizeInViewOptions(rawOptions);
423
+ const once = options.once !== false;
424
+ const observed = /* @__PURE__ */ new WeakSet();
425
+ const cleanups = [];
426
+ let observer = null;
427
+ const stop = () => {
428
+ observer?.disconnect();
429
+ observer = null;
430
+ cleanups.splice(0).forEach((cleanup) => cleanup());
431
+ };
432
+ if (typeof IntersectionObserver === "undefined") {
433
+ return tools.waitForElement(
434
+ selector,
435
+ (element) => {
436
+ const rect = element.getBoundingClientRect();
437
+ const inView = rect.width > 0 && rect.height > 0 && rect.bottom >= 0 && rect.right >= 0 && rect.top <= win.innerHeight && rect.left <= win.innerWidth;
438
+ if (inView) safeCallback(callback, element);
439
+ },
440
+ { once, timeoutMs: options.timeoutMs }
441
+ );
442
+ }
443
+ observer = new IntersectionObserver(
444
+ (entries) => {
445
+ for (const entry of entries) {
446
+ if (!entry.isIntersecting) continue;
447
+ safeCallback(callback, entry.target);
448
+ if (once) observer?.unobserve(entry.target);
449
+ }
450
+ },
451
+ {
452
+ root: isDocument(options.root) ? null : options.root ?? null,
453
+ rootMargin: options.rootMargin,
454
+ threshold: options.threshold ?? 1
455
+ }
456
+ );
457
+ cleanups.push(
458
+ tools.waitForElement(
459
+ selector,
460
+ (element) => {
461
+ if (observed.has(element)) return;
462
+ observed.add(element);
463
+ observer?.observe(element);
464
+ },
465
+ { once: false, timeoutMs: options.timeoutMs }
466
+ )
467
+ );
468
+ return stop;
469
+ },
470
+ domChanged(callback, options = {}) {
471
+ const doc = readDocument(env);
472
+ const win = readWindow(env);
473
+ if (!doc || typeof MutationObserver === "undefined") return noopCleanup();
474
+ const root = options.root ?? doc.documentElement ?? doc.body;
475
+ if (!root) return noopCleanup();
476
+ const debounceMs = Math.max(0, options.debounceMs ?? 50);
477
+ let pending = [];
478
+ let timeoutId = null;
479
+ const flush = () => {
480
+ timeoutId = null;
481
+ const mutations = pending;
482
+ pending = [];
483
+ safeCallback(callback, mutations);
484
+ };
485
+ const observer = new MutationObserver((mutations) => {
486
+ pending.push(...mutations);
487
+ if (debounceMs === 0) {
488
+ flush();
489
+ return;
490
+ }
491
+ if (timeoutId !== null) return;
492
+ timeoutId = win?.setTimeout(flush, debounceMs) ?? null;
493
+ });
494
+ observer.observe(root, { childList: true, subtree: options.subtree !== false });
495
+ return () => {
496
+ observer.disconnect();
497
+ if (timeoutId !== null) win?.clearTimeout(timeoutId);
498
+ };
499
+ },
500
+ domLoaded(callback) {
501
+ const doc = readDocument(env);
502
+ if (!doc || doc.readyState !== "loading") {
503
+ safeCallback(callback, void 0);
504
+ return noopCleanup();
505
+ }
506
+ const handler = () => safeCallback(callback, void 0);
507
+ doc.addEventListener("DOMContentLoaded", handler, { once: true });
508
+ return () => doc.removeEventListener("DOMContentLoaded", handler);
509
+ },
510
+ waitForDataLayer(matcher, callback, options = {}) {
511
+ const win = readWindow(env);
512
+ if (!win) return noopCleanup();
513
+ const layerName = options.layerName || "dataLayer";
514
+ const layerHost = win;
515
+ const layer = Array.isArray(layerHost[layerName]) ? layerHost[layerName] : [];
516
+ layerHost[layerName] = layer;
517
+ const originalPush = layer.push;
518
+ let active = true;
519
+ let timeoutId = null;
520
+ const process = (entry) => {
521
+ if (!active) return;
522
+ const normalized = normalizeDataLayerEntry(entry);
523
+ const rawRecord = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : null;
524
+ const candidate = normalized ?? (rawRecord && typeof rawRecord[0] !== "string" ? rawRecord : null);
525
+ if (!candidate || !matchesDataLayerEntry(candidate, matcher)) return;
526
+ safeCallback(callback, candidate);
527
+ if (options.once !== false) cleanup();
528
+ };
529
+ const cleanup = () => {
530
+ active = false;
531
+ if (layer.push === wrappedPush) layer.push = originalPush;
532
+ if (timeoutId !== null) {
533
+ win.clearTimeout(timeoutId);
534
+ timeoutId = null;
535
+ }
536
+ };
537
+ const wrappedPush = function(...items) {
538
+ const result = originalPush.apply(layer, items);
539
+ items.forEach(process);
540
+ return result;
541
+ };
542
+ layer.slice(Math.max(0, layer.length - 50)).forEach(process);
543
+ if (active) layer.push = wrappedPush;
544
+ if (options.timeoutMs && options.timeoutMs > 0) {
545
+ timeoutId = win.setTimeout(cleanup, options.timeoutMs);
546
+ }
547
+ return cleanup;
548
+ },
549
+ track(type, data) {
550
+ env.track?.(type, data);
551
+ },
552
+ trackGoal(goalId, data) {
553
+ env.trackGoal?.(goalId, data);
554
+ },
555
+ signal(key, value, ttlDays) {
556
+ env.signal?.(key, value, ttlDays);
557
+ },
558
+ redirect(target) {
559
+ const loc = readLocation(env);
560
+ if (!loc || !target.trim()) return;
561
+ loc.assign(new URL(target, loc.href).toString());
562
+ },
563
+ reInit(forceReapply) {
564
+ return env.reInit?.(forceReapply);
565
+ },
566
+ get(key) {
567
+ if (key === "tools") return tools;
568
+ return env.get?.(key);
569
+ }
570
+ };
571
+ return tools;
572
+ }
573
+
574
+ // ../event-schema/src/targeting-core.ts
575
+ function isRegexOuterQuantifierStart(pattern, index) {
576
+ return pattern[index] !== "?" && isRegexQuantifierStart(pattern, index);
577
+ }
578
+ function isRegexQuantifierStart(pattern, index) {
579
+ const char = pattern[index];
580
+ if (char === "*" || char === "+" || char === "?") return true;
581
+ if (char !== "{") return false;
582
+ const end = pattern.indexOf("}", index + 1);
583
+ if (end === -1) return false;
584
+ return /^\{\d+(?:,\d*)?\}$/.test(pattern.slice(index, end + 1));
585
+ }
586
+ function isSafeRegexPattern(pattern) {
587
+ if (!pattern || pattern.length > MAX_PATTERN_LENGTH) return false;
588
+ const groups = [];
589
+ let escaped = false;
590
+ let inClass = false;
591
+ let hasBackreference = false;
592
+ let hasQuantifiedGroup = false;
593
+ let hasQuantifierInGroup = false;
594
+ let hasQuantifiedBackreference = false;
595
+ for (let index = 0; index < pattern.length; index += 1) {
596
+ const char = pattern[index];
597
+ if (escaped) {
598
+ escaped = false;
599
+ continue;
600
+ }
601
+ if (char === "\\") {
602
+ const numericBackreference = /^[1-9]$/.test(pattern[index + 1] ?? "");
603
+ const namedBackreference = pattern[index + 1] === "k" ? pattern.slice(index + 2).match(/^<[^>]+>/)?.[0] : void 0;
604
+ if (numericBackreference || namedBackreference) {
605
+ hasBackreference = true;
606
+ const backreferenceEnd = numericBackreference ? index + 2 : index + 2 + namedBackreference.length;
607
+ if (isRegexQuantifierStart(pattern, backreferenceEnd)) {
608
+ hasQuantifiedBackreference = true;
609
+ }
610
+ index = backreferenceEnd - 1;
611
+ } else {
612
+ escaped = true;
613
+ }
614
+ continue;
615
+ }
616
+ if (char === "[") {
617
+ inClass = true;
618
+ continue;
619
+ }
620
+ if (char === "]") {
621
+ inClass = false;
622
+ continue;
623
+ }
624
+ if (inClass) continue;
625
+ if (char === "(") {
626
+ groups.push({ hasAlternation: false, hasQuantifier: false });
627
+ continue;
628
+ }
629
+ if (char === "|") {
630
+ const current = groups[groups.length - 1];
631
+ if (current) current.hasAlternation = true;
632
+ continue;
633
+ }
634
+ if (isRegexQuantifierStart(pattern, index)) {
635
+ if (char === "?" && pattern[index - 1] === "(") continue;
636
+ const current = groups[groups.length - 1];
637
+ if (current) {
638
+ current.hasQuantifier = true;
639
+ hasQuantifierInGroup = true;
640
+ }
641
+ if (char === "{") index = pattern.indexOf("}", index + 1);
642
+ continue;
643
+ }
644
+ if (char === ")") {
645
+ const group = groups.pop();
646
+ if (!group) return false;
647
+ const quantified = isRegexOuterQuantifierStart(pattern, index + 1);
648
+ hasQuantifiedGroup = hasQuantifiedGroup || quantified;
649
+ if (quantified && (group.hasQuantifier || group.hasAlternation))
650
+ return false;
651
+ const parent = groups[groups.length - 1];
652
+ if (parent) {
653
+ parent.hasQuantifier = parent.hasQuantifier || group.hasQuantifier || quantified;
654
+ parent.hasAlternation = parent.hasAlternation || group.hasAlternation;
655
+ }
656
+ }
657
+ }
658
+ return !escaped && !inClass && groups.length === 0 && !(hasBackreference && (hasQuantifiedGroup || hasQuantifierInGroup || hasQuantifiedBackreference));
659
+ }
660
+ var MAX_PATTERN_LENGTH = 1024;
661
+ var MAX_CACHE_ENTRIES = 256;
662
+ var regexCache = /* @__PURE__ */ new Map();
663
+ function cacheRegex(pattern, compiled) {
664
+ if (!regexCache.has(pattern) && regexCache.size >= MAX_CACHE_ENTRIES) {
665
+ const oldest = regexCache.keys().next().value;
666
+ if (oldest !== void 0) regexCache.delete(oldest);
667
+ }
668
+ regexCache.set(pattern, compiled);
669
+ }
670
+ function safeRegexTestDetailed(pattern, input, executeUnsafePatterns) {
671
+ if (!pattern || pattern.length > MAX_PATTERN_LENGTH) return { matched: false };
672
+ if (!executeUnsafePatterns && !isSafeRegexPattern(pattern)) {
673
+ try {
674
+ new RegExp(pattern);
675
+ return { matched: false, indeterminate: true };
676
+ } catch {
677
+ return { matched: false };
678
+ }
679
+ }
680
+ if (regexCache.has(pattern)) {
681
+ const cached = regexCache.get(pattern);
682
+ return { matched: cached ? cached.test(input) : false };
683
+ }
684
+ let compiled = null;
685
+ try {
686
+ compiled = new RegExp(pattern);
687
+ } catch {
688
+ compiled = null;
689
+ }
690
+ cacheRegex(pattern, compiled);
691
+ return { matched: compiled ? compiled.test(input) : false };
692
+ }
693
+ function safeRegexTest(pattern, input) {
694
+ return safeRegexTestDetailed(pattern, input, true).matched;
695
+ }
696
+ function evalSimpleUrlPart(actual, pattern, isPath) {
697
+ try {
698
+ let escaped = pattern.replace(/[*.+?^${}()|[\]\\]/g, "\\$&").replace(/_____/g, ".*");
699
+ if (isPath) escaped = "\\/?" + escaped.replace(/(^\/|\/$)/g, "") + "\\/?";
700
+ return new RegExp("^" + escaped + "$", "i").test(actual);
701
+ } catch {
702
+ return false;
703
+ }
704
+ }
705
+ function evalSimpleUrlTarget(actual, pattern) {
706
+ try {
707
+ const expected = new URL(pattern.replace(/^([^:/?]*)\./i, "https://$1.").replace(/\*/g, "_____"), "https://_____");
708
+ const components = [
709
+ [actual.host, expected.host, false],
710
+ [actual.pathname, expected.pathname, true]
711
+ ];
712
+ if (expected.hash) components.push([actual.hash, expected.hash, false]);
713
+ expected.searchParams.forEach((value, key) => components.push([actual.searchParams.get(key) || "", value, false]));
714
+ return !components.some(([value, expectedValue, path]) => !evalSimpleUrlPart(value, expectedValue, path));
715
+ } catch {
716
+ return false;
717
+ }
718
+ }
719
+ function unescapeRegexLiteral(value) {
720
+ return value.replace(/\\([.*+?^${}()|[\]\\\/])/g, "$1");
721
+ }
722
+ function evaluateUrlRuleDetailed(url, rule, options) {
723
+ const executeUnsafePatterns = options?.executeUnsafePatterns !== false;
724
+ try {
725
+ const parsed = new URL(url, "https://_");
726
+ const matchType = rule.matchType ?? rule.match_type;
727
+ if (matchType === "contains") {
728
+ const actual = parsed.href.toLowerCase();
729
+ const pattern = rule.pattern.toLowerCase();
730
+ return { matched: actual.includes(pattern) || actual.includes(unescapeRegexLiteral(pattern)) };
731
+ }
732
+ if (rule.type === "regex") {
733
+ const escaped = rule.pattern.replace(/([^\\])\//g, "$1\\/");
734
+ const absolute = safeRegexTestDetailed(escaped, parsed.href, executeUnsafePatterns);
735
+ if (absolute.matched || absolute.indeterminate) return absolute;
736
+ return safeRegexTestDetailed(escaped, parsed.href.substring(parsed.origin.length), executeUnsafePatterns);
737
+ }
738
+ return { matched: evalSimpleUrlTarget(parsed, rule.pattern) };
739
+ } catch {
740
+ return { matched: false };
741
+ }
742
+ }
743
+ function evaluateUrlRule(url, rule) {
744
+ return evaluateUrlRuleDetailed(url, rule).matched;
745
+ }
746
+ function urlRuleExplicitlyNamesHost(rule) {
747
+ const pattern = unescapeRegexLiteral(rule.pattern.trim()).replace(/^\^/, "");
748
+ return /^\(?[a-z][a-z0-9+.-]*\??:\/\//i.test(pattern) || /^\/\/[^/]/.test(pattern) || /^(?:\*\.)?[^/?#\s*]+\.[^/?#\s]+(?::\d+)?(?:[/?#]|$)/i.test(pattern);
749
+ }
750
+ function normalizeUrlRule(value) {
751
+ if (typeof value === "string") {
752
+ const pattern2 = value.trim();
753
+ return pattern2 ? { type: "simple", pattern: pattern2, include: true } : null;
754
+ }
755
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
756
+ const raw = value;
757
+ const candidate = raw.pattern ?? raw.value ?? raw.url ?? raw.path;
758
+ const pattern = typeof candidate === "string" ? candidate.trim() : "";
759
+ if (!pattern) return null;
760
+ const rawMatch = raw.matchType ?? raw.match_type;
761
+ const matchType = ["exact", "contains", "wildcard", "regex"].includes(String(rawMatch)) ? rawMatch : raw.type === "url_contains" ? "contains" : void 0;
762
+ return {
763
+ type: raw.type === "regex" || raw.type === "url_regex" ? "regex" : "simple",
764
+ pattern,
765
+ include: typeof raw.include === "boolean" ? raw.include : true,
766
+ ...matchType ? { matchType } : {}
767
+ };
768
+ }
769
+ function normalizeUrlRules(targeting) {
770
+ if (!targeting || typeof targeting !== "object" || Array.isArray(targeting)) return [];
771
+ const raw = targeting;
772
+ const urls = Array.isArray(raw.url) ? raw.url : raw.url == null ? [] : [raw.url];
773
+ return urls.concat(Array.isArray(raw.rules) ? raw.rules : []).map(normalizeUrlRule).filter((rule) => Boolean(rule));
774
+ }
775
+ function stringValues(value) {
776
+ if (Array.isArray(value)) return value.map((item) => String(item).trim()).filter(Boolean);
777
+ if (value == null) return [];
778
+ return [String(value)];
779
+ }
780
+ function meaningful(value) {
781
+ if (value == null) return false;
782
+ if (Array.isArray(value)) return value.length > 0;
783
+ return typeof value !== "string" || value.trim().length > 0;
784
+ }
785
+ function compareNumber(actual, expected, predicate) {
786
+ const a = Number(Array.isArray(actual) ? actual[0] : actual);
787
+ const b = Number(Array.isArray(expected) ? expected[0] : expected);
788
+ return Number.isFinite(a) && Number.isFinite(b) && predicate(a, b);
789
+ }
790
+ function wildcard(pattern) {
791
+ return "^" + pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$";
792
+ }
793
+ function matchSegmentConditionValues(condition, actual, expected = condition.value) {
794
+ const actualValues = stringValues(actual);
795
+ const expectedValues = stringValues(expected);
796
+ const first = expectedValues[0] ?? "";
797
+ switch (condition.operator) {
798
+ case "equals":
799
+ return actualValues.some((value) => value === String(expected ?? ""));
800
+ case "not_equals":
801
+ return !actualValues.some((value) => value === String(expected ?? ""));
802
+ case "contains":
803
+ return actualValues.some((value) => expectedValues.length > 1 ? expectedValues.includes(value) : value.includes(first));
804
+ case "not_contains":
805
+ return !actualValues.some((value) => expectedValues.length > 1 ? expectedValues.includes(value) : value.includes(first));
806
+ case "starts_with":
807
+ return actualValues.some((value) => value.startsWith(first));
808
+ case "ends_with":
809
+ return actualValues.some((value) => value.endsWith(first));
810
+ case "is_set":
811
+ return meaningful(actual);
812
+ case "is_empty":
813
+ return !meaningful(actual);
814
+ case "in":
815
+ return actualValues.some((value) => expectedValues.includes(value));
816
+ case "not_in":
817
+ return !actualValues.some((value) => expectedValues.includes(value));
818
+ case "matches_wildcard":
819
+ return actualValues.some((value) => safeRegexTest(wildcard(first), value));
820
+ case "matches_regex":
821
+ return actualValues.some((value) => safeRegexTest(first, value));
822
+ case "greater_than":
823
+ return compareNumber(actual, expected, (a, b) => a > b);
824
+ case "greater_than_or_equal":
825
+ return compareNumber(actual, expected, (a, b) => a >= b);
826
+ case "less_than":
827
+ return compareNumber(actual, expected, (a, b) => a < b);
828
+ case "less_than_or_equal":
829
+ return compareNumber(actual, expected, (a, b) => a <= b);
830
+ default:
831
+ return false;
832
+ }
833
+ }
834
+
835
+ // src/signals.ts
836
+ var STORE_KEY = "drip_signals_v1";
837
+ var VISITOR_SEEN_KEY = "drip_visitor_seen_v1";
838
+ var DEFAULT_TTL_DAYS = 30;
839
+ var MAX_ARRAY_LENGTH = 24;
840
+ var MAX_VALUE_BYTES = 4096;
841
+ var runtimeSignals = {};
842
+ var signalSources = {};
843
+ var signalPersistenceAllowed = () => true;
844
+ function now() {
845
+ return Date.now();
846
+ }
847
+ function ttlMs(days) {
848
+ return Math.max(1, days) * 86400 * 1e3;
849
+ }
850
+ function canUseLocalStorage() {
851
+ if (!canPersistSignals()) return false;
852
+ try {
853
+ if (typeof localStorage === "undefined") return false;
854
+ const key = "__drip_signal_test__";
855
+ localStorage.setItem(key, "1");
856
+ localStorage.removeItem(key);
857
+ return true;
858
+ } catch {
859
+ return false;
860
+ }
861
+ }
862
+ function canPersistSignals() {
863
+ try {
864
+ return signalPersistenceAllowed() === true;
865
+ } catch {
866
+ return false;
867
+ }
868
+ }
869
+ function readStore() {
870
+ if (!canUseLocalStorage()) return {};
871
+ try {
872
+ const parsed = JSON.parse(localStorage.getItem(STORE_KEY) || "{}");
873
+ return parsed && typeof parsed === "object" ? parsed : {};
874
+ } catch {
875
+ return {};
876
+ }
877
+ }
878
+ function writeStore(store) {
879
+ if (!canUseLocalStorage()) return;
880
+ try {
881
+ localStorage.setItem(STORE_KEY, JSON.stringify(store));
882
+ } catch {
883
+ }
884
+ }
885
+ function normalizeValue(value) {
886
+ if (value === null) return null;
887
+ if (typeof value === "string") return value.slice(0, 1e3);
888
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
889
+ if (typeof value === "boolean") return value;
890
+ if (Array.isArray(value)) {
891
+ return value.map((item) => String(item).trim()).filter(Boolean).slice(0, MAX_ARRAY_LENGTH);
892
+ }
893
+ if (value && typeof value === "object") {
894
+ try {
895
+ const json = JSON.stringify(value);
896
+ if (json.length > MAX_VALUE_BYTES) return void 0;
897
+ return JSON.parse(json);
898
+ } catch {
899
+ return void 0;
900
+ }
901
+ }
902
+ return void 0;
903
+ }
904
+ function compactStore(store) {
905
+ const ts = now();
906
+ const next = {};
907
+ for (const [key, entry] of Object.entries(store)) {
908
+ if (!entry || typeof entry !== "object") continue;
909
+ if (entry.expiresAt && entry.expiresAt <= ts) continue;
910
+ next[key] = entry;
911
+ }
912
+ return next;
913
+ }
914
+ function setSignal(key, value, ttlDays = DEFAULT_TTL_DAYS, source = "manual") {
915
+ if (!canPersistSignals()) return;
916
+ const normalized = normalizeValue(value);
917
+ if (normalized === void 0) return;
918
+ const store = compactStore(readStore());
919
+ store[key] = {
920
+ value: normalized,
921
+ updatedAt: now(),
922
+ expiresAt: now() + ttlMs(ttlDays)
923
+ };
924
+ writeStore(store);
925
+ signalSources[key] = source;
926
+ }
927
+ function getSignals(options = {}) {
928
+ const store = compactStore(readStore());
929
+ if (options.markVisitorSeen !== false) writeStore(store);
930
+ const signals = {};
931
+ for (const [key, entry] of Object.entries(store)) {
932
+ signals[key] = entry.value;
933
+ }
934
+ Object.assign(signals, runtimeSignals);
935
+ if (options.markVisitorSeen !== false && canPersistSignals()) {
936
+ try {
937
+ const seen = localStorage.getItem(VISITOR_SEEN_KEY) === "1";
938
+ signals["visitor.returning"] = seen;
939
+ localStorage.setItem(VISITOR_SEEN_KEY, "1");
940
+ } catch {
941
+ }
942
+ }
943
+ return signals;
944
+ }
945
+ function prependUnique(values, next) {
946
+ const clean = next.trim();
947
+ if (!clean) return values;
948
+ return [clean, ...values.filter((value) => value !== clean)].slice(0, MAX_ARRAY_LENGTH);
949
+ }
950
+ function readStringArray(key) {
951
+ const value = getSignals({ markVisitorSeen: false })[key];
952
+ return Array.isArray(value) ? value.map((item) => String(item)) : [];
953
+ }
954
+ function rememberProduct(product, ttlDays = DEFAULT_TTL_DAYS) {
955
+ const id = product.id == null ? "" : String(product.id);
956
+ const handle = product.handle ? String(product.handle) : "";
957
+ const productType = product.type ? String(product.type) : "";
958
+ if (id) {
959
+ setSignal("recently_viewed.product_ids", prependUnique(readStringArray("recently_viewed.product_ids"), id), ttlDays);
960
+ }
961
+ if (handle) {
962
+ setSignal("recently_viewed.product_handles", prependUnique(readStringArray("recently_viewed.product_handles"), handle), ttlDays);
963
+ }
964
+ if (productType) {
965
+ setSignal(
966
+ "recently_viewed.product_types",
967
+ prependUnique(readStringArray("recently_viewed.product_types"), productType),
968
+ ttlDays
969
+ );
970
+ }
971
+ if (product.tags?.length) {
972
+ const existing = readStringArray("recently_viewed.product_tags");
973
+ const next = product.tags.reduce((acc, tag) => prependUnique(acc, String(tag)), existing);
974
+ setSignal("recently_viewed.product_tags", next, ttlDays);
975
+ }
976
+ setSignal("recently_viewed.last_product", {
977
+ id: id || void 0,
978
+ handle: handle || void 0,
979
+ title: product.title || void 0,
980
+ type: productType || void 0,
981
+ vendor: product.vendor || void 0,
982
+ price: product.price ?? void 0
983
+ }, ttlDays);
984
+ }
985
+
986
+ // src/targeting.ts
987
+ var FAIL_CLOSED_TARGETING_ATTRIBUTE = "apex.internal.invalid_stored_targeting";
988
+ function normalizeRuntimeUrlRules(targeting) {
989
+ return normalizeUrlRules(targeting);
990
+ }
991
+ function normalizedHttpOrigins(value) {
992
+ const origins = /* @__PURE__ */ new Set();
993
+ for (const candidate of Array.isArray(value) ? value : value ? [value] : []) {
994
+ try {
995
+ const parsed = new URL(candidate);
996
+ if (/^https?:$/.test(parsed.protocol)) origins.add(parsed.origin);
997
+ } catch {
998
+ }
999
+ }
1000
+ return origins;
1001
+ }
1002
+ function matchUrlRules(url, rules, hostAgnosticIncludeOrigin) {
1003
+ if (!rules.length) return true;
1004
+ let hasInclude = false;
1005
+ let included = false;
1006
+ for (const rule of rules) {
1007
+ const match = evalUrlRuleForIncludeOrigin(url, rule, hostAgnosticIncludeOrigin);
1008
+ if (rule.include === false) {
1009
+ if (match) return false;
1010
+ } else {
1011
+ hasInclude = true;
1012
+ if (match) included = true;
1013
+ }
1014
+ }
1015
+ return included || !hasInclude;
1016
+ }
1017
+ function evalUrlRuleForIncludeOrigin(url, rule, origins) {
1018
+ const allowed = normalizedHttpOrigins(origins);
1019
+ let origin = "";
1020
+ try {
1021
+ origin = new URL(url, "https://_").origin;
1022
+ } catch {
1023
+ }
1024
+ return (rule.include === false || !allowed.size || urlRuleExplicitlyNamesHost(rule) || allowed.has(origin)) && evaluateUrlRule(url, rule);
1025
+ }
1026
+ function errorMessage(error) {
1027
+ return error instanceof Error ? error.message : String(error);
1028
+ }
1029
+ function jsErrorReason(error) {
1030
+ const message = errorMessage(error);
1031
+ return message ? `js_error: ${message}` : "js_error";
1032
+ }
1033
+ function evaluatePageAdvancedConditionResult(rule, ctx) {
1034
+ const code = rule.advancedCondition?.code?.trim();
1035
+ if (!code) return { matched: true };
1036
+ try {
1037
+ const url = new URL(
1038
+ ctx.url,
1039
+ typeof location !== "undefined" ? location.origin : "https://_"
1040
+ );
1041
+ const fn = new Function(
1042
+ "context",
1043
+ `"use strict";const attributes=context.attributes;const document=context.document;const location=context.location;const url=context.url;const window=context.window;return Boolean((function(){${/\breturn\b|;\s*$|\bthrow\b/.test(code) ? code : `return (${code});`}}).call(window));`
1044
+ );
1045
+ const matched = Boolean(fn({
1046
+ attributes: ctx.attributes,
1047
+ document: typeof document !== "undefined" ? document : void 0,
1048
+ location: typeof location !== "undefined" ? location : void 0,
1049
+ url,
1050
+ window: typeof window !== "undefined" ? window : void 0
1051
+ }));
1052
+ return { matched, reason: matched ? void 0 : "js_false" };
1053
+ } catch (error) {
1054
+ return { matched: false, reason: jsErrorReason(error), error };
1055
+ }
1056
+ }
1057
+ function evaluatePageAdvancedCondition(rule, ctx) {
1058
+ const result = evaluatePageAdvancedConditionResult(rule, ctx);
1059
+ if (!result.matched && result.reason?.startsWith("js_error")) {
1060
+ try {
1061
+ console.warn("[drip] Page trigger failed closed", {
1062
+ pageId: rule.id,
1063
+ pageName: rule.name,
1064
+ error: result.error
1065
+ });
1066
+ } catch {
1067
+ }
1068
+ }
1069
+ return result.matched;
1070
+ }
1071
+ function pageManualTriggerMatches(rule, ctx) {
1072
+ const mode = rule.trigger?.mode;
1073
+ if (mode !== "manual-callback" && mode !== "manual-api") return true;
1074
+ const signal = rule.trigger?.signal;
1075
+ if (!signal) return false;
1076
+ return ctx.attributes[signal] === true;
1077
+ }
1078
+ function matchPageTargetingRule(rule, ctx) {
1079
+ if (rule.disabled || rule.trigger?.disabled) return false;
1080
+ const includeRules = rule.includeRules ?? [];
1081
+ const excludeRules = (rule.excludeRules ?? []).map((item) => ({ ...item, include: false }));
1082
+ const urlMatched = matchUrlRules(ctx.url, [...includeRules, ...excludeRules]);
1083
+ return urlMatched && pageManualTriggerMatches(rule, ctx) && evaluatePageAdvancedCondition(rule, ctx);
1084
+ }
1085
+ function matchPageRules(rules, ctx) {
1086
+ if (!rules.length) return true;
1087
+ let hasInclude = false;
1088
+ let included = false;
1089
+ for (const rule of rules) {
1090
+ const matched = matchPageTargetingRule(rule, ctx);
1091
+ if (rule.include === false) {
1092
+ if (matched) return false;
1093
+ } else {
1094
+ hasInclude = true;
1095
+ if (matched) included = true;
1096
+ }
1097
+ }
1098
+ return included || !hasInclude;
1099
+ }
1100
+ function matchAttributeRule(attrs, rule) {
1101
+ if (rule.attribute === FAIL_CLOSED_TARGETING_ATTRIBUTE) return false;
1102
+ const value = attrs[rule.attribute];
1103
+ switch (rule.operator) {
1104
+ case "exists":
1105
+ return value !== void 0 && value !== null;
1106
+ case "equals":
1107
+ return value === rule.value;
1108
+ case "contains":
1109
+ return typeof value === "string" && typeof rule.value === "string" ? value.includes(rule.value) : false;
1110
+ case "in":
1111
+ return Array.isArray(rule.value) ? rule.value.includes(String(value)) : false;
1112
+ case "not_in":
1113
+ return Array.isArray(rule.value) ? !rule.value.includes(String(value)) : false;
1114
+ default:
1115
+ return false;
1116
+ }
1117
+ }
1118
+ var EMPTY_BROWSER_INFO = { name: "", version: "" };
1119
+ var EMPTY_OS_INFO = { name: "", version: "" };
1120
+ var highEntropyBrowserInfo = null;
1121
+ var highEntropyBrowserInfoRequested = false;
1122
+ function runtimeNavigator() {
1123
+ return typeof navigator === "undefined" ? void 0 : navigator;
1124
+ }
1125
+ function lower(value) {
1126
+ return typeof value === "string" ? value.toLowerCase() : "";
1127
+ }
1128
+ function normalizeVersion(value) {
1129
+ if (typeof value !== "string") return "";
1130
+ const match = value.match(/\d+(?:[._]\d+)*/);
1131
+ return match ? match[0].replace(/_/g, ".") : "";
1132
+ }
1133
+ function firstVersionMatch(ua, patterns) {
1134
+ for (const pattern of patterns) {
1135
+ const match = ua.match(pattern);
1136
+ const version = normalizeVersion(match?.[1]);
1137
+ if (version) return version;
1138
+ }
1139
+ return "";
1140
+ }
1141
+ function getUserAgent() {
1142
+ return runtimeNavigator()?.userAgent ?? "";
1143
+ }
1144
+ function getPlatform() {
1145
+ const nav = runtimeNavigator();
1146
+ return nav?.userAgentData?.platform || nav?.platform || "";
1147
+ }
1148
+ function getDeviceTypeFromUserAgent(ua, platform) {
1149
+ const normalizedUa = ua.toLowerCase();
1150
+ const normalizedPlatform = platform.toLowerCase();
1151
+ if (!normalizedUa && !normalizedPlatform) return "";
1152
+ const isIpad = /\bipad\b/.test(normalizedUa) || normalizedPlatform === "macintel" && typeof navigator !== "undefined" && Number(navigator.maxTouchPoints || 0) > 1;
1153
+ if (isIpad || /\btablet\b|playbook|silk|kindle|nexus 7|nexus 9|sm-t|gt-p/i.test(ua)) {
1154
+ return "tablet";
1155
+ }
1156
+ if (/\bmobi\b|iphone|ipod|android.*mobile|windows phone|blackberry|bb10/i.test(ua)) {
1157
+ return "mobile";
1158
+ }
1159
+ if (/android/i.test(ua)) return "tablet";
1160
+ if (/android/i.test(normalizedPlatform)) return "tablet";
1161
+ if (/windows|macintosh|mac os x|linux|x11|cros/i.test(ua) || normalizedPlatform) {
1162
+ return "desktop";
1163
+ }
1164
+ return "";
1165
+ }
1166
+ function getDeviceType() {
1167
+ const nav = runtimeNavigator();
1168
+ if (nav?.userAgentData?.mobile === true) return "mobile";
1169
+ const fromUa = getDeviceTypeFromUserAgent(getUserAgent(), getPlatform());
1170
+ if (fromUa) return fromUa;
1171
+ if (typeof window === "undefined") return "";
1172
+ const width = window.innerWidth || 0;
1173
+ if (width > 0 && width < 768) return "mobile";
1174
+ if (width >= 768 && width < 1024) return "tablet";
1175
+ return "desktop";
1176
+ }
1177
+ var DEVICE_TYPE_TOKENS = /* @__PURE__ */ new Set(["desktop", "mobile", "tablet"]);
1178
+ function normalizeDeviceType(value) {
1179
+ if (typeof value !== "string") return void 0;
1180
+ const normalized = value.trim().toLowerCase();
1181
+ return DEVICE_TYPE_TOKENS.has(normalized) ? normalized : void 0;
1182
+ }
1183
+ function browserInfoFromBrands(brands) {
1184
+ if (!Array.isArray(brands)) return EMPTY_BROWSER_INFO;
1185
+ for (const item of brands) {
1186
+ if (!item || typeof item !== "object") continue;
1187
+ const record = item;
1188
+ const brand = lower(record.brand);
1189
+ const version = normalizeVersion(record.version);
1190
+ if (!brand || brand.includes("not") || brand.includes("brand")) continue;
1191
+ if (brand.includes("edge")) return { name: "edge", version };
1192
+ if (brand.includes("chrome") || brand.includes("chromium")) return { name: "chrome", version };
1193
+ if (brand.includes("firefox")) return { name: "firefox", version };
1194
+ if (brand.includes("safari")) return { name: "safari", version };
1195
+ }
1196
+ return EMPTY_BROWSER_INFO;
1197
+ }
1198
+ function requestHighEntropyBrowserInfo() {
1199
+ const userAgentData = runtimeNavigator()?.userAgentData;
1200
+ if (!userAgentData?.getHighEntropyValues || highEntropyBrowserInfoRequested) return;
1201
+ highEntropyBrowserInfoRequested = true;
1202
+ try {
1203
+ userAgentData.getHighEntropyValues(["brands", "fullVersionList"]).then((values) => {
1204
+ if (!values || typeof values !== "object") return;
1205
+ const record = values;
1206
+ const info = browserInfoFromBrands(record.fullVersionList ?? record.brands);
1207
+ if (info.name) highEntropyBrowserInfo = info;
1208
+ }).catch(() => {
1209
+ });
1210
+ } catch {
1211
+ }
1212
+ }
1213
+ function getBrowserInfoFromClientHints() {
1214
+ const userAgentData = runtimeNavigator()?.userAgentData;
1215
+ const fullVersionInfo = browserInfoFromBrands(userAgentData?.fullVersionList);
1216
+ if (fullVersionInfo.name) return fullVersionInfo;
1217
+ if (highEntropyBrowserInfo?.name) return highEntropyBrowserInfo;
1218
+ requestHighEntropyBrowserInfo();
1219
+ return browserInfoFromBrands(userAgentData?.brands);
1220
+ }
1221
+ function browserWithVersionPriority(name, uaVersion, hinted) {
1222
+ return {
1223
+ name,
1224
+ version: hinted.name === name && hinted.version ? hinted.version : uaVersion || hinted.version
1225
+ };
1226
+ }
1227
+ function getBrowserInfo() {
1228
+ const hinted = getBrowserInfoFromClientHints();
1229
+ const ua = getUserAgent();
1230
+ if (!ua) return hinted;
1231
+ if (/Edg\//.test(ua)) {
1232
+ return browserWithVersionPriority("edge", firstVersionMatch(ua, [/Edg\/([\d.]+)/]), hinted);
1233
+ }
1234
+ if (/Firefox\//.test(ua)) {
1235
+ return browserWithVersionPriority("firefox", firstVersionMatch(ua, [/Firefox\/([\d.]+)/]), hinted);
1236
+ }
1237
+ if (/CriOS\//.test(ua)) {
1238
+ return browserWithVersionPriority("chrome", firstVersionMatch(ua, [/CriOS\/([\d.]+)/]), hinted);
1239
+ }
1240
+ if (/Chrome\//.test(ua) && !/Chromium|OPR\/|Edg\//.test(ua)) {
1241
+ return browserWithVersionPriority("chrome", firstVersionMatch(ua, [/Chrome\/([\d.]+)/]), hinted);
1242
+ }
1243
+ if (/Version\/[\d.]+.*Safari\//.test(ua) && !/Chrome|CriOS|FxiOS|Edg\//.test(ua)) {
1244
+ return browserWithVersionPriority("safari", firstVersionMatch(ua, [/Version\/([\d.]+)/]), hinted);
1245
+ }
1246
+ return hinted;
1247
+ }
1248
+ function getOsInfo() {
1249
+ const ua = getUserAgent();
1250
+ const platform = getPlatform();
1251
+ const normalized = `${ua} ${platform}`.toLowerCase();
1252
+ if (!normalized.trim()) return EMPTY_OS_INFO;
1253
+ if (/iphone|ipad|ipod/.test(normalized) || platform === "MacIntel" && typeof navigator !== "undefined" && Number(navigator.maxTouchPoints || 0) > 1) {
1254
+ return { name: "ios", version: firstVersionMatch(ua, [/OS ([\d_]+)/]) };
1255
+ }
1256
+ if (/android/.test(normalized)) {
1257
+ return { name: "android", version: firstVersionMatch(ua, [/Android ([\d.]+)/]) };
1258
+ }
1259
+ if (/windows/.test(normalized)) {
1260
+ return { name: "windows", version: firstVersionMatch(ua, [/Windows NT ([\d.]+)/]) };
1261
+ }
1262
+ if (/mac os x|macintosh|macintel/.test(normalized)) {
1263
+ return { name: "macos", version: firstVersionMatch(ua, [/Mac OS X ([\d_]+)/]) };
1264
+ }
1265
+ if (/linux|x11/.test(normalized)) {
1266
+ return { name: "linux", version: "" };
1267
+ }
1268
+ return EMPTY_OS_INFO;
1269
+ }
1270
+ function getLanguage() {
1271
+ const navLanguage = runtimeNavigator()?.language;
1272
+ if (typeof navLanguage === "string" && navLanguage.trim()) return navLanguage;
1273
+ if (typeof document !== "undefined") return document.documentElement.lang || "";
1274
+ return "";
1275
+ }
1276
+ function decodeCookieComponent(value) {
1277
+ try {
1278
+ return decodeURIComponent(value.replace(/\+/g, " "));
1279
+ } catch {
1280
+ return value;
1281
+ }
1282
+ }
1283
+ function getCookieValue(name) {
1284
+ if (typeof document === "undefined") return void 0;
1285
+ const needle = name.trim();
1286
+ if (!needle) return void 0;
1287
+ const pairs = document.cookie ? document.cookie.split(";") : [];
1288
+ for (const pair of pairs) {
1289
+ const index = pair.indexOf("=");
1290
+ const rawName = (index >= 0 ? pair.slice(0, index) : pair).trim();
1291
+ if (decodeCookieComponent(rawName) !== needle) continue;
1292
+ return decodeCookieComponent(index >= 0 ? pair.slice(index + 1) : "");
1293
+ }
1294
+ return void 0;
1295
+ }
1296
+ function parseNamedValue(value) {
1297
+ const raw = String(value ?? "").trim();
1298
+ const index = raw.indexOf("=");
1299
+ if (index < 0) return { name: raw, expected: "" };
1300
+ return {
1301
+ name: raw.slice(0, index).trim(),
1302
+ expected: raw.slice(index + 1)
1303
+ };
1304
+ }
1305
+ function getPersistedUtmValue(key) {
1306
+ try {
1307
+ const params = new URLSearchParams(location.search);
1308
+ const direct = params.get(key);
1309
+ if (direct) return direct;
1310
+ } catch {
1311
+ }
1312
+ try {
1313
+ const stored = sessionStorage.getItem("drip_utm");
1314
+ if (!stored) return "";
1315
+ const parsed = JSON.parse(stored);
1316
+ return typeof parsed[key] === "string" ? parsed[key] : "";
1317
+ } catch {
1318
+ return "";
1319
+ }
1320
+ }
1321
+ function normalizeReferrer() {
1322
+ if (typeof document === "undefined") return "";
1323
+ const referrer = document.referrer;
1324
+ if (!referrer) return "(direct)";
1325
+ try {
1326
+ const host = new URL(referrer).hostname;
1327
+ return host === location.hostname ? "(internal)" : host;
1328
+ } catch {
1329
+ return "(direct)";
1330
+ }
1331
+ }
1332
+ function normalizeTrafficSource(utmSource, referrer) {
1333
+ const source = typeof utmSource === "string" ? utmSource.toLowerCase() : "";
1334
+ const ref = typeof referrer === "string" ? referrer.toLowerCase() : "";
1335
+ if (source.includes("facebook") || source.includes("instagram") || source.includes("meta") || source.includes("tiktok")) {
1336
+ return "paid-social";
1337
+ }
1338
+ if (source.includes("google") || source.includes("bing") || ref.includes("google") || ref.includes("bing")) {
1339
+ return "search";
1340
+ }
1341
+ if (source.includes("email") || source.includes("newsletter")) return "email";
1342
+ if (source) return source;
1343
+ if (ref && ref !== "(direct)" && ref !== "(internal)") return "referral";
1344
+ return "direct";
1345
+ }
1346
+ function readShopifySignal(signal) {
1347
+ if (typeof window === "undefined") return void 0;
1348
+ const root = window.ShopifyAnalytics?.meta ?? window.Shopify?.analytics?.meta;
1349
+ if (!root || typeof root !== "object") return void 0;
1350
+ if (signal === "shopify.page_type") {
1351
+ return root.page?.pageType ?? root.page?.page_type ?? root.pageType;
1352
+ }
1353
+ if (signal === "shopify.product_type") {
1354
+ return root.product?.type ?? root.product?.product_type;
1355
+ }
1356
+ if (signal === "shopify.product_tag") {
1357
+ return root.product?.tags;
1358
+ }
1359
+ if (signal === "shopify.customer_tag") {
1360
+ return root.customer?.tags;
1361
+ }
1362
+ return void 0;
1363
+ }
1364
+ function buildTargetingAttributes(baseAttributes = {}, currentUrl, options = {}) {
1365
+ const attrs = { ...baseAttributes };
1366
+ const href = currentUrl || (typeof location !== "undefined" ? location.href : "");
1367
+ const persistSignals = options.persistSignals !== false;
1368
+ try {
1369
+ const parsed = new URL(href, typeof location !== "undefined" ? location.origin : "https://_");
1370
+ attrs["url.full"] ??= parsed.href;
1371
+ attrs["url.path"] ??= parsed.pathname;
1372
+ attrs["url.query"] ??= parsed.search;
1373
+ } catch {
1374
+ }
1375
+ attrs["utm.source"] ??= baseAttributes.utm_source ?? getPersistedUtmValue("utm_source");
1376
+ attrs["utm.medium"] ??= baseAttributes.utm_medium ?? getPersistedUtmValue("utm_medium");
1377
+ attrs["utm.campaign"] ??= baseAttributes.utm_campaign ?? getPersistedUtmValue("utm_campaign");
1378
+ attrs.referrer ??= normalizeReferrer();
1379
+ const deviceTypeOverride = attrs["device.type"] ?? baseAttributes.device;
1380
+ attrs["device.type"] = normalizeDeviceType(deviceTypeOverride) ?? normalizeDeviceType(getDeviceType()) ?? "";
1381
+ const browser = getBrowserInfo();
1382
+ attrs["device.browser"] ??= browser.name;
1383
+ attrs["device.browser.version"] ??= browser.version;
1384
+ const os = getOsInfo();
1385
+ attrs["device.os"] ??= os.name;
1386
+ attrs["device.os.version"] ??= os.version;
1387
+ attrs["navigator.language"] ??= getLanguage();
1388
+ attrs["traffic.source"] ??= normalizeTrafficSource(attrs["utm.source"], attrs.referrer);
1389
+ for (const signal of [
1390
+ "shopify.page_type",
1391
+ "shopify.product_type",
1392
+ "shopify.product_tag",
1393
+ "shopify.customer_tag"
1394
+ ]) {
1395
+ attrs[signal] ??= readShopifySignal(signal);
1396
+ }
1397
+ if (persistSignals) {
1398
+ const productMeta = readShopifyProductMeta();
1399
+ if (productMeta) rememberProduct(productMeta);
1400
+ }
1401
+ for (const [signal, value] of Object.entries(getSignals({ markVisitorSeen: persistSignals }))) {
1402
+ attrs[signal] ??= value;
1403
+ }
1404
+ return attrs;
1405
+ }
1406
+ function resolveUrlQueryValue(url, conditionValue) {
1407
+ const { name } = parseNamedValue(conditionValue);
1408
+ if (!name) return void 0;
1409
+ try {
1410
+ const parsed = new URL(url, typeof location !== "undefined" ? location.origin : "https://_");
1411
+ return parsed.searchParams.get(name) ?? void 0;
1412
+ } catch {
1413
+ return void 0;
1414
+ }
1415
+ }
1416
+ function resolveNamedConditionActual(condition, ctx) {
1417
+ if (condition.signal === "cookie.value") {
1418
+ const { name } = parseNamedValue(condition.value);
1419
+ return getCookieValue(name);
1420
+ }
1421
+ if (condition.signal === "url.query") {
1422
+ if (isLegacyRawQueryCondition(condition)) {
1423
+ return rawQueryString(ctx.url);
1424
+ }
1425
+ return resolveUrlQueryValue(ctx.url, condition.value);
1426
+ }
1427
+ return condition.js ? evaluateJsTrigger(condition, ctx) : resolveSignalValue(ctx.attributes, ctx.url, condition.signal);
1428
+ }
1429
+ function resolveNamedConditionExpected(condition) {
1430
+ if (condition.signal !== "cookie.value" && condition.signal !== "url.query") return condition.value;
1431
+ if (condition.operator === "is_set" || condition.operator === "is_empty") return condition.value;
1432
+ if (condition.signal === "url.query" && isLegacyRawQueryCondition(condition)) {
1433
+ return condition.value;
1434
+ }
1435
+ return parseNamedValue(condition.value).expected;
1436
+ }
1437
+ var LEGACY_RAW_QUERY_OPERATORS = /* @__PURE__ */ new Set(["contains", "not_contains", "starts_with", "ends_with"]);
1438
+ function isLegacyRawQueryCondition(condition) {
1439
+ if (!LEGACY_RAW_QUERY_OPERATORS.has(String(condition.operator))) return false;
1440
+ return !String(condition.value ?? "").includes("=");
1441
+ }
1442
+ function rawQueryString(url) {
1443
+ try {
1444
+ const parsed = new URL(url, typeof location !== "undefined" ? location.origin : "https://_");
1445
+ return parsed.search || "";
1446
+ } catch {
1447
+ return "";
1448
+ }
1449
+ }
1450
+ function readShopifyProductMeta() {
1451
+ if (typeof window === "undefined") return null;
1452
+ const root = window.ShopifyAnalytics?.meta ?? window.Shopify?.analytics?.meta;
1453
+ const product = root?.product;
1454
+ if (!product || typeof product !== "object") return null;
1455
+ const id = product.id ?? product.gid ?? product.product_id;
1456
+ if (!id && !product.handle) return null;
1457
+ return {
1458
+ id,
1459
+ handle: product.handle,
1460
+ title: product.title,
1461
+ type: product.type ?? product.product_type,
1462
+ vendor: product.vendor,
1463
+ tags: Array.isArray(product.tags) ? product.tags : void 0,
1464
+ price: product.price
1465
+ };
1466
+ }
1467
+ function resolveSignalValue(attrs, url, signal) {
1468
+ if (attrs[signal] !== void 0) return attrs[signal];
1469
+ try {
1470
+ const parsed = new URL(url, "https://_");
1471
+ if (signal === "url.full") return parsed.href;
1472
+ if (signal === "url.path") return parsed.pathname;
1473
+ if (signal === "url.query") return parsed.search;
1474
+ } catch {
1475
+ }
1476
+ return void 0;
1477
+ }
1478
+ function buildJsTriggerSource(code) {
1479
+ const trimmed = code.trim();
1480
+ if (!trimmed) return "return false;";
1481
+ if (/\breturn\b/.test(trimmed)) return trimmed;
1482
+ return `return (${trimmed});`;
1483
+ }
1484
+ function isActivateJsTrigger(condition) {
1485
+ const code = condition.js?.code ?? "";
1486
+ return condition.js?.mode === "activate" || /\bactivate\s*\(/.test(code) || /\bcallbackFn\b/.test(code);
1487
+ }
1488
+ function evaluateJsTrigger(condition, ctx) {
1489
+ return evaluateJsTriggerResult(condition, ctx).actual;
1490
+ }
1491
+ function evaluateJsTriggerResult(condition, ctx) {
1492
+ const code = condition.js?.code?.trim();
1493
+ if (!code || code.length > 4e3) return { actual: void 0 };
1494
+ if (isActivateJsTrigger(condition)) {
1495
+ return { actual: resolveSignalValue(ctx.attributes, ctx.url, condition.signal) };
1496
+ }
1497
+ try {
1498
+ const url = new URL(
1499
+ ctx.url,
1500
+ typeof location !== "undefined" ? location.origin : "https://_"
1501
+ );
1502
+ const apexTools = createApexRuntimeTools();
1503
+ const context = {
1504
+ apexTools,
1505
+ attributes: ctx.attributes,
1506
+ document: typeof document !== "undefined" ? document : void 0,
1507
+ location: typeof location !== "undefined" ? location : void 0,
1508
+ signal: condition.signal,
1509
+ url,
1510
+ value: condition.value,
1511
+ window: typeof window !== "undefined" ? window : void 0
1512
+ };
1513
+ const fn = new Function(
1514
+ "context",
1515
+ `"use strict";const attributes=context.attributes;const apexTools=context.apexTools;const document=context.document;const location=context.location;const signal=context.signal;const url=context.url;const value=context.value;const window=context.window;return Boolean((function(){${buildJsTriggerSource(code)}}).call(window));`
1516
+ );
1517
+ const actual = Boolean(fn(context));
1518
+ return { actual, reason: actual ? void 0 : "js_false" };
1519
+ } catch (error) {
1520
+ return { actual: false, reason: jsErrorReason(error) };
1521
+ }
1522
+ }
1523
+ function matchSegmentCondition(condition, ctx) {
1524
+ const actual = resolveNamedConditionActual(condition, ctx);
1525
+ const expected = resolveNamedConditionExpected(condition);
1526
+ return matchSegmentConditionValues2(condition, actual, expected);
1527
+ }
1528
+ function matchSegmentConditionValues2(condition, actual, expected) {
1529
+ return matchSegmentConditionValues(condition, actual, expected);
1530
+ }
1531
+ function matchSegmentRule(rule, ctx) {
1532
+ const groups = Array.isArray(rule.rules?.groups) ? rule.rules.groups.filter((group) => Array.isArray(group) && group.length > 0) : [];
1533
+ if (groups.length) {
1534
+ return groups.every(
1535
+ (group) => group.some((condition) => matchSegmentCondition(condition, ctx))
1536
+ );
1537
+ }
1538
+ const conditions = rule.rules?.conditions ?? [];
1539
+ if (!conditions.length) return true;
1540
+ if (rule.rules.operator === "or") {
1541
+ return conditions.some((condition) => matchSegmentCondition(condition, ctx));
1542
+ }
1543
+ return conditions.every((condition) => matchSegmentCondition(condition, ctx));
1544
+ }
1545
+ function matchSegmentRules(rules, ctx) {
1546
+ if (!rules.length) return true;
1547
+ let hasInclude = false;
1548
+ let included = false;
1549
+ for (const rule of rules) {
1550
+ const matched = matchSegmentRule(rule, ctx);
1551
+ if (rule.include === false) {
1552
+ if (matched) return false;
1553
+ } else {
1554
+ hasInclude = true;
1555
+ if (matched) included = true;
1556
+ }
1557
+ }
1558
+ return included || !hasInclude;
1559
+ }
1560
+ function matchesTargeting(targeting, ctx) {
1561
+ return evaluateTargetingGates(targeting, ctx).matched;
1562
+ }
1563
+ function evaluateTargetingGates(targeting, ctx) {
1564
+ const gates = [];
1565
+ if (!targeting) {
1566
+ return {
1567
+ matched: true,
1568
+ gates: [
1569
+ { id: "page", label: "Page check", status: "skipped", reason: "no_page_rules" },
1570
+ { id: "audience", label: "Audience check", status: "skipped", reason: "no_audience_rules" }
1571
+ ]
1572
+ };
1573
+ }
1574
+ const urlRules = normalizeRuntimeUrlRules(targeting);
1575
+ const pageRules = targeting.pageRules ?? [];
1576
+ const pageHasRules = urlRules.length > 0 || pageRules.length > 0;
1577
+ const pageMatched = pageHasRules ? matchUrlRules(
1578
+ ctx.url,
1579
+ urlRules,
1580
+ ctx.hostAgnosticIncludeOrigin
1581
+ ) && matchPageRules(pageRules, ctx) : true;
1582
+ gates.push({
1583
+ id: "page",
1584
+ label: "Page check",
1585
+ status: pageHasRules ? pageMatched ? "passed" : "failed" : "skipped",
1586
+ reason: pageHasRules ? pageMatched ? "url_matched" : "url_mismatch" : "no_page_rules",
1587
+ detail: pageHasRules ? { url: ctx.url, rules: urlRules.length, pageRules: pageRules.length } : void 0
1588
+ });
1589
+ if (!pageMatched) {
1590
+ gates.push({
1591
+ id: "audience",
1592
+ label: "Audience check",
1593
+ status: "skipped",
1594
+ reason: "page_mismatch",
1595
+ detail: { skipped: true }
1596
+ });
1597
+ return { matched: false, gates };
1598
+ }
1599
+ let attributesMatched = true;
1600
+ const attributeRules = targeting.attributes ?? [];
1601
+ for (const rule of attributeRules) {
1602
+ if (!matchAttributeRule(ctx.attributes, rule)) {
1603
+ attributesMatched = false;
1604
+ break;
1605
+ }
1606
+ }
1607
+ const segmentRules = targeting.segmentRules ?? [];
1608
+ const segmentRefs = targeting.segments ?? [];
1609
+ const segmentsExpanded = segmentRefs.every(
1610
+ (ref) => segmentRules.some((rule) => rule.id === ref.id)
1611
+ );
1612
+ const segmentRulesMatched = segmentsExpanded && matchSegmentRules(segmentRules, ctx);
1613
+ const audienceHasRules = attributeRules.length > 0 || segmentRules.length > 0 || segmentRefs.length > 0;
1614
+ const audienceMatched = attributesMatched && segmentRulesMatched;
1615
+ gates.push({
1616
+ id: "audience",
1617
+ label: "Audience check",
1618
+ status: audienceHasRules ? audienceMatched ? "passed" : "failed" : "skipped",
1619
+ reason: audienceHasRules ? audienceMatched ? "audience_matched" : !segmentsExpanded ? "segments_unexpanded" : !attributesMatched ? "attribute_mismatch" : "segment_mismatch" : "no_audience_rules",
1620
+ detail: audienceHasRules ? { attributeRules: attributeRules.length, segmentRules: segmentRules.length } : void 0
1621
+ });
1622
+ return {
1623
+ matched: pageMatched && audienceMatched,
1624
+ gates
1625
+ };
1626
+ }
1627
+
1628
+ // ../shared-runtime/src/weekday-schedule.ts
1629
+ var WEEKDAYS = [
1630
+ "monday",
1631
+ "tuesday",
1632
+ "wednesday",
1633
+ "thursday",
1634
+ "friday",
1635
+ "saturday",
1636
+ "sunday"
1637
+ ];
1638
+ var warnedInvalidTimezones = /* @__PURE__ */ new Set();
1639
+ function normalizeWeekdaySchedule(value) {
1640
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
1641
+ const record = value;
1642
+ if (Object.keys(record).some(
1643
+ (key) => key !== "excludedDays" && key !== "timezone"
1644
+ )) {
1645
+ return null;
1646
+ }
1647
+ if (!Array.isArray(record.excludedDays)) return null;
1648
+ if (!record.excludedDays.every(
1649
+ (day) => typeof day === "string" && WEEKDAYS.includes(day)
1650
+ )) {
1651
+ return null;
1652
+ }
1653
+ const excludedDays = Array.from(new Set(record.excludedDays));
1654
+ if (excludedDays.length < 1 || excludedDays.length > 6) return null;
1655
+ if (typeof record.timezone !== "string") return null;
1656
+ const timezone = record.timezone.trim();
1657
+ if (!timezone) return null;
1658
+ try {
1659
+ new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
1660
+ } catch {
1661
+ return null;
1662
+ }
1663
+ return { excludedDays, timezone };
1664
+ }
1665
+ function warningTimezone(value) {
1666
+ return value && typeof value === "object" && !Array.isArray(value) ? String(value.timezone ?? "unknown") : "unknown";
1667
+ }
1668
+ function isWeekdayExcluded(schedule, now2) {
1669
+ if (!schedule) return false;
1670
+ try {
1671
+ const weekday = new Intl.DateTimeFormat("en-US", {
1672
+ weekday: "long",
1673
+ timeZone: schedule.timezone
1674
+ }).format(now2).toLowerCase();
1675
+ return Array.isArray(schedule.excludedDays) && schedule.excludedDays.includes(weekday);
1676
+ } catch {
1677
+ return false;
1678
+ }
1679
+ }
1680
+ function weekdayScheduleBlocksDelivery(schedule, now2, logPrefix) {
1681
+ if (schedule == null) return false;
1682
+ const normalized = normalizeWeekdaySchedule(schedule);
1683
+ if (!normalized) {
1684
+ const timezone = warningTimezone(schedule);
1685
+ const warnKey = `${logPrefix}:${timezone}`;
1686
+ if (!warnedInvalidTimezones.has(warnKey)) {
1687
+ warnedInvalidTimezones.add(warnKey);
1688
+ console.warn(
1689
+ `${logPrefix} Invalid weekday schedule (timezone "${timezone}" or malformed shape); delivery will fail open.`
1690
+ );
1691
+ }
1692
+ return false;
1693
+ }
1694
+ return isWeekdayExcluded(normalized, now2);
1695
+ }
1696
+
1697
+ // src/weekday-schedule.ts
1698
+ function weekdayScheduleBlocksDelivery2(schedule, now2) {
1699
+ return weekdayScheduleBlocksDelivery(schedule, now2, "[drip]");
1700
+ }
1701
+
1702
+ // src/flags.ts
1703
+ function evaluateFlag(flagId, experiments, context) {
1704
+ const assignment = evaluateFlagAssignment(flagId, experiments, context);
1705
+ return assignment !== null && !assignment.isControl;
1706
+ }
1707
+ function evaluateFlagAssignment(flagId, experiments, context) {
1708
+ const experiment = experiments.find((e) => e.id === flagId);
1709
+ if (!experiment) return null;
1710
+ const forcedVariationId = context?.forceMap?.[experiment.id];
1711
+ const forcedIndex = forcedVariationId ? experiment.variations.findIndex(
1712
+ (variation2) => variation2.id === forcedVariationId
1713
+ ) : -1;
1714
+ const forcedVariation = forcedIndex >= 0 ? experiment.variations[forcedIndex] : null;
1715
+ if (!forcedVariation && weekdayScheduleBlocksDelivery2(experiment.weekdaySchedule, /* @__PURE__ */ new Date())) {
1716
+ return null;
1717
+ }
1718
+ const url = context?.url ?? (typeof location !== "undefined" ? location.href : "");
1719
+ const attributes = buildTargetingAttributes(context?.attributes ?? {}, url);
1720
+ if (!matchesTargeting(experiment.targeting, { url, attributes })) {
1721
+ return null;
1722
+ }
1723
+ const userId = getUserId(context?.userId);
1724
+ const hashValue = hashToFloat(experiment.id, userId);
1725
+ if (!forcedVariation && !inTrafficAllocation(hashValue, experiment.trafficAllocation ?? 1)) {
1726
+ return null;
1727
+ }
1728
+ const allocation = experiment.trafficAllocation ?? 1;
1729
+ const variationHashValue = allocation >= 1 ? hashValue : variationHashToFloat(experiment.id, userId);
1730
+ const weights = experiment.variations.map((v) => v.weight);
1731
+ const index = forcedVariation ? forcedIndex : chooseVariation(variationHashValue, weights);
1732
+ if (index < 0 || index >= experiment.variations.length) return null;
1733
+ const variation = forcedVariation ?? experiment.variations[index];
1734
+ return {
1735
+ experimentId: experiment.id,
1736
+ experimentName: void 0,
1737
+ variationId: variation.id,
1738
+ pageActive: true,
1739
+ index,
1740
+ mutations: variation.mutations ?? [],
1741
+ value: variation.value,
1742
+ featureFlag: experiment.featureFlag,
1743
+ kind: experiment.kind ?? (experiment.featureFlag ? "feature_flag" : "experiment"),
1744
+ css: variation.css,
1745
+ js: variation.js,
1746
+ resetJs: variation.resetJs,
1747
+ externalCssUrl: variation.externalCssUrl,
1748
+ externalJsUrl: variation.externalJsUrl,
1749
+ harnessSession: variation.harnessSession,
1750
+ isControl: Boolean(variation.isControl),
1751
+ hashValue: userId
1752
+ };
1753
+ }
1754
+ function evaluateFlagValue(flagId, experiments, context, fallback) {
1755
+ const assignment = evaluateFlagAssignment(flagId, experiments, context);
1756
+ if (!assignment) return fallback;
1757
+ return assignment.value === void 0 ? fallback : assignment.value;
1758
+ }
1759
+ function defineFlagExperiment(def) {
1760
+ const rollout = Math.max(0, Math.min(100, def.rolloutPercent ?? 0)) / 100;
1761
+ const flag = defineExperiment({
1762
+ slug: def.slug,
1763
+ name: def.name,
1764
+ trafficAllocation: 1,
1765
+ targeting: def.targeting,
1766
+ variations: [
1767
+ {
1768
+ id: "control",
1769
+ weight: 1 - rollout,
1770
+ mutations: [],
1771
+ isControl: true,
1772
+ value: def.offValue ?? false
1773
+ },
1774
+ {
1775
+ id: "enabled",
1776
+ weight: rollout,
1777
+ mutations: [],
1778
+ value: def.onValue ?? true
1779
+ }
1780
+ ]
1781
+ });
1782
+ return {
1783
+ ...flag,
1784
+ variations: flag.variations.map((variation) => ({ ...variation })),
1785
+ featureFlag: { key: def.slug }
1786
+ };
1787
+ }
1788
+ //# sourceMappingURL=flags.cjs.map