@prosdevlab/experience-sdk 0.1.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.
@@ -0,0 +1,451 @@
1
+ export { bannerPlugin, debugPlugin, frequencyPlugin } from '@prosdevlab/experience-sdk-plugins';
2
+
3
+ /**
4
+ * Type Definitions for Experience SDK
5
+ *
6
+ * These types define the public API surface and should remain stable.
7
+ * Breaking changes to these types require a major version bump.
8
+ */
9
+ /**
10
+ * Experience Definition
11
+ *
12
+ * An experience represents a targeted piece of content (banner, modal, tooltip)
13
+ * that should be shown to users based on targeting rules.
14
+ */
15
+ interface Experience {
16
+ /** Unique identifier for the experience */
17
+ id: string;
18
+ /** Type of experience to render */
19
+ type: 'banner' | 'modal' | 'tooltip';
20
+ /** Rules that determine when/where to show this experience */
21
+ targeting: TargetingRules;
22
+ /** Content to display (type-specific) */
23
+ content: ExperienceContent;
24
+ /** Optional frequency capping configuration */
25
+ frequency?: FrequencyConfig;
26
+ /** Priority for ordering (higher = more important, default: 0) */
27
+ priority?: number;
28
+ }
29
+ /**
30
+ * Targeting Rules
31
+ *
32
+ * Rules that determine when an experience should be shown.
33
+ * All rules must pass for the experience to be shown.
34
+ */
35
+ interface TargetingRules {
36
+ /** URL-based targeting */
37
+ url?: UrlRule;
38
+ /** Frequency-based targeting */
39
+ frequency?: FrequencyRule;
40
+ }
41
+ /**
42
+ * URL Targeting Rule
43
+ *
44
+ * Determines if current URL matches the target.
45
+ * Multiple patterns can be specified; first match wins.
46
+ */
47
+ interface UrlRule {
48
+ /** URL must contain this string */
49
+ contains?: string;
50
+ /** URL must exactly match this string */
51
+ equals?: string;
52
+ /** URL must match this regular expression */
53
+ matches?: RegExp;
54
+ }
55
+ /**
56
+ * Frequency Targeting Rule
57
+ *
58
+ * Limits how often an experience can be shown.
59
+ */
60
+ interface FrequencyRule {
61
+ /** Maximum number of times to show */
62
+ max: number;
63
+ /** Time period for the cap */
64
+ per: 'session' | 'day' | 'week';
65
+ }
66
+ /**
67
+ * Frequency Configuration
68
+ *
69
+ * Configuration for frequency capping at the experience level.
70
+ */
71
+ interface FrequencyConfig {
72
+ /** Maximum number of impressions */
73
+ max: number;
74
+ /** Time period for the cap */
75
+ per: 'session' | 'day' | 'week';
76
+ }
77
+ /**
78
+ * Experience Content (type-specific)
79
+ *
80
+ * Union type for all possible experience content types.
81
+ */
82
+ type ExperienceContent = BannerContent | ModalContent | TooltipContent;
83
+ /**
84
+ * Banner Content
85
+ *
86
+ * Content for banner-type experiences.
87
+ */
88
+ interface BannerContent {
89
+ /** Banner title/heading */
90
+ title: string;
91
+ /** Banner message/body text */
92
+ message: string;
93
+ /** Whether the banner can be dismissed */
94
+ dismissable?: boolean;
95
+ }
96
+ /**
97
+ * Modal Content
98
+ *
99
+ * Content for modal-type experiences.
100
+ */
101
+ interface ModalContent {
102
+ /** Modal title */
103
+ title: string;
104
+ /** Modal body content */
105
+ body: string;
106
+ /** Optional action buttons */
107
+ actions?: ModalAction[];
108
+ }
109
+ /**
110
+ * Tooltip Content
111
+ *
112
+ * Content for tooltip-type experiences.
113
+ */
114
+ interface TooltipContent {
115
+ /** Tooltip text */
116
+ text: string;
117
+ /** Position relative to target element */
118
+ position?: 'top' | 'bottom' | 'left' | 'right';
119
+ }
120
+ /**
121
+ * Modal Action Button
122
+ *
123
+ * Defines an action button in a modal.
124
+ */
125
+ interface ModalAction {
126
+ /** Button label text */
127
+ label: string;
128
+ /** Action to perform when clicked */
129
+ action: 'close' | 'confirm' | 'dismiss';
130
+ }
131
+ /**
132
+ * Evaluation Context
133
+ *
134
+ * Context information used to evaluate targeting rules.
135
+ * This is the input to the decision-making process.
136
+ */
137
+ interface Context {
138
+ /** Current page URL */
139
+ url?: string;
140
+ /** User-specific context */
141
+ user?: UserContext;
142
+ /** Evaluation timestamp */
143
+ timestamp?: number;
144
+ /** Custom context properties */
145
+ custom?: Record<string, any>;
146
+ }
147
+ /**
148
+ * User Context
149
+ *
150
+ * User-specific information for targeting.
151
+ */
152
+ interface UserContext {
153
+ /** User identifier */
154
+ id?: string;
155
+ /** Whether user is a returning visitor */
156
+ returning?: boolean;
157
+ /** Additional custom user properties */
158
+ [key: string]: any;
159
+ }
160
+ /**
161
+ * Decision Output - Core of Explainability
162
+ *
163
+ * The result of evaluating experiences against a context.
164
+ * Includes human-readable reasons and machine-readable trace.
165
+ */
166
+ interface Decision {
167
+ /** Whether to show an experience */
168
+ show: boolean;
169
+ /** ID of the experience to show (if show=true) */
170
+ experienceId?: string;
171
+ /** Human-readable reasons for the decision */
172
+ reasons: string[];
173
+ /** Machine-readable trace of evaluation steps */
174
+ trace: TraceStep[];
175
+ /** Context used for evaluation */
176
+ context: Context;
177
+ /** Metadata about the evaluation */
178
+ metadata: DecisionMetadata;
179
+ }
180
+ /**
181
+ * Trace Step
182
+ *
183
+ * A single step in the evaluation trace.
184
+ * Provides detailed information about each evaluation step.
185
+ */
186
+ interface TraceStep {
187
+ /** Name of the evaluation step */
188
+ step: string;
189
+ /** When this step started (unix timestamp) */
190
+ timestamp: number;
191
+ /** How long this step took (milliseconds) */
192
+ duration: number;
193
+ /** Input to this step */
194
+ input?: any;
195
+ /** Output from this step */
196
+ output?: any;
197
+ /** Whether this step passed */
198
+ passed: boolean;
199
+ }
200
+ /**
201
+ * Decision Metadata
202
+ *
203
+ * Metadata about the evaluation process.
204
+ */
205
+ interface DecisionMetadata {
206
+ /** When evaluation completed (unix timestamp) */
207
+ evaluatedAt: number;
208
+ /** Total time taken (milliseconds) */
209
+ totalDuration: number;
210
+ /** Number of experiences evaluated */
211
+ experiencesEvaluated: number;
212
+ }
213
+ /**
214
+ * Experience SDK Configuration
215
+ *
216
+ * Configuration options for the Experience SDK.
217
+ */
218
+ interface ExperienceConfig {
219
+ /** Enable debug mode (verbose logging) */
220
+ debug?: boolean;
221
+ /** Storage backend to use */
222
+ storage?: 'session' | 'local' | 'memory';
223
+ /** Additional custom configuration */
224
+ [key: string]: any;
225
+ }
226
+ /**
227
+ * Runtime State
228
+ *
229
+ * Internal runtime state (exposed for inspection/debugging).
230
+ */
231
+ interface RuntimeState {
232
+ /** Whether the runtime has been initialized */
233
+ initialized: boolean;
234
+ /** Registered experiences */
235
+ experiences: Map<string, Experience>;
236
+ /** History of decisions made */
237
+ decisions: Decision[];
238
+ /** Current configuration */
239
+ config: ExperienceConfig;
240
+ }
241
+
242
+ /**
243
+ * Experience Runtime
244
+ *
245
+ * Core class that manages experience registration and evaluation.
246
+ * Built on @lytics/sdk-kit for plugin system and lifecycle management.
247
+ *
248
+ * Design principles:
249
+ * - Pure functions for evaluation logic (easy to test)
250
+ * - Event-driven architecture (extensible via plugins)
251
+ * - Explainability-first (every decision has reasons)
252
+ */
253
+ declare class ExperienceRuntime {
254
+ private sdk;
255
+ private experiences;
256
+ private decisions;
257
+ private initialized;
258
+ private destroyed;
259
+ constructor(config?: ExperienceConfig);
260
+ /**
261
+ * Initialize the runtime
262
+ */
263
+ init(config?: ExperienceConfig): Promise<void>;
264
+ /**
265
+ * Register an experience
266
+ */
267
+ register(id: string, experience: Omit<Experience, 'id'>): void;
268
+ /**
269
+ * Evaluate experiences against context
270
+ * Returns decision with explainability
271
+ * First match wins (use evaluateAll() for multiple experiences)
272
+ */
273
+ evaluate(context?: Partial<Context>): Decision;
274
+ /**
275
+ * Evaluate all experiences against context
276
+ * Returns multiple decisions (sorted by priority)
277
+ * All matching experiences will be shown
278
+ */
279
+ evaluateAll(context?: Partial<Context>): Decision[];
280
+ /**
281
+ * Explain a specific experience
282
+ */
283
+ explain(experienceId: string): Decision | null;
284
+ /**
285
+ * Get runtime state (for inspection)
286
+ */
287
+ getState(): RuntimeState;
288
+ /**
289
+ * Event subscription (proxy to SDK)
290
+ */
291
+ on(event: string, handler: (...args: any[]) => void): () => void;
292
+ /**
293
+ * Destroy runtime
294
+ */
295
+ destroy(): Promise<void>;
296
+ }
297
+ /**
298
+ * Build evaluation context from partial input
299
+ * Pure function - no side effects
300
+ */
301
+ declare function buildContext(partial?: Partial<Context>): Context;
302
+ /**
303
+ * Evaluate an experience against context
304
+ * Pure function - returns reasons and trace
305
+ */
306
+ declare function evaluateExperience(experience: Experience, context: Context): {
307
+ matched: boolean;
308
+ reasons: string[];
309
+ trace: TraceStep[];
310
+ };
311
+ /**
312
+ * Evaluate URL targeting rule
313
+ * Pure function - deterministic output
314
+ */
315
+ declare function evaluateUrlRule(rule: UrlRule, url?: string): boolean;
316
+
317
+ /**
318
+ * Singleton Pattern Implementation
319
+ *
320
+ * Provides a default singleton instance with convenient wrapper functions
321
+ * for simple use cases, plus createInstance() for advanced scenarios.
322
+ */
323
+
324
+ /**
325
+ * Create a new Experience SDK instance
326
+ *
327
+ * Use this for advanced scenarios where you need multiple isolated runtimes.
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * import { createInstance } from '@prosdevlab/experience-sdk';
332
+ *
333
+ * const exp = createInstance({ debug: true });
334
+ * await exp.init();
335
+ * exp.register('welcome', { ... });
336
+ * ```
337
+ */
338
+ declare function createInstance(config?: ExperienceConfig): ExperienceRuntime;
339
+ /**
340
+ * Initialize the Experience SDK
341
+ *
342
+ * @example
343
+ * ```typescript
344
+ * import { init } from '@prosdevlab/experience-sdk';
345
+ * await init({ debug: true });
346
+ * ```
347
+ */
348
+ declare function init(config?: ExperienceConfig): Promise<void>;
349
+ /**
350
+ * Register an experience
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * import { register } from '@prosdevlab/experience-sdk';
355
+ *
356
+ * register('welcome-banner', {
357
+ * type: 'banner',
358
+ * targeting: { url: { contains: '/' } },
359
+ * content: { title: 'Welcome!', message: 'Thanks for visiting' }
360
+ * });
361
+ * ```
362
+ */
363
+ declare function register(id: string, experience: Omit<Experience, 'id'>): void;
364
+ /**
365
+ * Evaluate experiences against current context
366
+ * First match wins (use evaluateAll() for multiple experiences)
367
+ *
368
+ * @example
369
+ * ```typescript
370
+ * import { evaluate } from '@prosdevlab/experience-sdk';
371
+ *
372
+ * const decision = evaluate({ url: window.location.href });
373
+ * if (decision.show) {
374
+ * console.log('Show experience:', decision.experienceId);
375
+ * console.log('Reasons:', decision.reasons);
376
+ * }
377
+ * ```
378
+ */
379
+ declare function evaluate(context?: Partial<Context>): Decision;
380
+ /**
381
+ * Evaluate all experiences against current context
382
+ * Returns array of decisions sorted by priority (higher = more important)
383
+ * All matching experiences will be shown
384
+ *
385
+ * @example
386
+ * ```typescript
387
+ * import { evaluateAll } from '@prosdevlab/experience-sdk';
388
+ *
389
+ * const decisions = evaluateAll({ url: window.location.href });
390
+ * decisions.forEach(decision => {
391
+ * if (decision.show) {
392
+ * console.log('Show:', decision.experienceId);
393
+ * console.log('Reasons:', decision.reasons);
394
+ * }
395
+ * });
396
+ * ```
397
+ */
398
+ declare function evaluateAll(context?: Partial<Context>): Decision[];
399
+ /**
400
+ * Explain why a specific experience would/wouldn't show
401
+ *
402
+ * @example
403
+ * ```typescript
404
+ * import { explain } from '@prosdevlab/experience-sdk';
405
+ *
406
+ * const explanation = explain('welcome-banner');
407
+ * console.log('Would show?', explanation?.show);
408
+ * console.log('Reasons:', explanation?.reasons);
409
+ * ```
410
+ */
411
+ declare function explain(experienceId: string): Decision | null;
412
+ /**
413
+ * Get current runtime state
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * import { getState } from '@prosdevlab/experience-sdk';
418
+ *
419
+ * const state = getState();
420
+ * console.log('Initialized?', state.initialized);
421
+ * console.log('Experiences:', Array.from(state.experiences.keys()));
422
+ * ```
423
+ */
424
+ declare function getState(): RuntimeState;
425
+ /**
426
+ * Subscribe to SDK events
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * import { on } from '@prosdevlab/experience-sdk';
431
+ *
432
+ * const unsubscribe = on('experiences:evaluated', (decision) => {
433
+ * console.log('Evaluation:', decision);
434
+ * });
435
+ *
436
+ * // Later: unsubscribe()
437
+ * ```
438
+ */
439
+ declare function on(event: string, handler: (...args: unknown[]) => void): () => void;
440
+ /**
441
+ * Destroy the SDK instance
442
+ *
443
+ * @example
444
+ * ```typescript
445
+ * import { destroy } from '@prosdevlab/experience-sdk';
446
+ * await destroy();
447
+ * ```
448
+ */
449
+ declare function destroy(): Promise<void>;
450
+
451
+ export { type BannerContent, type Context, type Decision, type DecisionMetadata, type Experience, type ExperienceConfig, type ExperienceContent, ExperienceRuntime, type FrequencyConfig, type FrequencyRule, type ModalAction, type ModalContent, type RuntimeState, type TargetingRules, type TooltipContent, type TraceStep, type UrlRule, type UserContext, buildContext, createInstance, destroy, evaluate, evaluateAll, evaluateExperience, evaluateUrlRule, explain, getState, init, on, register };
@@ -0,0 +1,61 @@
1
+ var experiences=(function(exports){'use strict';var A=class{storage=new Map;get(e){return this.storage.get(e)??null}set(e,t,r){this.storage.set(e,t);}remove(e){this.storage.delete(e);}clear(){this.storage.clear();}isSupported(){return true}},Z=class{fallback=null;defaultOptions;constructor(e={}){this.defaultOptions={path:"/",sameSite:"lax",...e};}get(e){if(!this.isSupported())return this.getFallback().get(e);try{let t=`${encodeURIComponent(e)}=`,r=document.cookie.split(";");for(let n of r)if(n=n.trim(),n.startsWith(t))return decodeURIComponent(n.substring(t.length));return null}catch(t){return console.warn("Cookie get failed:",t),this.getFallback().get(e)}}set(e,t,r){if(!this.isSupported()){this.getFallback().set(e,t);return}try{let n={...this.defaultOptions,...r},a=[`${encodeURIComponent(e)}=${encodeURIComponent(t)}`];if(n.ttl){let f=new Date;f.setTime(f.getTime()+n.ttl*1e3),a.push(`expires=${f.toUTCString()}`);}n.path&&a.push(`path=${n.path}`),n.domain&&a.push(`domain=${n.domain}`),n.secure&&a.push("secure"),n.sameSite&&a.push(`samesite=${n.sameSite}`),document.cookie=a.join("; ");}catch(n){console.warn("Cookie set failed, falling back to memory:",n),this.getFallback().set(e,t);}}remove(e){if(!this.isSupported()){this.getFallback().remove(e);return}try{let t=this.defaultOptions,r=[`${encodeURIComponent(e)}=`,"expires=Thu, 01 Jan 1970 00:00:00 UTC"];t.path&&r.push(`path=${t.path}`),t.domain&&r.push(`domain=${t.domain}`),document.cookie=r.join("; ");}catch(t){console.warn("Cookie remove failed:",t),this.getFallback().remove(e);}}clear(){if(!this.isSupported()){this.getFallback().clear();return}try{let e=document.cookie.split(";");for(let t of e){t=t.trim();let r=t.indexOf("="),n=r>-1?t.substring(0,r):t;this.remove(decodeURIComponent(n));}}catch(e){console.warn("Cookie clear failed:",e),this.getFallback().clear();}}isSupported(){try{if(typeof document>"u"||!document.cookie)return !1;let e="__cookie_test__";document.cookie=`${e}=test; path=/`;let t=document.cookie.indexOf(e)!==-1;return document.cookie=`${e}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/`,t}catch{return false}}getFallback(){return this.fallback||(this.fallback=new A),this.fallback}},ee=class{fallback=null;get(e){if(!this.isSupported())return this.getFallback().get(e);try{return localStorage.getItem(e)}catch(t){return console.warn("localStorage.getItem failed:",t),this.getFallback().get(e)}}set(e,t,r){if(!this.isSupported()){this.getFallback().set(e,t);return}try{localStorage.setItem(e,t);}catch(n){console.warn("localStorage.setItem failed, falling back to memory:",n),this.getFallback().set(e,t);}}remove(e){if(!this.isSupported()){this.getFallback().remove(e);return}try{localStorage.removeItem(e);}catch(t){console.warn("localStorage.removeItem failed:",t),this.getFallback().remove(e);}}clear(){if(!this.isSupported()){this.getFallback().clear();return}try{localStorage.clear();}catch(e){console.warn("localStorage.clear failed:",e),this.getFallback().clear();}}isSupported(){try{let e="__storage_test__";return localStorage.setItem(e,"test"),localStorage.removeItem(e),!0}catch{return false}}getFallback(){return this.fallback||(this.fallback=new A),this.fallback}},te=class{fallback=null;get(e){if(!this.isSupported())return this.getFallback().get(e);try{return sessionStorage.getItem(e)}catch(t){return console.warn("sessionStorage.getItem failed:",t),this.getFallback().get(e)}}set(e,t,r){if(!this.isSupported()){this.getFallback().set(e,t);return}try{sessionStorage.setItem(e,t);}catch(n){console.warn("sessionStorage.setItem failed, falling back to memory:",n),this.getFallback().set(e,t);}}remove(e){if(!this.isSupported()){this.getFallback().remove(e);return}try{sessionStorage.removeItem(e);}catch(t){console.warn("sessionStorage.removeItem failed:",t),this.getFallback().remove(e);}}clear(){if(!this.isSupported()){this.getFallback().clear();return}try{sessionStorage.clear();}catch(e){console.warn("sessionStorage.clear failed:",e),this.getFallback().clear();}}isSupported(){try{let e="__storage_test__";return sessionStorage.setItem(e,"test"),sessionStorage.removeItem(e),!0}catch{return false}}getFallback(){return this.fallback||(this.fallback=new A),this.fallback}},D=(e,t,r)=>{e.ns("storage"),e.defaults({storage:{backend:"localStorage",namespace:"",path:"/",sameSite:"lax"}});let n=()=>r.get("storage.backend")??"localStorage",a=()=>r.get("storage.namespace")??"",f=()=>r.get("storage.ttl"),y=()=>({domain:r.get("storage.domain"),path:r.get("storage.path")??"/",secure:r.get("storage.secure"),sameSite:r.get("storage.sameSite")??"lax"}),i={};function s(o){if(!i[o])switch(o){case "localStorage":i[o]=new ee;break;case "sessionStorage":i[o]=new te;break;case "cookie":i[o]=new Z(y());break;case "memory":i[o]=new A;break}return i[o]}function l(o,c){let u=c??a();return u?`${u}:${o}`:o}function b(o){return o.expires?Date.now()>o.expires:false}function x(o,c,u){let h=u?.backend??n(),w=s(h),S=l(o,u?.namespace),k={value:c},C=u?.ttl??f();C&&(k.expires=Date.now()+C*1e3);let d=JSON.stringify(k);w.set(S,d,u),e.emit("storage:set",{key:o,value:c,backend:h});}function I(o,c){let u=c?.backend??n(),h=s(u),w=l(o,c?.namespace),S=h.get(w);if(!S)return null;try{let k=JSON.parse(S);return b(k)?(h.remove(w),e.emit("storage:expired",{key:o,backend:u}),null):(e.emit("storage:get",{key:o,backend:u}),k.value)}catch(k){return console.warn("Failed to parse stored value:",k),h.remove(w),null}}function m(o,c){let u=c?.backend??n(),h=s(u),w=l(o,c?.namespace);h.remove(w),e.emit("storage:remove",{key:o,backend:u});}function p(o){let c=o?.backend??n(),u=s(c),h=o?.namespace;if(!h){u.clear(),e.emit("storage:clear",{backend:c});return}if(c==="localStorage"||c==="sessionStorage"){let w=c==="localStorage"?localStorage:sessionStorage,S=`${h}:`,k=[];for(let C=0;C<w.length;C++){let d=w.key(C);d?.startsWith(S)&&k.push(d);}for(let C of k)u.remove(C);}else if(c==="cookie"){let w=`${h}:`,S=document.cookie.split(";");for(let k of S){let C=k.trim(),d=C.indexOf("=");if(d===-1)continue;let v=C.substring(0,d),E=decodeURIComponent(v);E.startsWith(w)&&u.remove(E);}}else u.clear();e.emit("storage:clear",{backend:c,namespace:h});}function g(o){let c=o??n();return s(c).isSupported()}e.expose({storage:{set:x,get:I,remove:m,clear:p,isSupported:g}}),t.on("sdk:ready",()=>{let o=n();s(o).isSupported()||console.warn(`Storage backend '${o}' is not supported, falling back to memory storage`);});};var F=(e,t,r)=>{e.ns("banner"),e.defaults({banner:{position:"top",dismissable:true,zIndex:1e4}});let n=new Map;function a(s){let l=s.content,b=l.position??r.get("banner.position")??"top",x=l.dismissable??r.get("banner.dismissable")??true,I=r.get("banner.zIndex")??1e4,m=document.documentElement.classList.contains("dark"),p=m?"#1f2937":"#f9fafb",g=m?"#f3f4f6":"#111827",o=m?"#374151":"#e5e7eb",c=m?"rgba(0, 0, 0, 0.3)":"rgba(0, 0, 0, 0.05)",u=document.createElement("div");u.setAttribute("data-experience-id",s.id);let h=`banner-responsive-${s.id}`;if(!document.getElementById(h)){let d=document.createElement("style");d.id=h,d.textContent=`
2
+ @media (max-width: 640px) {
3
+ [data-experience-id="${s.id}"] {
4
+ flex-direction: column !important;
5
+ align-items: flex-start !important;
6
+ }
7
+ [data-experience-id="${s.id}"] > div:last-child {
8
+ width: 100%;
9
+ flex-direction: column !important;
10
+ }
11
+ [data-experience-id="${s.id}"] button {
12
+ width: 100%;
13
+ }
14
+ }
15
+ `,document.head.appendChild(d);}u.style.cssText=`
16
+ position: fixed;
17
+ ${b}: 0;
18
+ left: 0;
19
+ right: 0;
20
+ background: ${p};
21
+ color: ${g};
22
+ padding: 16px 20px;
23
+ border-${b==="top"?"bottom":"top"}: 1px solid ${o};
24
+ box-shadow: 0 ${b==="top"?"1":"-1"}px 3px 0 ${c};
25
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
26
+ font-size: 14px;
27
+ line-height: 1.5;
28
+ z-index: ${I};
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: space-between;
32
+ box-sizing: border-box;
33
+ `;let w=document.createElement("div");if(w.style.cssText="flex: 1; margin-right: 20px;",l.title){let d=document.createElement("div");d.textContent=l.title,d.style.cssText="font-weight: 600; margin-bottom: 4px;",w.appendChild(d);}let S=document.createElement("div");S.textContent=l.message,w.appendChild(S),u.appendChild(w);let k=document.createElement("div");k.style.cssText=`
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 12px;
37
+ flex-wrap: wrap;
38
+ `;function C(d){let v=document.createElement("button");v.textContent=d.text;let E=d.variant||"primary",q,R,O,$;return E==="primary"?(q=m?"#3b82f6":"#2563eb",R=m?"#2563eb":"#1d4ed8",O="#ffffff",$="none"):E==="secondary"?(q=m?"#374151":"#ffffff",R=m?"#4b5563":"#f9fafb",O=m?"#f3f4f6":"#374151",$=m?"1px solid #4b5563":"1px solid #d1d5db"):(q="transparent",R=m?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.05)",O=m?"#93c5fd":"#2563eb",$="none"),v.style.cssText=`
39
+ background: ${q};
40
+ border: ${$};
41
+ color: ${O};
42
+ padding: ${E==="link"?"4px 8px":"8px 16px"};
43
+ font-size: 14px;
44
+ font-weight: ${E==="link"?"400":"500"};
45
+ border-radius: 6px;
46
+ cursor: pointer;
47
+ transition: all 0.2s;
48
+ text-decoration: ${E==="link"?"underline":"none"};
49
+ `,v.addEventListener("mouseenter",()=>{v.style.background=R;}),v.addEventListener("mouseleave",()=>{v.style.background=q;}),v.addEventListener("click",()=>{t.emit("experiences:action",{experienceId:s.id,type:"banner",action:d.action,url:d.url,metadata:d.metadata,variant:E,timestamp:Date.now()}),d.url&&(window.location.href=d.url);}),v}if(l.buttons&&l.buttons.length>0&&l.buttons.forEach(d=>{let v=C(d);k.appendChild(v);}),x){let d=document.createElement("button");d.innerHTML="&times;",d.setAttribute("aria-label","Close banner");let v=m?"#9ca3af":"#6b7280";d.style.cssText=`
50
+ background: transparent;
51
+ border: none;
52
+ color: ${v};
53
+ font-size: 24px;
54
+ line-height: 1;
55
+ cursor: pointer;
56
+ padding: 0;
57
+ margin: 0;
58
+ opacity: 0.7;
59
+ transition: opacity 0.2s;
60
+ `,d.addEventListener("mouseenter",()=>{d.style.opacity="1";}),d.addEventListener("mouseleave",()=>{d.style.opacity="0.7";}),d.addEventListener("click",()=>{y(s.id),t.emit("experiences:dismissed",{experienceId:s.id,type:"banner"});}),k.appendChild(d);}return u.appendChild(k),u}function f(s){if(n.has(s.id)||typeof document>"u")return;let l=a(s);document.body.appendChild(l),n.set(s.id,l),t.emit("experiences:shown",{experienceId:s.id,type:"banner",timestamp:Date.now()});}function y(s){if(s){let l=n.get(s);l?.parentNode&&l.parentNode.removeChild(l),n.delete(s);}else for(let[l,b]of n.entries())b?.parentNode&&b.parentNode.removeChild(b),n.delete(l);}function i(){return n.size>0}e.expose({banner:{show:f,remove:y,isShowing:i}}),t.on("experiences:evaluated",s=>{let l=Array.isArray(s)?s:[s];for(let b of l){let x=b,I=x.decision,m=x.experience;m?.type==="banner"&&(I?.show?f(m):m.id&&n.has(m.id)&&y(m.id));}}),t.on("sdk:destroy",()=>{y();});},_=(e,t,r)=>{e.ns("debug"),e.defaults({debug:{enabled:false,console:false,window:true}});let n=()=>r.get("debug.enabled")??false,a=()=>r.get("debug.console")??false,f=()=>r.get("debug.window")??true,y=(i,s)=>{if(!n())return;let b={timestamp:new Date().toISOString(),message:i,data:s};if(a()&&console.log(`[experiences] ${i}`,s||""),f()&&typeof window<"u"){let x=new CustomEvent("experience-sdk:debug",{detail:b});window.dispatchEvent(x);}};e.expose({debug:{log:y,isEnabled:n}}),n()&&(t.on("experiences:ready",()=>{n()&&y("SDK initialized and ready");}),t.on("experiences:registered",i=>{n()&&y("Experience registered",i);}),t.on("experiences:evaluated",i=>{n()&&y("Experience evaluated",i);}));},z=(e,t,r)=>{e.ns("frequency"),e.defaults({frequency:{enabled:true,namespace:"experiences:frequency"}});let n=new Map;t.storage||t.use(D);let a=()=>r.get("frequency.enabled")??true,f=()=>r.get("frequency.namespace")??"experiences:frequency",y=p=>p==="session"?sessionStorage:localStorage,i=p=>`${f()}:${p}`,s=(p,g)=>{let o=y(g),c=i(p),u=o.getItem(c);if(!u)return {count:0,lastImpression:0,impressions:[],per:g};try{return JSON.parse(u)}catch{return {count:0,lastImpression:0,impressions:[],per:g}}},l=(p,g)=>{let o=g.per||"session",c=y(o),u=i(p);c.setItem(u,JSON.stringify(g));},b=p=>{switch(p){case "session":return Number.POSITIVE_INFINITY;case "day":return 1440*60*1e3;case "week":return 10080*60*1e3}},x=(p,g="session")=>a()?s(p,g).count:0,I=(p,g,o)=>{if(!a())return false;let c=s(p,o),u=b(o),h=Date.now();return o==="session"?c.count>=g:c.impressions.filter(S=>h-S<u).length>=g},m=(p,g="session")=>{if(!a())return;let o=s(p,g),c=Date.now();o.count+=1,o.lastImpression=c,o.impressions.push(c),o.per=g;let u=c-10080*60*1e3;o.impressions=o.impressions.filter(h=>h>u),l(p,o),t.emit("experiences:impression-recorded",{experienceId:p,count:o.count,timestamp:c});};e.expose({frequency:{getImpressionCount:x,hasReachedCap:I,recordImpression:m,_registerExperience:(p,g)=>{n.set(p,g);}}}),a()&&t.on("experiences:evaluated",p=>{let g=Array.isArray(p)?p:[p];for(let o of g){let c=o.decision;if(c?.show&&c.experienceId){let u=n.get(c.experienceId)||"session";if(!n.has(c.experienceId)){let h=c.trace.find(w=>w.step==="check-frequency-cap");h?.input&&typeof h.input=="object"&&"per"in h.input&&(u=h.input.per,n.set(c.experienceId,u));}m(c.experienceId,u);}}});};function j(e,t){let r={...e};for(let n in t){if(!Object.hasOwn(t,n))continue;let a=t[n],f=r[n];if(f===void 0){r[n]=a;continue}B(f)&&B(a)&&(r[n]=j(f,a));}return r}function B(e){return e===null||typeof e!="object"?false:Object.prototype.toString.call(e)==="[object Object]"}var ne=class{data={};required=new Set;constructor(e={}){this.data=JSON.parse(JSON.stringify(e));}defaults(e){this.data=j(this.data,e);}merge(e){this.data=j(e,this.data);}get(e){let t=e.split("."),r=this.data;for(let n of t){if(r==null)return;r=r[n];}return r}set(e,t){let r=e.split("."),n=r.pop();if(!n)return;let a=this.data;for(let f of r)(a[f]==null||typeof a[f]!="object")&&(a[f]={}),a=a[f];a[n]=t;}markRequired(e){e!=null&&this.required.add(e);}isRequired(e){return this.required.has(e)}getAll(){return JSON.parse(JSON.stringify(this.data))}},re=class{subscriptions=[];on(e,t){if(typeof t!="function")throw new TypeError("handler must be a function");let r={pattern:e,compiledPattern:this.compilePattern(e),handler:t};return this.subscriptions.push(r),()=>this.off(e,t)}off(e,t){this.subscriptions=this.subscriptions.filter(r=>!(r.pattern===e&&r.handler===t));}emit(e,...t){for(let r of this.subscriptions)if(r.compiledPattern.test(e))try{r.handler(...t);}catch(n){console.error(`Error in event handler for "${e}":`,n);}}removeAllListeners(){this.subscriptions=[];}compilePattern(e){let r=this.escapeRegExp(e).replace(/\\\*/g,"(.*?)");return new RegExp(`^${r}$`)}escapeRegExp(e){return e.replace(/[\\^$*+?.()|[\]{}]/g,"\\$&")}},se=class{sdk;constructor(e){this.sdk=e;}expose(e){Object.assign(this.sdk,e);}},oe=class{name="";ns(e){if(this.name)throw new Error(`Namespace already set to "${this.name}". Cannot reassign to "${e}".`);this.name=e;}},N=class{configInstance;emitter;plugins=new Map;isInitialized=false;constructor(e={}){this.emitter=new re,this.configInstance=new ne(e);}use(e){let t=new oe,r=new se(this),n={ns:t.ns.bind(t),defaults:this.configInstance.defaults.bind(this.configInstance),on:this.emitter.on.bind(this.emitter),off:this.emitter.off.bind(this.emitter),emit:this.emitter.emit.bind(this.emitter),expose:r.expose.bind(r),mustEnable:()=>{t.name&&this.configInstance.markRequired(t.name);}};return e(n,this,this.configInstance),t.name&&this.plugins.set(t.name,{namespace:t,plugin:n}),this}async init(e){if(this.isInitialized){console.warn("SDK already initialized");return}e&&this.configInstance.merge(e),this.emitter.emit("sdk:init");let t=Array.from(this.plugins.keys()).filter(r=>this.configInstance.isRequired(r));for(let r of t)if(!this.plugins.has(r))throw new Error(`Required plugin "${r}" is not registered`);this.isInitialized=true,this.emitter.emit("sdk:ready");}async destroy(){this.emitter.emit("sdk:destroy"),this.plugins.clear(),this.emitter.removeAllListeners(),this.isInitialized=false;}get(e){return this.configInstance.get(e)}set(e,t){this.configInstance.set(e,t);}on(e,t){return this.emitter.on(e,t)}off(e,t){this.emitter.off(e,t);}emit(e,...t){this.emitter.emit(e,...t);}getAll(){return this.configInstance.getAll()}isReady(){return this.isInitialized}};var P=class{sdk;experiences=new Map;decisions=[];initialized=false;destroyed=false;constructor(t={}){this.sdk=new N({name:"experience-sdk",...t}),this.sdk.use(D),this.sdk.use(_),this.sdk.use(z),this.sdk.use(F);}async init(t){if(this.initialized){console.warn("[experiences] Already initialized");return}this.destroyed&&(this.sdk=new N({name:"experience-sdk",...t}),this.sdk.use(D),this.sdk.use(_),this.sdk.use(z),this.sdk.use(F),this.destroyed=false),t&&Object.entries(t).forEach(([r,n])=>{this.sdk.set(r,n);}),await this.sdk.init(),this.initialized=true,this.sdk.emit("experiences:ready");}register(t,r){let n={id:t,...r};this.experiences.set(t,n),n.frequency&&this.sdk.frequency?._registerExperience&&this.sdk.frequency._registerExperience(t,n.frequency.per),this.sdk.emit("experiences:registered",{id:t,experience:n});}evaluate(t){let r=Date.now(),n=L(t),a,f=[],y=[];for(let[,s]of this.experiences){let l=U(s,n);if(f.push(...l.reasons),y.push(...l.trace),l.matched){if(s.frequency&&this.sdk.frequency){let b=Date.now(),x=this.sdk.frequency.hasReachedCap(s.id,s.frequency.max,s.frequency.per);if(y.push({step:"check-frequency-cap",timestamp:b,duration:Date.now()-b,input:s.frequency,output:x,passed:!x}),x){let m=this.sdk.frequency.getImpressionCount(s.id,s.frequency.per);f.push(`Frequency cap reached (${m}/${s.frequency.max} this ${s.frequency.per})`);continue}let I=this.sdk.frequency.getImpressionCount(s.id,s.frequency.per);f.push(`Frequency cap not reached (${I}/${s.frequency.max} this ${s.frequency.per})`);}a=s;break}}let i={show:!!a,experienceId:a?.id,reasons:f,trace:y,context:n,metadata:{evaluatedAt:Date.now(),totalDuration:Date.now()-r,experiencesEvaluated:this.experiences.size}};return this.decisions.push(i),this.sdk.emit("experiences:evaluated",{decision:i,experience:a}),i}evaluateAll(t){let r=L(t),n=Array.from(this.experiences.values()).sort((i,s)=>{let l=i.priority??0;return (s.priority??0)-l}),a=[];for(let i of n){let s=Date.now(),l=U(i,r),b=l.matched,x=[...l.reasons],I=[...l.trace];if(b&&i.frequency&&this.sdk.frequency){let p=Date.now(),g=this.sdk.frequency.hasReachedCap(i.id,i.frequency.max,i.frequency.per);if(I.push({step:"check-frequency-cap",timestamp:p,duration:Date.now()-p,input:i.frequency,output:g,passed:!g}),g){let o=this.sdk.frequency.getImpressionCount(i.id,i.frequency.per);x.push(`Frequency cap reached (${o}/${i.frequency.max} this ${i.frequency.per})`),b=false;}else {let o=this.sdk.frequency.getImpressionCount(i.id,i.frequency.per);x.push(`Frequency cap not reached (${o}/${i.frequency.max} this ${i.frequency.per})`);}}let m={show:b,experienceId:i.id,reasons:x,trace:I,context:r,metadata:{evaluatedAt:Date.now(),totalDuration:Date.now()-s,experiencesEvaluated:1}};a.push(m),this.decisions.push(m);}let f=a.filter(i=>i.show),y=f.map(i=>i.experienceId&&this.experiences.get(i.experienceId)).filter(i=>i!==void 0);return this.sdk.emit("experiences:evaluated",f.map((i,s)=>({decision:i,experience:y[s]}))),a}explain(t){let r=this.experiences.get(t);if(!r)return null;let n=L(),a=U(r,n);return {show:a.matched,experienceId:t,reasons:a.reasons,trace:a.trace,context:n,metadata:{evaluatedAt:Date.now(),totalDuration:0,experiencesEvaluated:1}}}getState(){return {initialized:this.initialized,experiences:new Map(this.experiences),decisions:[...this.decisions],config:this.sdk?this.sdk.getAll():{}}}on(t,r){return this.sdk.on(t,r)}async destroy(){this.sdk&&await this.sdk.destroy(),this.destroyed=true,this.experiences.clear(),this.decisions=[],this.initialized=false;}};function L(e){return {url:e?.url??(typeof window<"u"?window.location.href:""),timestamp:Date.now(),user:e?.user,custom:e?.custom}}function U(e,t){let r=[],n=[],a=true;if(e.targeting.url){let f=Date.now(),y=K(e.targeting.url,t.url);n.push({step:"evaluate-url-rule",timestamp:f,duration:Date.now()-f,input:{rule:e.targeting.url,url:t.url},output:y,passed:y}),y?r.push("URL matches targeting rule"):(r.push("URL does not match targeting rule"),a=false);}return {matched:a,reasons:r,trace:n}}function K(e,t=""){return e.equals!==void 0?t===e.equals:e.contains!==void 0?t.includes(e.contains):e.matches!==void 0?e.matches.test(t):true}function M(e){return new P(e)}var T=M();async function V(e){return T.init(e)}function J(e,t){T.register(e,t);}function Q(e){return T.evaluate(e)}function W(e){return T.evaluateAll(e)}function H(e){return T.explain(e)}function G(){return T.getState()}function X(e,t){return T.on(e,t)}async function Y(){return T.destroy()}var ie={createInstance:M,init:V,register:J,evaluate:Q,evaluateAll:W,explain:H,getState:G,on:X,destroy:Y};typeof window<"u"&&(window.experiences=ie);exports.ExperienceRuntime=P;exports.bannerPlugin=F;exports.buildContext=L;exports.createInstance=M;exports.debugPlugin=_;exports.destroy=Y;exports.evaluate=Q;exports.evaluateAll=W;exports.evaluateExperience=U;exports.evaluateUrlRule=K;exports.explain=H;exports.frequencyPlugin=z;exports.getState=G;exports.init=V;exports.on=X;exports.register=J;return exports;})({});//# sourceMappingURL=experience-sdk.global.js.map
61
+ //# sourceMappingURL=experience-sdk.global.js.map