@runbandit/sdk 3.1.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -62,6 +62,103 @@ declare function serializeTreatmentConfig(content: TreatmentContent): {
62
62
  content: unknown;
63
63
  };
64
64
 
65
+ declare const SITE_TARGET_KINDS: readonly ["text", "image"];
66
+ type SiteTargetKind = (typeof SITE_TARGET_KINDS)[number];
67
+ interface SiteTarget {
68
+ page: string;
69
+ selector: string;
70
+ tag: string;
71
+ kind: SiteTargetKind;
72
+ original: string;
73
+ }
74
+ type FunnelGoal = {
75
+ kind: 'page';
76
+ page: string;
77
+ } | {
78
+ kind: 'click';
79
+ page: string;
80
+ selector: string;
81
+ tag: string;
82
+ label: string;
83
+ };
84
+ declare function parseSiteContent(config: unknown): string | null;
85
+ declare function normalizeSiteTarget(target: SiteTarget): SiteTarget;
86
+ declare function normalizeFunnelGoal(goal: FunnelGoal): FunnelGoal;
87
+ declare function targetMatchesPage(target: Pick<SiteTarget, 'page'>, pathname: string): boolean;
88
+ declare function goalMatchesPage(goal: Pick<FunnelGoal, 'page'>, pathname: string): boolean;
89
+ declare const MAX_ALTERNATIVES = 5;
90
+ declare const MAX_TEXT_ALTERNATIVE_LENGTH = 300;
91
+ declare const MAX_IMAGE_URL_LENGTH = 2048;
92
+ interface PickDraft {
93
+ target?: SiteTarget;
94
+ alternatives: string[];
95
+ goal?: FunnelGoal;
96
+ }
97
+ interface PickState {
98
+ funnelId: string;
99
+ siteHost: string;
100
+ expiresAt: number;
101
+ draft: PickDraft;
102
+ funnelGoal: FunnelGoal | null;
103
+ experimentId: string | null;
104
+ runningExperiments: number;
105
+ }
106
+ declare const SUGGESTED_SITE_EXPERIMENTS = 3;
107
+ declare function moreExperimentsSentence(running: number, suggested: number): string;
108
+ type PickStep = 'pick' | 'alternatives' | 'goal' | 'ready' | 'live';
109
+ declare function pickStep(state: Pick<PickState, 'draft' | 'funnelGoal' | 'experimentId'>): PickStep;
110
+ declare function parseAlternative(kind: SiteTargetKind, raw: string): string;
111
+ declare const PICK_CHANNEL = "bandit-pick";
112
+ declare const PICK_PROTOCOL_VERSION = 1;
113
+ declare const PICK_MODES: readonly ["idle", "target", "goal-click"];
114
+ type PickMode = (typeof PICK_MODES)[number];
115
+ declare const PICK_REJECTIONS: readonly ["not-text-or-image", "has-markup", "not-clickable", "no-selector"];
116
+ type PickRejection = (typeof PICK_REJECTIONS)[number];
117
+ type ClickFunnelGoal = Extract<FunnelGoal, {
118
+ kind: 'click';
119
+ }>;
120
+ type PickerToPanel = {
121
+ type: 'navigated';
122
+ page: string;
123
+ } | {
124
+ type: 'target-picked';
125
+ target: SiteTarget;
126
+ } | {
127
+ type: 'goal-picked';
128
+ goal: ClickFunnelGoal;
129
+ } | {
130
+ type: 'rejected';
131
+ reason: PickRejection;
132
+ } | {
133
+ type: 'cancelled';
134
+ };
135
+ type PanelToPicker = {
136
+ type: 'hello';
137
+ } | {
138
+ type: 'mode';
139
+ mode: PickMode;
140
+ } | {
141
+ type: 'preview';
142
+ target: SiteTarget;
143
+ content: string;
144
+ } | {
145
+ type: 'clear-preview';
146
+ } | {
147
+ type: 'finish';
148
+ } | {
149
+ type: 'renew';
150
+ token: string;
151
+ };
152
+ declare const PICK_TOKEN_PREFIX = "bpick_";
153
+ interface PickEnvelope<M> {
154
+ channel: typeof PICK_CHANNEL;
155
+ v: typeof PICK_PROTOCOL_VERSION;
156
+ msg: M;
157
+ }
158
+ declare function pickEnvelope<M extends PickerToPanel | PanelToPicker>(msg: M): PickEnvelope<M>;
159
+ declare function parsePickerMessage(data: unknown): PickerToPanel | null;
160
+ declare function parsePanelMessage(data: unknown): PanelToPicker | null;
161
+
65
162
  interface Company {
66
163
  id: string;
67
164
  name: string;
@@ -359,11 +456,14 @@ interface SnapshotExperiment {
359
456
  mode: 'snapshot' | 'network';
360
457
  assignmentTtl: number;
361
458
  treatments: SnapshotTreatment[];
459
+ target?: SiteTarget;
460
+ funnelId?: string;
362
461
  }
363
462
  interface CompanySnapshot {
364
463
  ttl: number;
365
464
  generatedAt: number;
366
465
  experiments: Record<string, SnapshotExperiment>;
466
+ goals?: Record<string, FunnelGoal>;
367
467
  }
368
468
 
369
469
  declare function validateTreatmentWeights(treatments: Treatment[]): boolean;
@@ -495,6 +595,93 @@ declare function renderEmailDocument(document: EmailDocument, slots: Record<stri
495
595
  declare function normalizeConversionPath(input: string | undefined): string | undefined;
496
596
  declare function matchesConversionPath(pattern: string, pathname: string): boolean;
497
597
 
598
+ type SitePlatformPath = 'shopify' | 'script' | 'code';
599
+ interface SitePlatform {
600
+ id: string;
601
+ label: string;
602
+ path: SitePlatformPath;
603
+ codeBox: string;
604
+ steps: readonly string[];
605
+ planNote?: string;
606
+ }
607
+ declare const SITE_PLATFORMS: readonly [{
608
+ readonly id: "shopify";
609
+ readonly label: "Shopify";
610
+ readonly path: "shopify";
611
+ readonly codeBox: "";
612
+ readonly steps: readonly [];
613
+ }, {
614
+ readonly id: "webflow";
615
+ readonly label: "Webflow";
616
+ readonly path: "script";
617
+ readonly codeBox: "Site settings, Custom code, Head code. Save, then Publish.";
618
+ readonly steps: readonly ["Open the site in Webflow and click the gear for Site settings.", "Open the Custom code tab.", "Paste the script into the Head code box.", "Click Save changes.", "Publish the site. Custom code only goes live when you publish."];
619
+ readonly planNote: "Needs a paid Site plan.";
620
+ }, {
621
+ readonly id: "wordpress";
622
+ readonly label: "WordPress";
623
+ readonly path: "script";
624
+ readonly codeBox: "Install the free WPCode plugin, then Code Snippets, Header & Footer, Header box.";
625
+ readonly steps: readonly ["In your WordPress admin go to Plugins, Add New, and search for WPCode. Install and activate it.", "In the left menu open Code Snippets, then Header & Footer.", "Paste the script into the Header box.", "Click Save Changes.", "Load your site once so the tag is in the page."];
626
+ readonly planNote: "WordPress.com needs the Business plan for plugins.";
627
+ }, {
628
+ readonly id: "squarespace";
629
+ readonly label: "Squarespace";
630
+ readonly path: "script";
631
+ readonly codeBox: "Settings, Website Tools, Code Injection, Header.";
632
+ readonly steps: readonly ["From the Home menu open Settings, then Website Tools. On older sites it is under Advanced.", "Click Code Injection.", "Paste the script into the Header box.", "Click Save."];
633
+ readonly planNote: "Needs the Core plan or higher (Business on older plans).";
634
+ }, {
635
+ readonly id: "framer";
636
+ readonly label: "Framer";
637
+ readonly path: "script";
638
+ readonly codeBox: "Site Settings, General, Custom Code, Start of head. Publish after saving.";
639
+ readonly steps: readonly ["Open your project and click the gear for Site Settings.", "On the General tab scroll to Custom Code.", "Paste the script under Start of head tag.", "Click Save.", "Publish the site."];
640
+ readonly planNote: "Needs a paid Site plan.";
641
+ }, {
642
+ readonly id: "wix";
643
+ readonly label: "Wix";
644
+ readonly path: "script";
645
+ readonly codeBox: "Settings, Custom Code, Add Custom Code. Choose All pages and place it in Head.";
646
+ readonly steps: readonly ["In your Wix dashboard open Settings, then Custom Code under Advanced.", "Click Add Custom Code.", "Paste the script and name it Bandit.", "Under Add Code to Pages choose All pages and Load code once.", "Under Place Code in choose Head.", "Click Apply."];
647
+ readonly planNote: "Needs a Premium plan with a connected domain.";
648
+ }, {
649
+ readonly id: "other";
650
+ readonly label: "Something else";
651
+ readonly path: "script";
652
+ readonly codeBox: "Any custom-code or head-injection box, or the HTML template, on every page.";
653
+ readonly steps: readonly ["Find where your builder lets you add code to the head of every page. It is usually called Custom code, Code injection, Header scripts, or Tracking code.", "Paste the script there.", "Save and publish.", "If you edit the HTML yourself, paste it before the closing head tag of every page template."];
654
+ }, {
655
+ readonly id: "nextjs_react";
656
+ readonly label: "Next.js / React";
657
+ readonly path: "code";
658
+ readonly codeBox: "Your root layout's head.";
659
+ readonly steps: readonly ["Add the tag to the head of your root layout."];
660
+ }, {
661
+ readonly id: "plain_html";
662
+ readonly label: "Plain HTML";
663
+ readonly path: "code";
664
+ readonly codeBox: "The head of every page.";
665
+ readonly steps: readonly ["Paste the tag into the head of every page."];
666
+ }, {
667
+ readonly id: "tag_manager";
668
+ readonly label: "Tag manager";
669
+ readonly path: "code";
670
+ readonly codeBox: "A Custom HTML tag that fires on all pages.";
671
+ readonly steps: readonly ["Create a Custom HTML tag with the script and fire it on all pages."];
672
+ }];
673
+ type SitePlatformId = (typeof SITE_PLATFORMS)[number]['id'];
674
+ declare const SITE_PLATFORM_IDS: readonly SitePlatformId[];
675
+ type ScriptEntry = Extract<(typeof SITE_PLATFORMS)[number], {
676
+ path: 'script';
677
+ }>;
678
+ type ScriptPlatform = SitePlatform & {
679
+ id: ScriptEntry['id'];
680
+ path: 'script';
681
+ };
682
+ declare const SCRIPT_PLATFORMS: readonly ScriptPlatform[];
683
+ declare function platformById(id: string | undefined): SitePlatform | undefined;
684
+
498
685
  interface BanditsClientConfig {
499
686
  apiUrl: string;
500
687
  apiKey: string;
@@ -503,6 +690,10 @@ interface BanditsClientConfig {
503
690
  flushInterval?: number;
504
691
  sessionTtl?: number;
505
692
  }
693
+ declare class BanditsRequestError extends Error {
694
+ readonly status: number | undefined;
695
+ constructor(message: string, status: number | undefined);
696
+ }
506
697
  interface Assignment {
507
698
  assignmentId: string;
508
699
  treatmentId: string;
@@ -640,4 +831,4 @@ declare function reportPlanImpressions(plan: EmailPlan, recipientIds: string[],
640
831
  alreadyRecorded: boolean;
641
832
  }>;
642
833
 
643
- export { type APIResponse, type Account, type AlgorithmResult, type ApiKey, type Assignment, type AssignmentRequest, BanditAlgorithm, BanditsClient as BanditClient, type BanditConfig, type BanditResult, BanditsClient, type BanditsClientConfig, type Bound, type Bundle, type BundleAssignmentRequest, type BundleAssignmentResponse, type BundleMode, type BundleSlotAssignment, type ButtonBlock, CONTENT_TYPE_BY_KIND, type Company, type CompanySnapshot, ContentType, type ContextualFeatures, type CreateApiKeyRequest, type CreateApiKeyResponse, type CreateExperimentRequest, DEFAULT_HALF_LIFE_MS, DEFAULT_SESSION_TTL_MS, DEFAULT_WINDOW_MS, type DividerBlock, EMAIL_TOKEN_PARAM, type EmailBlock, type EmailDocument, type EmailPlan, type EmailTokenPayload, type Experiment, type ExperimentStats, ExperimentStatus, type ForgettingConfig, ForgettingMode, type Funnel, type FunnelRewardRequest, FunnelStatus, type HeadingBlock, type IdentifyMergeReason, type IdentifyUserRequest, type IdentifyUserResponse, type ImageBlock, type ImageContent, MAX_ORDERING_ITEMS, MIN_ORDERING_ITEMS, type OfferContent, PLACEMENTS, PLACEMENTS_BY_KIND, type Placement, type PlanArm, type PlanRenderResult, type PlanSlot, Posterior, type RenderEmailOptions, type RenderedDocument, type RenderedEmail, type ResolvedIdentity, type RewriteOptions, type RewriteResult, SESSION_CREATED_AT_KEY, SESSION_ID_KEY, STATIONARY, STOREFRONT_KINDS, type Session, type SessionOptions, type SigningKey, type SlotBinding, SnapshotClient, type SnapshotClientConfig, type SnapshotExperiment, type SnapshotTreatment, type SpacerBlock, type StorefrontKind, type SyncAssignment, type TextBlock, type TrackNamedEventRequest, type Treatment, type TreatmentContent, type TreatmentMetadata, type TreatmentStats, type User, VISITOR_ID_KEY, type VariantConfig, type VerifiedToken, appendEmailToken, buildSubdomainUrl, calculateConfidenceInterval, extractDocumentSlots, extractEmailLinks, extractSlotNames, extractSubdomain, generateId, generateSubdomain, getContentTypeCategory, getOrCreateSessionId, getOrCreateVisitorId, hashRecipient, impressionManifest, isPlacement, isShopifyCdnUrl, isSlotBinding, isValidEmail, isValidSubdomain, kindOfPlacement, matchesConversionPath, mintEmailToken, normalizeAlgorithm, normalizeConversionPath, normalizePosterior, normalizeTreatmentWeights, parseTreatmentContent, pickArm, pickTreatment, renderEmail, renderEmailDocument, renderFromPlan, reportPlanImpressions, resolveForgetting, rewriteEmailLinks, sampleCombination, sanitizeMetadata, serializeTreatmentConfig, shouldRefreshPlan, uniformFor, validateTreatmentWeights, verifyEmailToken };
834
+ export { type APIResponse, type Account, type AlgorithmResult, type ApiKey, type Assignment, type AssignmentRequest, BanditAlgorithm, BanditsClient as BanditClient, type BanditConfig, type BanditResult, BanditsClient, type BanditsClientConfig, BanditsRequestError, type Bound, type Bundle, type BundleAssignmentRequest, type BundleAssignmentResponse, type BundleMode, type BundleSlotAssignment, type ButtonBlock, CONTENT_TYPE_BY_KIND, type ClickFunnelGoal, type Company, type CompanySnapshot, ContentType, type ContextualFeatures, type CreateApiKeyRequest, type CreateApiKeyResponse, type CreateExperimentRequest, DEFAULT_HALF_LIFE_MS, DEFAULT_SESSION_TTL_MS, DEFAULT_WINDOW_MS, type DividerBlock, EMAIL_TOKEN_PARAM, type EmailBlock, type EmailDocument, type EmailPlan, type EmailTokenPayload, type Experiment, type ExperimentStats, ExperimentStatus, type ForgettingConfig, ForgettingMode, type Funnel, type FunnelGoal, type FunnelRewardRequest, FunnelStatus, type HeadingBlock, type IdentifyMergeReason, type IdentifyUserRequest, type IdentifyUserResponse, type ImageBlock, type ImageContent, MAX_ALTERNATIVES, MAX_IMAGE_URL_LENGTH, MAX_ORDERING_ITEMS, MAX_TEXT_ALTERNATIVE_LENGTH, MIN_ORDERING_ITEMS, type OfferContent, PICK_CHANNEL, PICK_MODES, PICK_PROTOCOL_VERSION, PICK_REJECTIONS, PICK_TOKEN_PREFIX, PLACEMENTS, PLACEMENTS_BY_KIND, type PanelToPicker, type PickDraft, type PickEnvelope, type PickMode, type PickRejection, type PickState, type PickStep, type PickerToPanel, type Placement, type PlanArm, type PlanRenderResult, type PlanSlot, Posterior, type RenderEmailOptions, type RenderedDocument, type RenderedEmail, type ResolvedIdentity, type RewriteOptions, type RewriteResult, SCRIPT_PLATFORMS, SESSION_CREATED_AT_KEY, SESSION_ID_KEY, SITE_PLATFORMS, SITE_PLATFORM_IDS, SITE_TARGET_KINDS, STATIONARY, STOREFRONT_KINDS, SUGGESTED_SITE_EXPERIMENTS, type ScriptPlatform, type Session, type SessionOptions, type SigningKey, type SitePlatform, type SitePlatformId, type SitePlatformPath, type SiteTarget, type SiteTargetKind, type SlotBinding, SnapshotClient, type SnapshotClientConfig, type SnapshotExperiment, type SnapshotTreatment, type SpacerBlock, type StorefrontKind, type SyncAssignment, type TextBlock, type TrackNamedEventRequest, type Treatment, type TreatmentContent, type TreatmentMetadata, type TreatmentStats, type User, VISITOR_ID_KEY, type VariantConfig, type VerifiedToken, appendEmailToken, buildSubdomainUrl, calculateConfidenceInterval, extractDocumentSlots, extractEmailLinks, extractSlotNames, extractSubdomain, generateId, generateSubdomain, getContentTypeCategory, getOrCreateSessionId, getOrCreateVisitorId, goalMatchesPage, hashRecipient, impressionManifest, isPlacement, isShopifyCdnUrl, isSlotBinding, isValidEmail, isValidSubdomain, kindOfPlacement, matchesConversionPath, mintEmailToken, moreExperimentsSentence, normalizeAlgorithm, normalizeConversionPath, normalizeFunnelGoal, normalizePosterior, normalizeSiteTarget, normalizeTreatmentWeights, parseAlternative, parsePanelMessage, parsePickerMessage, parseSiteContent, parseTreatmentContent, pickArm, pickEnvelope, pickStep, pickTreatment, platformById, renderEmail, renderEmailDocument, renderFromPlan, reportPlanImpressions, resolveForgetting, rewriteEmailLinks, sampleCombination, sanitizeMetadata, serializeTreatmentConfig, shouldRefreshPlan, targetMatchesPage, uniformFor, validateTreatmentWeights, verifyEmailToken };
package/dist/index.js CHANGED
@@ -595,6 +595,309 @@ function matchesConversionPath(pattern, pathname) {
595
595
  return matchSegments(segmentsOf(pattern), segmentsOf(pathname));
596
596
  }
597
597
 
598
+ // ../shared/dist/site.js
599
+ var SITE_TARGET_KINDS = ["text", "image"];
600
+ var TAG_NAME = /^[a-z][a-z0-9-]*$/;
601
+ function collapseWhitespace(raw) {
602
+ return raw.replace(/\s+/g, " ").trim();
603
+ }
604
+ function requireText(raw, message) {
605
+ const value = collapseWhitespace(raw);
606
+ if (!value)
607
+ throw new Error(message);
608
+ return value;
609
+ }
610
+ function requirePage(raw) {
611
+ const page = normalizeConversionPath(raw);
612
+ if (!page)
613
+ throw new Error("Enter the page as a path like /pricing");
614
+ return page;
615
+ }
616
+ function requireTag(raw) {
617
+ const tag = raw.trim().toLowerCase();
618
+ if (!TAG_NAME.test(tag))
619
+ throw new Error("Enter the element tag, like h1 or button");
620
+ return tag;
621
+ }
622
+ function parseSiteContent(config) {
623
+ if (typeof config !== "object" || config === null)
624
+ return null;
625
+ const { content, value } = config;
626
+ for (const candidate of [content, value]) {
627
+ if (typeof candidate === "string" && candidate.trim())
628
+ return candidate;
629
+ }
630
+ return null;
631
+ }
632
+ function normalizeSiteTarget(target) {
633
+ if (!SITE_TARGET_KINDS.includes(target.kind))
634
+ throw new Error("A target is text or an image");
635
+ return {
636
+ page: requirePage(target.page),
637
+ selector: requireText(target.selector, "The picked element has no selector"),
638
+ tag: requireTag(target.tag),
639
+ kind: target.kind,
640
+ original: requireText(target.original, "The picked element has no content to replace")
641
+ };
642
+ }
643
+ function normalizeFunnelGoal(goal) {
644
+ const page = requirePage(goal.page);
645
+ if (goal.kind === "page")
646
+ return { kind: "page", page };
647
+ return {
648
+ kind: "click",
649
+ page,
650
+ selector: requireText(goal.selector, "The picked element has no selector"),
651
+ tag: requireTag(goal.tag),
652
+ label: collapseWhitespace(goal.label)
653
+ };
654
+ }
655
+ function targetMatchesPage(target, pathname) {
656
+ return matchesConversionPath(target.page, pathname);
657
+ }
658
+ function goalMatchesPage(goal, pathname) {
659
+ return matchesConversionPath(goal.page, pathname);
660
+ }
661
+ var MAX_ALTERNATIVES = 5;
662
+ var MAX_TEXT_ALTERNATIVE_LENGTH = 300;
663
+ var MAX_IMAGE_URL_LENGTH = 2048;
664
+ var SUGGESTED_SITE_EXPERIMENTS = 3;
665
+ function moreExperimentsSentence(running, suggested) {
666
+ const noun = (count) => count === 1 ? "experiment" : "experiments";
667
+ const status = `${running} ${noun(running)} running on your site.`;
668
+ if (running >= suggested)
669
+ return `${status} Keep going if you have more elements to test.`;
670
+ const remaining = suggested - running;
671
+ return `${status} Pick ${remaining} more while you are here. Each element you test is another place to find a win.`;
672
+ }
673
+ function pickStep(state) {
674
+ if (state.experimentId !== null)
675
+ return "live";
676
+ if (!state.draft.target)
677
+ return "pick";
678
+ if (state.draft.alternatives.length === 0)
679
+ return "alternatives";
680
+ if (!state.draft.goal && !state.funnelGoal)
681
+ return "goal";
682
+ return "ready";
683
+ }
684
+ function parseAlternative(kind, raw) {
685
+ if (kind === "text") {
686
+ const text = requireText(raw, "Type the text to try");
687
+ if (text.length > MAX_TEXT_ALTERNATIVE_LENGTH) {
688
+ throw new Error(`Keep each option under ${MAX_TEXT_ALTERNATIVE_LENGTH} characters`);
689
+ }
690
+ return text;
691
+ }
692
+ const trimmed = raw.trim();
693
+ if (trimmed.length > MAX_IMAGE_URL_LENGTH)
694
+ throw new Error("That image address is too long");
695
+ let url;
696
+ try {
697
+ url = new URL(trimmed);
698
+ } catch {
699
+ throw new Error("Enter the image address as a link that starts with https://");
700
+ }
701
+ if (url.protocol !== "https:")
702
+ throw new Error("Enter the image address as a link that starts with https://");
703
+ return url.href;
704
+ }
705
+ var PICK_CHANNEL = "bandit-pick";
706
+ var PICK_PROTOCOL_VERSION = 1;
707
+ var PICK_MODES = ["idle", "target", "goal-click"];
708
+ var PICK_REJECTIONS = ["not-text-or-image", "has-markup", "not-clickable", "no-selector"];
709
+ var PICK_TOKEN_PREFIX = "bpick_";
710
+ function pickEnvelope(msg) {
711
+ return { channel: PICK_CHANNEL, v: PICK_PROTOCOL_VERSION, msg };
712
+ }
713
+ function isFields(value) {
714
+ return typeof value === "object" && value !== null && !Array.isArray(value);
715
+ }
716
+ function isString(value) {
717
+ return typeof value === "string";
718
+ }
719
+ function isOneOf(options, value) {
720
+ return options.some((option) => option === value);
721
+ }
722
+ function openEnvelope(data) {
723
+ if (!isFields(data) || data.channel !== PICK_CHANNEL || data.v !== PICK_PROTOCOL_VERSION)
724
+ return null;
725
+ return isFields(data.msg) ? data.msg : null;
726
+ }
727
+ function asSiteTarget(value) {
728
+ if (!isFields(value))
729
+ return null;
730
+ const { page, selector, tag, kind, original } = value;
731
+ if (!isString(page) || !isString(selector) || !isString(tag) || !isString(original))
732
+ return null;
733
+ if (!isOneOf(SITE_TARGET_KINDS, kind))
734
+ return null;
735
+ return { page, selector, tag, kind, original };
736
+ }
737
+ function asClickGoal(value) {
738
+ if (!isFields(value) || value.kind !== "click")
739
+ return null;
740
+ const { page, selector, tag, label } = value;
741
+ if (!isString(page) || !isString(selector) || !isString(tag) || !isString(label))
742
+ return null;
743
+ return { kind: "click", page, selector, tag, label };
744
+ }
745
+ function parsePickerMessage(data) {
746
+ const msg = openEnvelope(data);
747
+ switch (msg?.type) {
748
+ case "navigated":
749
+ return isString(msg.page) ? { type: "navigated", page: msg.page } : null;
750
+ case "target-picked": {
751
+ const target = asSiteTarget(msg.target);
752
+ return target ? { type: "target-picked", target } : null;
753
+ }
754
+ case "goal-picked": {
755
+ const goal = asClickGoal(msg.goal);
756
+ return goal ? { type: "goal-picked", goal } : null;
757
+ }
758
+ case "rejected":
759
+ return isOneOf(PICK_REJECTIONS, msg.reason) ? { type: "rejected", reason: msg.reason } : null;
760
+ case "cancelled":
761
+ return { type: "cancelled" };
762
+ default:
763
+ return null;
764
+ }
765
+ }
766
+ function parsePanelMessage(data) {
767
+ const msg = openEnvelope(data);
768
+ switch (msg?.type) {
769
+ case "hello":
770
+ return { type: "hello" };
771
+ case "mode":
772
+ return isOneOf(PICK_MODES, msg.mode) ? { type: "mode", mode: msg.mode } : null;
773
+ case "preview": {
774
+ const target = asSiteTarget(msg.target);
775
+ return target && isString(msg.content) ? { type: "preview", target, content: msg.content } : null;
776
+ }
777
+ case "clear-preview":
778
+ return { type: "clear-preview" };
779
+ case "finish":
780
+ return { type: "finish" };
781
+ case "renew":
782
+ return isString(msg.token) && msg.token.startsWith(PICK_TOKEN_PREFIX) ? { type: "renew", token: msg.token } : null;
783
+ default:
784
+ return null;
785
+ }
786
+ }
787
+
788
+ // ../shared/dist/site-platforms.js
789
+ var SITE_PLATFORMS = [
790
+ { id: "shopify", label: "Shopify", path: "shopify", codeBox: "", steps: [] },
791
+ {
792
+ id: "webflow",
793
+ label: "Webflow",
794
+ path: "script",
795
+ codeBox: "Site settings, Custom code, Head code. Save, then Publish.",
796
+ steps: [
797
+ "Open the site in Webflow and click the gear for Site settings.",
798
+ "Open the Custom code tab.",
799
+ "Paste the script into the Head code box.",
800
+ "Click Save changes.",
801
+ "Publish the site. Custom code only goes live when you publish."
802
+ ],
803
+ planNote: "Needs a paid Site plan."
804
+ },
805
+ {
806
+ id: "wordpress",
807
+ label: "WordPress",
808
+ path: "script",
809
+ codeBox: "Install the free WPCode plugin, then Code Snippets, Header & Footer, Header box.",
810
+ steps: [
811
+ "In your WordPress admin go to Plugins, Add New, and search for WPCode. Install and activate it.",
812
+ "In the left menu open Code Snippets, then Header & Footer.",
813
+ "Paste the script into the Header box.",
814
+ "Click Save Changes.",
815
+ "Load your site once so the tag is in the page."
816
+ ],
817
+ planNote: "WordPress.com needs the Business plan for plugins."
818
+ },
819
+ {
820
+ id: "squarespace",
821
+ label: "Squarespace",
822
+ path: "script",
823
+ codeBox: "Settings, Website Tools, Code Injection, Header.",
824
+ steps: [
825
+ "From the Home menu open Settings, then Website Tools. On older sites it is under Advanced.",
826
+ "Click Code Injection.",
827
+ "Paste the script into the Header box.",
828
+ "Click Save."
829
+ ],
830
+ planNote: "Needs the Core plan or higher (Business on older plans)."
831
+ },
832
+ {
833
+ id: "framer",
834
+ label: "Framer",
835
+ path: "script",
836
+ codeBox: "Site Settings, General, Custom Code, Start of head. Publish after saving.",
837
+ steps: [
838
+ "Open your project and click the gear for Site Settings.",
839
+ "On the General tab scroll to Custom Code.",
840
+ "Paste the script under Start of head tag.",
841
+ "Click Save.",
842
+ "Publish the site."
843
+ ],
844
+ planNote: "Needs a paid Site plan."
845
+ },
846
+ {
847
+ id: "wix",
848
+ label: "Wix",
849
+ path: "script",
850
+ codeBox: "Settings, Custom Code, Add Custom Code. Choose All pages and place it in Head.",
851
+ steps: [
852
+ "In your Wix dashboard open Settings, then Custom Code under Advanced.",
853
+ "Click Add Custom Code.",
854
+ "Paste the script and name it Bandit.",
855
+ "Under Add Code to Pages choose All pages and Load code once.",
856
+ "Under Place Code in choose Head.",
857
+ "Click Apply."
858
+ ],
859
+ planNote: "Needs a Premium plan with a connected domain."
860
+ },
861
+ {
862
+ id: "other",
863
+ label: "Something else",
864
+ path: "script",
865
+ codeBox: "Any custom-code or head-injection box, or the HTML template, on every page.",
866
+ steps: [
867
+ "Find where your builder lets you add code to the head of every page. It is usually called Custom code, Code injection, Header scripts, or Tracking code.",
868
+ "Paste the script there.",
869
+ "Save and publish.",
870
+ "If you edit the HTML yourself, paste it before the closing head tag of every page template."
871
+ ]
872
+ },
873
+ {
874
+ id: "nextjs_react",
875
+ label: "Next.js / React",
876
+ path: "code",
877
+ codeBox: "Your root layout's head.",
878
+ steps: ["Add the tag to the head of your root layout."]
879
+ },
880
+ {
881
+ id: "plain_html",
882
+ label: "Plain HTML",
883
+ path: "code",
884
+ codeBox: "The head of every page.",
885
+ steps: ["Paste the tag into the head of every page."]
886
+ },
887
+ {
888
+ id: "tag_manager",
889
+ label: "Tag manager",
890
+ path: "code",
891
+ codeBox: "A Custom HTML tag that fires on all pages.",
892
+ steps: ["Create a Custom HTML tag with the script and fire it on all pages."]
893
+ }
894
+ ];
895
+ var SITE_PLATFORM_IDS = SITE_PLATFORMS.map((platform) => platform.id);
896
+ var SCRIPT_PLATFORMS = SITE_PLATFORMS.filter((platform) => platform.path === "script");
897
+ function platformById(id) {
898
+ return SITE_PLATFORMS.find((platform) => platform.id === id);
899
+ }
900
+
598
901
  // src/session.ts
599
902
  var SESSION_ID_KEY = "bandits_session_id";
600
903
  var SESSION_CREATED_AT_KEY = "bandits_session_created_at";
@@ -644,6 +947,17 @@ function defaultSessionStorage() {
644
947
  }
645
948
 
646
949
  // src/client.ts
950
+ var BanditsRequestError = class extends Error {
951
+ status;
952
+ constructor(message, status) {
953
+ super(message);
954
+ this.name = "BanditsRequestError";
955
+ this.status = status;
956
+ }
957
+ };
958
+ function requestError(response, fallback) {
959
+ return new BanditsRequestError(response.error || fallback, response.status);
960
+ }
647
961
  function currentPageUrl() {
648
962
  if (typeof location !== "undefined" && location.origin && location.pathname) {
649
963
  return location.origin + location.pathname;
@@ -683,7 +997,7 @@ var BanditsClient = class {
683
997
  request
684
998
  );
685
999
  if (!response.success || !response.data) {
686
- throw new Error(response.error || "Failed to get assignment");
1000
+ throw requestError(response, "Failed to get assignment");
687
1001
  }
688
1002
  const assignment = response.data?.data ?? response.data;
689
1003
  if (!assignment?.assignmentId) {
@@ -759,13 +1073,13 @@ var BanditsClient = class {
759
1073
  }
760
1074
  );
761
1075
  if (!response.success) {
762
- throw new Error(response.error || "Failed to identify user");
1076
+ throw requestError(response, "Failed to identify user");
763
1077
  }
764
1078
  }
765
1079
  async label(userId, labels) {
766
1080
  const response = await this.makeRequest("/labels", "POST", { userId, labels });
767
1081
  if (!response.success) {
768
- throw new Error(response.error || "Failed to record labels");
1082
+ throw requestError(response, "Failed to record labels");
769
1083
  }
770
1084
  }
771
1085
  async trackFunnelReward(funnelId, userId, value) {
@@ -775,7 +1089,7 @@ var BanditsClient = class {
775
1089
  { userId, value }
776
1090
  );
777
1091
  if (!response.success) {
778
- throw new Error(response.error || "Failed to record funnel reward");
1092
+ throw requestError(response, "Failed to record funnel reward");
779
1093
  }
780
1094
  }
781
1095
  convert(funnelId, value = 1) {
@@ -797,7 +1111,7 @@ var BanditsClient = class {
797
1111
  if (pageUrl) request.pageUrl = pageUrl;
798
1112
  const response = await this.makeRequest("/bundles/assignment", "POST", request);
799
1113
  if (!response.success || !response.data) {
800
- throw new Error(response.error || "Failed to get bundle assignment");
1114
+ throw requestError(response, "Failed to get bundle assignment");
801
1115
  }
802
1116
  const payload = response.data?.data ?? response.data;
803
1117
  if (!payload?.bundleAssignmentId || !payload?.slots) {
@@ -814,7 +1128,7 @@ var BanditsClient = class {
814
1128
  value
815
1129
  });
816
1130
  if (!response.success) {
817
- throw new Error(response.error || "Failed to record bundle reward");
1131
+ throw requestError(response, "Failed to record bundle reward");
818
1132
  }
819
1133
  }
820
1134
  generateId() {
@@ -843,7 +1157,8 @@ var BanditsClient = class {
843
1157
  const requestConfig = {
844
1158
  method,
845
1159
  headers,
846
- signal: AbortSignal.timeout(this.config.timeout)
1160
+ signal: AbortSignal.timeout(this.config.timeout),
1161
+ keepalive: method === "POST"
847
1162
  };
848
1163
  if (body && method !== "GET") {
849
1164
  requestConfig.body = JSON.stringify(body);
@@ -853,6 +1168,7 @@ var BanditsClient = class {
853
1168
  const data = await response.json();
854
1169
  return {
855
1170
  success: response.ok,
1171
+ status: response.status,
856
1172
  data: response.ok ? data : void 0,
857
1173
  error: response.ok ? void 0 : data?.message || "Request failed",
858
1174
  timestamp: /* @__PURE__ */ new Date()
@@ -1277,6 +1593,7 @@ export {
1277
1593
  BanditAlgorithm,
1278
1594
  BanditsClient as BanditClient,
1279
1595
  BanditsClient,
1596
+ BanditsRequestError,
1280
1597
  CONTENT_TYPE_BY_KIND,
1281
1598
  ContentType,
1282
1599
  DEFAULT_HALF_LIFE_MS,
@@ -1286,15 +1603,28 @@ export {
1286
1603
  ExperimentStatus,
1287
1604
  ForgettingMode,
1288
1605
  FunnelStatus,
1606
+ MAX_ALTERNATIVES,
1607
+ MAX_IMAGE_URL_LENGTH,
1289
1608
  MAX_ORDERING_ITEMS,
1609
+ MAX_TEXT_ALTERNATIVE_LENGTH,
1290
1610
  MIN_ORDERING_ITEMS,
1611
+ PICK_CHANNEL,
1612
+ PICK_MODES,
1613
+ PICK_PROTOCOL_VERSION,
1614
+ PICK_REJECTIONS,
1615
+ PICK_TOKEN_PREFIX,
1291
1616
  PLACEMENTS,
1292
1617
  PLACEMENTS_BY_KIND,
1293
1618
  Posterior,
1619
+ SCRIPT_PLATFORMS,
1294
1620
  SESSION_CREATED_AT_KEY,
1295
1621
  SESSION_ID_KEY,
1622
+ SITE_PLATFORMS,
1623
+ SITE_PLATFORM_IDS,
1624
+ SITE_TARGET_KINDS,
1296
1625
  STATIONARY,
1297
1626
  STOREFRONT_KINDS,
1627
+ SUGGESTED_SITE_EXPERIMENTS,
1298
1628
  SnapshotClient,
1299
1629
  VISITOR_ID_KEY,
1300
1630
  appendEmailToken,
@@ -1309,6 +1639,7 @@ export {
1309
1639
  getContentTypeCategory,
1310
1640
  getOrCreateSessionId,
1311
1641
  getOrCreateVisitorId,
1642
+ goalMatchesPage,
1312
1643
  hashRecipient,
1313
1644
  impressionManifest,
1314
1645
  isPlacement,
@@ -1319,13 +1650,23 @@ export {
1319
1650
  kindOfPlacement,
1320
1651
  matchesConversionPath,
1321
1652
  mintEmailToken,
1653
+ moreExperimentsSentence,
1322
1654
  normalizeAlgorithm,
1323
1655
  normalizeConversionPath,
1656
+ normalizeFunnelGoal,
1324
1657
  normalizePosterior,
1658
+ normalizeSiteTarget,
1325
1659
  normalizeTreatmentWeights,
1660
+ parseAlternative,
1661
+ parsePanelMessage,
1662
+ parsePickerMessage,
1663
+ parseSiteContent,
1326
1664
  parseTreatmentContent,
1327
1665
  pickArm,
1666
+ pickEnvelope,
1667
+ pickStep,
1328
1668
  pickTreatment,
1669
+ platformById,
1329
1670
  renderEmail,
1330
1671
  renderEmailDocument,
1331
1672
  renderFromPlan,
@@ -1336,6 +1677,7 @@ export {
1336
1677
  sanitizeMetadata,
1337
1678
  serializeTreatmentConfig,
1338
1679
  shouldRefreshPlan,
1680
+ targetMatchesPage,
1339
1681
  uniformFor,
1340
1682
  validateTreatmentWeights,
1341
1683
  verifyEmailToken
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../shared/src/types.ts","../../shared/src/utils.ts","../../shared/src/email-token.ts","../../shared/src/email-plan.ts","../../shared/src/email-blocks.ts","../../shared/src/storefront-content.ts","../../shared/src/conversion-path.ts","../src/session.ts","../src/client.ts","../src/snapshot-cache.ts","../src/email-links.ts","../src/email.ts","../src/email-plan.ts"],"sourcesContent":["import type { Placement } from './storefront-content.js';\n\nexport interface Company {\n id: string;\n name: string;\n subdomain?: string;\n domain?: string;\n plan: string;\n apiKey: string;\n status: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface User {\n id: string;\n email: string;\n name: string;\n image?: string;\n role: string;\n companyId: string | null;\n emailVerified?: boolean;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface Session {\n id: string;\n userId: string;\n token: string;\n expiresAt: Date;\n ipAddress?: string | null;\n userAgent?: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface Account {\n id: string;\n userId: string;\n provider: string;\n providerAccountId: string;\n accessToken?: string;\n refreshToken?: string;\n accessTokenExpiresAt?: Date;\n refreshTokenExpiresAt?: Date;\n scope?: string;\n idToken?: string;\n sessionState?: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface Experiment {\n id: string;\n name: string;\n description?: string;\n algorithm: BanditAlgorithm;\n forgetting?: ForgettingConfig;\n status: ExperimentStatus;\n contentType: ContentType;\n treatments: Treatment[];\n context?: Record<string, any>;\n bundleId?: string;\n image?: string;\n emoji?: string;\n assignmentTtl: number;\n companyId: string;\n createdAt: Date;\n updatedAt: Date;\n endAt?: Date;\n placement?: Placement;\n}\n\nexport interface Treatment {\n id: string;\n experimentId: string;\n name: string;\n description?: string;\n config: VariantConfig;\n weight?: number;\n companyId: string;\n createdAt: Date;\n}\n\nexport interface AlgorithmResult {\n treatmentId: string;\n confidence: number;\n probabilityBest?: number;\n}\n\nexport interface BanditResult {\n assignmentId: string;\n treatmentId: string;\n confidence: number;\n probabilityBest?: number;\n metadata?: Record<string, any>;\n}\n\nexport interface ContextualFeatures {\n [key: string]: string | number | boolean;\n}\n\nexport enum ExperimentStatus {\n DRAFT = 'draft',\n ACTIVE = 'active',\n PAUSED = 'paused',\n COMPLETED = 'completed',\n ARCHIVED = 'archived'\n}\n\n\nexport interface TreatmentMetadata {\n strategy?: string;\n tone?: string;\n ctaStyle?: string;\n emotionalHook?: string;\n reasoning?: string;\n tags?: string[];\n generated?: boolean;\n generatedAt?: string;\n}\n\nexport interface VariantConfig {\n content: string;\n metadata?: TreatmentMetadata & Record<string, any>;\n}\n\nexport enum BanditAlgorithm {\n THOMPSON_SAMPLING = 'thompson_sampling',\n CONTEXTUAL_LINEAR = 'contextual_linear'\n}\n\nexport enum Posterior {\n BETA = 'beta',\n NORMAL_GAMMA = 'normal_gamma'\n}\n\nexport function normalizePosterior(posterior: string | null | undefined): Posterior {\n return posterior === Posterior.NORMAL_GAMMA ? Posterior.NORMAL_GAMMA : Posterior.BETA;\n}\n\nexport enum ForgettingMode {\n NONE = 'none',\n DISCOUNTED = 'discounted',\n SLIDING_WINDOW = 'sliding_window'\n}\n\nexport interface ForgettingConfig {\n mode: ForgettingMode;\n halfLifeMs?: number;\n windowMs?: number;\n}\n\nexport const DEFAULT_HALF_LIFE_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport const DEFAULT_WINDOW_MS = 14 * 24 * 60 * 60 * 1000;\n\nexport const STATIONARY: ForgettingConfig = { mode: ForgettingMode.NONE };\n\nexport function resolveForgetting(config?: Partial<ForgettingConfig> | null): ForgettingConfig {\n switch (config?.mode) {\n case ForgettingMode.DISCOUNTED:\n return {\n mode: ForgettingMode.DISCOUNTED,\n halfLifeMs: config.halfLifeMs && config.halfLifeMs > 0\n ? config.halfLifeMs\n : DEFAULT_HALF_LIFE_MS\n };\n case ForgettingMode.SLIDING_WINDOW:\n return {\n mode: ForgettingMode.SLIDING_WINDOW,\n windowMs: config.windowMs && config.windowMs > 0\n ? config.windowMs\n : DEFAULT_WINDOW_MS\n };\n default:\n return STATIONARY;\n }\n}\n\nconst LEGACY_ALGORITHM_ALIASES: Record<string, BanditAlgorithm> = {\n epsilon_greedy: BanditAlgorithm.THOMPSON_SAMPLING,\n ucb1: BanditAlgorithm.THOMPSON_SAMPLING,\n contextual_neural: BanditAlgorithm.CONTEXTUAL_LINEAR\n};\n\nexport function normalizeAlgorithm(algorithm: string | null | undefined): BanditAlgorithm {\n if (algorithm === BanditAlgorithm.THOMPSON_SAMPLING) return BanditAlgorithm.THOMPSON_SAMPLING;\n if (algorithm === BanditAlgorithm.CONTEXTUAL_LINEAR) return BanditAlgorithm.CONTEXTUAL_LINEAR;\n return LEGACY_ALGORITHM_ALIASES[algorithm ?? ''] ?? BanditAlgorithm.THOMPSON_SAMPLING;\n}\n\nexport enum ContentType {\n PLAIN_TEXT = 'plain_text',\n MARKDOWN = 'markdown',\n HTML = 'html',\n CSS = 'css',\n JAVASCRIPT = 'javascript',\n JSON = 'json',\n IMAGE = 'image',\n VIDEO = 'video',\n AUDIO = 'audio',\n DOCUMENT = 'document',\n}\n\nexport function getContentTypeCategory(contentType: ContentType): 'text' | 'code' | 'file' {\n switch (contentType) {\n case ContentType.PLAIN_TEXT:\n case ContentType.MARKDOWN:\n return 'text';\n case ContentType.HTML:\n case ContentType.CSS:\n case ContentType.JAVASCRIPT:\n case ContentType.JSON:\n return 'code';\n case ContentType.IMAGE:\n case ContentType.VIDEO:\n case ContentType.AUDIO:\n case ContentType.DOCUMENT:\n return 'file';\n }\n}\n\nexport interface BanditConfig {\n algorithm: BanditAlgorithm;\n parameters: Record<string, any>;\n}\n\nexport interface ExperimentStats {\n totalAssignments: number;\n totalConversions: number;\n conversionRate: number;\n treatmentStats: Record<string, TreatmentStats>;\n}\n\nexport interface TreatmentStats {\n assignments: number;\n conversions: number;\n conversionRate: number;\n confidence: number;\n lift?: number;\n}\n\nexport interface APIResponse<T = any> {\n success: boolean;\n data?: T;\n error?: string;\n timestamp: Date;\n}\n\nexport interface CreateExperimentRequest {\n name: string;\n description?: string;\n algorithm: BanditAlgorithm;\n contentType: ContentType;\n algorithmConfig?: Record<string, any>;\n treatments?: Omit<Treatment, 'id' | 'experimentId' | 'createdAt'>[];\n}\n\nexport interface AssignmentRequest {\n experimentId: string;\n userId: string;\n context?: ContextualFeatures;\n sessionId?: string;\n pageUrl?: string;\n}\n\n\nexport interface TrackNamedEventRequest {\n name: string;\n category?: string;\n value?: number;\n metadata?: Record<string, any>;\n sessionId: string;\n userId?: string;\n assignmentId?: string;\n}\n\n\nexport enum FunnelStatus {\n DRAFT = 'draft',\n ACTIVE = 'active',\n PAUSED = 'paused',\n COMPLETED = 'completed',\n}\n\nexport interface Funnel {\n id: string;\n name: string;\n description?: string;\n status: FunnelStatus;\n inactivityTimeoutMs?: number;\n revisionWindowMs?: number;\n companyId: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface FunnelRewardRequest {\n funnelId: string;\n userId: string;\n value: number;\n}\n\n\nexport interface ApiKey {\n id: string;\n name: string;\n keyPrefix: string;\n status: 'active' | 'revoked';\n createdBy: string;\n createdAt: Date;\n lastUsedAt?: Date;\n revokedAt?: Date;\n revokedBy?: string;\n}\n\nexport interface CreateApiKeyRequest {\n name: string;\n}\n\nexport interface CreateApiKeyResponse {\n id: string;\n key: string;\n name: string;\n}\n\n\nexport type IdentifyMergeReason =\n | 'user_signup'\n | 'email_verification'\n | 'social_login'\n | 'account_recovery'\n | 'manual';\n\nexport interface IdentifyUserRequest {\n anonymousId: string;\n identifiedUserId: string;\n mergeReason?: IdentifyMergeReason;\n}\n\nexport interface IdentifyUserResponse {\n anonymousId: string;\n identifiedUserId: string;\n alreadyMapped: boolean;\n}\n\nexport interface ResolvedIdentity {\n canonicalId: string;\n anonymousIds: string[];\n isIdentified: boolean;\n}\n\n\nexport type BundleMode = 'factored' | 'joint';\n\nexport interface Bundle {\n id: string;\n name: string;\n description?: string;\n status: ExperimentStatus;\n mode: BundleMode;\n funnelId?: string;\n assignmentTtl: number;\n conversionWindowMs: number;\n inactivityTimeoutMs?: number;\n revisionWindowMs?: number;\n companyId: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface BundleAssignmentRequest {\n bundleId: string;\n userId: string;\n context?: ContextualFeatures;\n sessionId?: string;\n pageUrl?: string;\n}\n\nexport interface BundleSlotAssignment {\n assignmentId: string;\n experimentId: string;\n treatmentId: string;\n config: VariantConfig | Record<string, unknown>;\n}\n\nexport interface BundleAssignmentResponse {\n bundleAssignmentId: string;\n slots: Record<string, BundleSlotAssignment>;\n}\n\nexport interface SnapshotTreatment {\n id: string;\n name?: string;\n config: unknown;\n pct: number;\n}\n\nexport interface SnapshotExperiment {\n mode: 'snapshot' | 'network';\n assignmentTtl: number;\n treatments: SnapshotTreatment[];\n}\n\nexport interface CompanySnapshot {\n ttl: number;\n generatedAt: number;\n experiments: Record<string, SnapshotExperiment>;\n}\n","import { Treatment } from './types.js';\n\nexport function generateId(): string {\n return Math.random().toString(36).substring(2) + Date.now().toString(36);\n}\n\nexport function validateTreatmentWeights(treatments: Treatment[]): boolean {\n const totalWeight = treatments.reduce((sum, treatment) => sum + (treatment.weight || 1), 0);\n return totalWeight > 0;\n}\n\nexport function normalizeTreatmentWeights(treatments: Treatment[]): Treatment[] {\n const totalWeight = treatments.reduce((sum, treatment) => sum + (treatment.weight || 1), 0);\n \n return treatments.map(treatment => ({\n ...treatment,\n weight: (treatment.weight || 1) / totalWeight\n }));\n}\n\nexport function calculateConfidenceInterval(\n successes: number,\n trials: number,\n confidence: number = 0.95\n): { lower: number; upper: number } {\n if (trials === 0) return { lower: 0, upper: 0 };\n \n const z = confidence === 0.95 ? 1.96 : confidence === 0.99 ? 2.576 : 1.645;\n const p = successes / trials;\n const margin = z * Math.sqrt((p * (1 - p)) / trials);\n \n return {\n lower: Math.max(0, p - margin),\n upper: Math.min(1, p + margin)\n };\n}\n\nexport function isValidEmail(email: string): boolean {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n return emailRegex.test(email);\n}\n\nexport function sanitizeMetadata(metadata: Record<string, any>): Record<string, any> {\n const sanitized: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(metadata)) {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n sanitized[key] = value;\n } else if (value !== null && value !== undefined) {\n sanitized[key] = String(value);\n }\n }\n\n return sanitized;\n}\n\nexport function generateSubdomain(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .substring(0, 63);\n}\n\nexport function isValidSubdomain(subdomain: string): boolean {\n if (!subdomain || subdomain.length === 0 || subdomain.length > 63) {\n return false;\n }\n\n const subdomainRegex = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;\n\n const reserved = [\n 'www', 'api', 'app', 'admin', 'dashboard', 'mail', 'ftp', 'localhost',\n 'staging', 'dev', 'test', 'demo', 'blog', 'docs', 'status', 'help',\n 'support', 'cdn', 'static', 'assets', 'secure', 'payment', 'pay'\n ];\n\n return subdomainRegex.test(subdomain) && !reserved.includes(subdomain);\n}\n\nexport function extractSubdomain(hostname: string, baseDomain: string): string | null {\n if (!hostname || !baseDomain) {\n return null;\n }\n\n const cleanHostname = hostname.split(':')[0];\n\n if (!cleanHostname.endsWith(baseDomain)) {\n return null;\n }\n\n const subdomain = cleanHostname.slice(0, -(baseDomain.length + 1));\n\n if (!subdomain || subdomain === 'www') {\n return null;\n }\n\n return subdomain;\n}\n\nexport function buildSubdomainUrl(\n subdomain: string,\n baseDomain: string,\n path: string = '',\n protocol: string = 'https'\n): string {\n const url = `${protocol}://${subdomain}.${baseDomain}`;\n return path ? `${url}${path.startsWith('/') ? path : '/' + path}` : url;\n}","\nexport const EMAIL_TOKEN_PARAM = 'bt';\n\nexport interface SigningKey {\n version: number;\n secret: string;\n}\n\nexport interface EmailTokenPayload {\n c: string;\n r: string;\n k: number[];\n l: number;\n v?: number;\n}\n\nexport type VerifiedToken =\n | { valid: true; payload: Required<EmailTokenPayload> }\n | { valid: false; reason: 'malformed' | 'unknown_key' | 'bad_signature' };\n\n\nconst encoder = new TextEncoder();\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction base64UrlDecode(value: string): Uint8Array {\n const padded = value.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4));\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\nasync function hmac(secret: string, message: string): Promise<Uint8Array> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n return new Uint8Array(await crypto.subtle.sign('HMAC', key, encoder.encode(message)));\n}\n\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\n\nexport async function hashRecipient(recipientId: string, companySecret: string): Promise<string> {\n const digest = await hmac(companySecret, `recipient:${recipientId}`);\n return base64UrlEncode(digest.slice(0, 16));\n}\n\nexport async function mintEmailToken(\n payload: EmailTokenPayload,\n key: SigningKey\n): Promise<string> {\n const body = base64UrlEncode(\n encoder.encode(\n JSON.stringify({\n c: payload.c,\n r: payload.r,\n k: payload.k,\n l: payload.l,\n v: key.version,\n })\n )\n );\n\n const signature = base64UrlEncode(await hmac(key.secret, body));\n return `${body}.${signature}`;\n}\n\nexport async function verifyEmailToken(\n token: string,\n keys: SigningKey[]\n): Promise<VerifiedToken> {\n const parts = token.split('.');\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n return { valid: false, reason: 'malformed' };\n }\n const [body, signature] = parts;\n\n let payload: EmailTokenPayload;\n try {\n payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(body)));\n } catch {\n return { valid: false, reason: 'malformed' };\n }\n\n if (\n !payload ||\n typeof payload.c !== 'string' ||\n typeof payload.r !== 'string' ||\n !Array.isArray(payload.k) ||\n !payload.k.every((arm) => Number.isInteger(arm) && arm >= 0) ||\n !Number.isInteger(payload.l) ||\n (payload.l as number) < 0 ||\n !Number.isInteger(payload.v)\n ) {\n return { valid: false, reason: 'malformed' };\n }\n\n const key = keys.find((candidate) => candidate.version === payload.v);\n if (!key) return { valid: false, reason: 'unknown_key' };\n\n const expected = base64UrlEncode(await hmac(key.secret, body));\n if (!timingSafeEqual(expected, signature)) {\n return { valid: false, reason: 'bad_signature' };\n }\n\n return { valid: true, payload: payload as Required<EmailTokenPayload> };\n}\n\nexport function appendEmailToken(url: string, token: string): string {\n const hashIndex = url.indexOf('#');\n const fragment = hashIndex === -1 ? '' : url.slice(hashIndex);\n const base = hashIndex === -1 ? url : url.slice(0, hashIndex);\n const separator = base.includes('?') ? '&' : '?';\n\n return `${base}${separator}${EMAIL_TOKEN_PARAM}=${encodeURIComponent(token)}${fragment}`;\n}\n","\nexport interface PlanArm {\n treatmentId: string;\n armIndex: number;\n}\n\nexport interface PlanSlot {\n experimentId: string;\n name: string;\n arms: PlanArm[];\n probabilities: number[];\n configs: unknown[];\n}\n\nexport interface EmailPlan {\n campaignId: string;\n slots: PlanSlot[];\n linkTable: string[];\n salt: string;\n signingKey: { version: number; secret: string };\n refreshEvery: number;\n expiresAt: number;\n}\n\nexport async function uniformFor(\n salt: string,\n recipientId: string,\n slotIndex: number\n): Promise<number> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(salt),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n const mac = new Uint8Array(\n await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(`${recipientId}:${slotIndex}`))\n );\n\n let value = 0;\n for (let i = 0; i < 6; i++) value = value * 256 + mac[i];\n return value / 2 ** 48;\n}\n\nexport function pickArm(probabilities: number[], uniform: number): number {\n if (probabilities.length === 0) return -1;\n\n const total = probabilities.reduce((sum, p) => sum + Math.max(p, 0), 0);\n if (total <= 0) {\n return Math.min(probabilities.length - 1, Math.floor(uniform * probabilities.length));\n }\n\n const target = uniform * total;\n let cumulative = 0;\n for (let i = 0; i < probabilities.length; i++) {\n cumulative += Math.max(probabilities[i], 0);\n if (target < cumulative) return i;\n }\n\n return probabilities.length - 1;\n}\n\nexport async function sampleCombination(\n plan: EmailPlan,\n recipientId: string\n): Promise<number[]> {\n const combination: number[] = [];\n\n for (let i = 0; i < plan.slots.length; i++) {\n const slot = plan.slots[i];\n const uniform = await uniformFor(plan.salt, recipientId, i);\n const chosen = pickArm(slot.probabilities, uniform);\n combination.push(chosen === -1 ? 0 : slot.arms[chosen]?.armIndex ?? 0);\n }\n\n return combination;\n}\n\nexport async function impressionManifest(\n plan: EmailPlan,\n recipientIds: string[]\n): Promise<Array<{ experimentId: string; treatmentId: string; count: number }>> {\n const counts = new Map<string, { experimentId: string; treatmentId: string; count: number }>();\n\n for (const recipientId of recipientIds) {\n for (let i = 0; i < plan.slots.length; i++) {\n const slot = plan.slots[i];\n const uniform = await uniformFor(plan.salt, recipientId, i);\n const chosen = pickArm(slot.probabilities, uniform);\n const arm = slot.arms[chosen === -1 ? 0 : chosen];\n if (!arm) continue;\n\n const key = `${slot.experimentId}:${arm.treatmentId}`;\n const entry = counts.get(key);\n if (entry) entry.count++;\n else\n counts.set(key, {\n experimentId: slot.experimentId,\n treatmentId: arm.treatmentId,\n count: 1,\n });\n }\n }\n\n return Array.from(counts.values());\n}\n","\nexport interface SlotBinding {\n $slot: string;\n}\n\nexport type Bound<T> = T | SlotBinding;\n\nexport function isSlotBinding(value: unknown): value is SlotBinding {\n return typeof value === 'object' && value !== null && typeof (value as SlotBinding).$slot === 'string';\n}\n\nexport interface HeadingBlock {\n type: 'heading';\n text: Bound<string>;\n level?: 1 | 2 | 3;\n align?: 'left' | 'center' | 'right';\n color?: Bound<string>;\n}\n\nexport interface TextBlock {\n type: 'text';\n text: Bound<string>;\n align?: 'left' | 'center' | 'right';\n color?: Bound<string>;\n}\n\nexport interface ButtonBlock {\n type: 'button';\n text: Bound<string>;\n href: Bound<string>;\n backgroundColor?: Bound<string>;\n textColor?: Bound<string>;\n align?: 'left' | 'center' | 'right';\n}\n\nexport interface ImageBlock {\n type: 'image';\n src: Bound<string>;\n alt: Bound<string>;\n href?: Bound<string>;\n width?: number;\n}\n\nexport interface DividerBlock {\n type: 'divider';\n color?: string;\n}\n\nexport interface SpacerBlock {\n type: 'spacer';\n height?: number;\n}\n\nexport type EmailBlock =\n | HeadingBlock\n | TextBlock\n | ButtonBlock\n | ImageBlock\n | DividerBlock\n | SpacerBlock;\n\nexport interface EmailDocument {\n subject: Bound<string>;\n preheader?: Bound<string>;\n blocks: EmailBlock[];\n backgroundColor?: string;\n contentWidth?: number;\n}\n\nconst CONTENT_WIDTH = 600;\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\nfunction resolve(value: Bound<string> | undefined, slots: Record<string, string>, missing: string[]): string {\n if (value === undefined) return '';\n if (!isSlotBinding(value)) return value;\n\n const resolved = slots[value.$slot];\n if (resolved === undefined) {\n missing.push(value.$slot);\n return '';\n }\n return resolved;\n}\n\nexport function extractDocumentSlots(document: EmailDocument): string[] {\n const names: string[] = [];\n const seen = new Set<string>();\n\n const visit = (value: unknown) => {\n if (isSlotBinding(value)) {\n if (!seen.has(value.$slot)) {\n seen.add(value.$slot);\n names.push(value.$slot);\n }\n return;\n }\n if (Array.isArray(value)) value.forEach(visit);\n else if (typeof value === 'object' && value !== null) Object.values(value).forEach(visit);\n };\n\n visit(document.subject);\n visit(document.preheader);\n document.blocks.forEach(visit);\n\n return names;\n}\n\nfunction renderBlock(block: EmailBlock, slots: Record<string, string>, missing: string[]): string {\n const align = (block as { align?: string }).align ?? 'left';\n\n switch (block.type) {\n case 'heading': {\n const size = block.level === 3 ? 18 : block.level === 2 ? 22 : 28;\n const color = escapeHtml(resolve(block.color, slots, missing) || '#111111');\n return `<tr><td style=\"padding:12px 24px;text-align:${align};font-family:Arial,Helvetica,sans-serif;font-size:${size}px;line-height:1.3;font-weight:bold;color:${color}\">${escapeHtml(\n resolve(block.text, slots, missing)\n )}</td></tr>`;\n }\n\n case 'text': {\n const color = escapeHtml(resolve(block.color, slots, missing) || '#333333');\n return `<tr><td style=\"padding:8px 24px;text-align:${align};font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:${color}\">${escapeHtml(\n resolve(block.text, slots, missing)\n )}</td></tr>`;\n }\n\n case 'button': {\n const href = escapeHtml(resolve(block.href, slots, missing));\n const label = escapeHtml(resolve(block.text, slots, missing));\n const bg = escapeHtml(resolve(block.backgroundColor, slots, missing) || '#1a73e8');\n const fg = escapeHtml(resolve(block.textColor, slots, missing) || '#ffffff');\n\n return `<tr><td style=\"padding:16px 24px;text-align:${align}\">\n<!--[if mso]><v:roundrect xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" href=\"${href}\" style=\"height:44px;v-text-anchor:middle;width:220px;\" arcsize=\"12%\" strokecolor=\"${bg}\" fillcolor=\"${bg}\"><w:anchorlock/><center style=\"color:${fg};font-family:Arial,sans-serif;font-size:16px;font-weight:bold;\">${label}</center></v:roundrect><![endif]-->\n<!--[if !mso]><!--><a href=\"${href}\" style=\"display:inline-block;padding:13px 28px;background-color:${bg};color:${fg};font-family:Arial,Helvetica,sans-serif;font-size:16px;font-weight:bold;text-decoration:none;border-radius:6px\">${label}</a><!--<![endif]-->\n</td></tr>`;\n }\n\n case 'image': {\n const src = escapeHtml(resolve(block.src, slots, missing));\n const alt = escapeHtml(resolve(block.alt, slots, missing));\n const width = block.width ?? CONTENT_WIDTH;\n const img = `<img src=\"${src}\" alt=\"${alt}\" width=\"${width}\" style=\"display:block;border:0;outline:none;text-decoration:none;max-width:100%;height:auto\" />`;\n const href = block.href ? escapeHtml(resolve(block.href, slots, missing)) : '';\n\n return `<tr><td style=\"padding:0;text-align:center\">${\n href ? `<a href=\"${href}\">${img}</a>` : img\n }</td></tr>`;\n }\n\n case 'divider':\n return `<tr><td style=\"padding:12px 24px\"><table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr><td style=\"border-top:1px solid ${escapeHtml(\n block.color ?? '#e5e5e5'\n )};font-size:0;line-height:0\">&nbsp;</td></tr></table></td></tr>`;\n\n case 'spacer':\n return `<tr><td style=\"height:${block.height ?? 24}px;font-size:0;line-height:0\">&nbsp;</td></tr>`;\n\n default:\n return '';\n }\n}\n\nexport interface RenderedDocument {\n subject: string;\n html: string;\n missingSlots: string[];\n}\n\nexport function renderEmailDocument(\n document: EmailDocument,\n slots: Record<string, string>\n): RenderedDocument {\n const missingSlots: string[] = [];\n const width = document.contentWidth ?? CONTENT_WIDTH;\n const background = escapeHtml(document.backgroundColor ?? '#f4f4f5');\n\n const subject = resolve(document.subject, slots, missingSlots);\n const preheader = resolve(document.preheader, slots, missingSlots);\n\n const body = document.blocks.map((block) => renderBlock(block, slots, missingSlots)).join('\\n');\n\n const html = `<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<meta name=\"x-apple-disable-message-reformatting\" />\n<title>${escapeHtml(subject)}</title>\n<!--[if mso]><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->\n</head>\n<body style=\"margin:0;padding:0;background-color:${background}\">\n<div style=\"display:none;font-size:0;line-height:0;max-height:0;overflow:hidden;mso-hide:all\">${escapeHtml(\n preheader\n )}</div>\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background-color:${background}\">\n<tr><td align=\"center\" style=\"padding:24px 12px\">\n<table role=\"presentation\" width=\"${width}\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"width:${width}px;max-width:100%;background-color:#ffffff;border-radius:8px\">\n${body}\n</table>\n</td></tr>\n</table>\n</body>\n</html>`;\n\n return { subject, html, missingSlots };\n}\n","export const STOREFRONT_KINDS = ['text', 'image', 'ordering', 'offer'] as const;\nexport type StorefrontKind = (typeof STOREFRONT_KINDS)[number];\n\nexport const PLACEMENTS_BY_KIND = {\n text: ['hero_headline', 'hero_subhead', 'cta_primary', 'banner_text', 'badge_text'],\n image: ['hero_image', 'banner_image', 'feature_image'],\n ordering: ['product_list_primary', 'product_list_secondary'],\n offer: ['offer_banner'],\n} as const satisfies Record<StorefrontKind, readonly string[]>;\n\nexport type Placement = (typeof PLACEMENTS_BY_KIND)[StorefrontKind][number];\n\nexport const PLACEMENTS: readonly Placement[] = STOREFRONT_KINDS.flatMap(\n (kind) => PLACEMENTS_BY_KIND[kind]\n);\n\nexport function isPlacement(value: unknown): value is Placement {\n return typeof value === 'string' && (PLACEMENTS as readonly string[]).includes(value);\n}\n\nexport function kindOfPlacement(placement: Placement): StorefrontKind {\n const found = STOREFRONT_KINDS.find((kind) =>\n (PLACEMENTS_BY_KIND[kind] as readonly string[]).includes(placement)\n );\n if (!found) throw new Error(`Unknown placement: ${placement}`);\n return found;\n}\n\nexport const CONTENT_TYPE_BY_KIND = {\n text: { contentType: 'TEXT', contentSubtype: 'plain_text' },\n image: { contentType: 'FILE', contentSubtype: 'image' },\n ordering: { contentType: 'CODE', contentSubtype: 'json' },\n offer: { contentType: 'CODE', contentSubtype: 'json' },\n} as const satisfies Record<StorefrontKind, { contentType: string; contentSubtype: string }>;\n\nexport const MIN_ORDERING_ITEMS = 2;\nexport const MAX_ORDERING_ITEMS = 12;\n\nexport interface ImageContent {\n url: string;\n alt: string;\n width: number;\n height: number;\n sourceId: string;\n}\n\nexport interface OfferContent {\n discountId: string;\n code: string;\n headline: string;\n subtext: string;\n}\n\nexport type TreatmentContent =\n | { kind: 'text'; text: string }\n | { kind: 'image'; image: ImageContent }\n | { kind: 'ordering'; productIds: string[] }\n | { kind: 'offer'; offer: OfferContent | null };\n\nexport function isShopifyCdnUrl(value: string): boolean {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n return url.protocol === 'https:' && url.hostname === 'cdn.shopify.com';\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction isPositiveInt(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value > 0;\n}\n\nfunction parseImage(raw: unknown): ImageContent | null {\n if (!isRecord(raw)) return null;\n const { url, alt, width, height, sourceId } = raw;\n if (typeof url !== 'string' || !isShopifyCdnUrl(url)) return null;\n if (typeof alt !== 'string') return null;\n if (!isPositiveInt(width) || !isPositiveInt(height)) return null;\n if (!isNonEmptyString(sourceId)) return null;\n return { url, alt, width, height, sourceId };\n}\n\nfunction parseOrdering(raw: unknown): string[] | null {\n if (!Array.isArray(raw)) return null;\n if (raw.length < MIN_ORDERING_ITEMS || raw.length > MAX_ORDERING_ITEMS) return null;\n if (!raw.every((id) => typeof id === 'string' && /^\\d+$/.test(id))) return null;\n if (new Set(raw).size !== raw.length) return null;\n return raw as string[];\n}\n\nfunction parseOffer(raw: unknown): OfferContent | null | undefined {\n if (raw === null) return null;\n if (!isRecord(raw)) return undefined;\n const { discountId, code, headline, subtext } = raw;\n if (!isNonEmptyString(discountId) || !isNonEmptyString(code)) return undefined;\n if (typeof headline !== 'string' || typeof subtext !== 'string') return undefined;\n return { discountId, code, headline, subtext };\n}\n\nexport function parseTreatmentContent(\n kind: StorefrontKind,\n config: unknown\n): TreatmentContent | null {\n if (!isRecord(config)) return null;\n const content = config.content;\n switch (kind) {\n case 'text':\n return isNonEmptyString(content) ? { kind, text: content } : null;\n case 'image': {\n const image = parseImage(content);\n return image ? { kind, image } : null;\n }\n case 'ordering': {\n const productIds = parseOrdering(content);\n return productIds ? { kind, productIds } : null;\n }\n case 'offer': {\n const offer = parseOffer(content);\n return offer === undefined ? null : { kind, offer };\n }\n }\n}\n\nexport function serializeTreatmentConfig(content: TreatmentContent): { content: unknown } {\n switch (content.kind) {\n case 'text':\n return { content: content.text };\n case 'image':\n return { content: content.image };\n case 'ordering':\n return { content: content.productIds };\n case 'offer':\n return { content: content.offer };\n }\n}\n","const SCHEME = /^[a-z][a-z0-9+.-]*:\\/\\//i;\n\nconst isWildcard = (segment: string) => segment === '*' || segment === '**';\n\nfunction segmentsOf(path: string): string[] {\n return path.split('/').filter(Boolean);\n}\n\nexport function normalizeConversionPath(input: string | undefined): string | undefined {\n const trimmed = input?.trim();\n if (!trimmed) return undefined;\n if (/[?#]/.test(trimmed)) {\n throw new Error('Leave out the query string: enter a path like /thank-you or /orders/*/thanks');\n }\n const withScheme = SCHEME.test(trimmed) ? trimmed : `https://placeholder.invalid/${trimmed.replace(/^\\/+/, '')}`;\n let pathname: string;\n try {\n pathname = new URL(withScheme).pathname;\n } catch {\n throw new Error('Enter the conversion page as a path like /thank-you');\n }\n const segments = segmentsOf(pathname);\n if (segments.some((segment) => segment.includes('*') && !isWildcard(segment))) {\n throw new Error('Use * for a whole path segment, like /orders/*/thanks');\n }\n if (segments.length > 0 && segments.every(isWildcard)) {\n throw new Error('That pattern matches every page. Add a fixed part, like /orders/*/thanks');\n }\n return pathname.length > 1 ? pathname.replace(/\\/+$/, '') : pathname;\n}\n\nfunction matchSegments(pattern: string[], path: string[]): boolean {\n if (pattern.length === 0) return path.length === 0;\n const [head, ...rest] = pattern;\n if (head === '**') {\n for (let skip = 0; skip <= path.length; skip++) {\n if (matchSegments(rest, path.slice(skip))) return true;\n }\n return false;\n }\n if (path.length === 0) return false;\n return (head === '*' || head === path[0]) && matchSegments(rest, path.slice(1));\n}\n\nexport function matchesConversionPath(pattern: string, pathname: string): boolean {\n return matchSegments(segmentsOf(pattern), segmentsOf(pathname));\n}\n","\nexport const SESSION_ID_KEY = 'bandits_session_id';\nexport const SESSION_CREATED_AT_KEY = 'bandits_session_created_at';\n\nexport const VISITOR_ID_KEY = 'bandits_visitor_id';\n\nexport const DEFAULT_SESSION_TTL_MS = 86400000;\n\nexport function generateId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\nexport interface SessionOptions {\n storage?: Storage | null;\n now?: () => number;\n ttlMs?: number;\n}\n\nexport function getOrCreateSessionId(options: SessionOptions = {}): string {\n const { storage, now = () => Date.now(), ttlMs = DEFAULT_SESSION_TTL_MS } = options;\n if (!storage) return generateId();\n\n try {\n const stored = storage.getItem(SESSION_ID_KEY);\n const storedAt = storage.getItem(SESSION_CREATED_AT_KEY);\n if (stored && storedAt) {\n const age = now() - parseInt(storedAt, 10);\n if (age < ttlMs) return stored;\n }\n const id = generateId();\n storage.setItem(SESSION_ID_KEY, id);\n storage.setItem(SESSION_CREATED_AT_KEY, String(now()));\n return id;\n } catch {\n return generateId();\n }\n}\n\nexport function getOrCreateVisitorId(storage: Storage | null | undefined): string {\n if (!storage) return generateId();\n try {\n const stored = storage.getItem(VISITOR_ID_KEY);\n if (stored) return stored;\n const id = generateId();\n storage.setItem(VISITOR_ID_KEY, id);\n return id;\n } catch {\n return generateId();\n }\n}\n\nexport function defaultSessionStorage(): Storage | null {\n return typeof window !== 'undefined' && window.localStorage ? window.localStorage : null;\n}\n","import {\n AssignmentRequest,\n TrackNamedEventRequest,\n BanditResult,\n APIResponse,\n ContextualFeatures,\n BundleAssignmentRequest,\n BundleAssignmentResponse,\n matchesConversionPath,\n} from '@runbandit/shared';\nimport { defaultSessionStorage, generateId, getOrCreateSessionId, getOrCreateVisitorId } from './session.js';\n\nexport interface BanditsClientConfig {\n apiUrl: string;\n apiKey: string;\n timeout?: number;\n batchSize?: number;\n flushInterval?: number;\n sessionTtl?: number;\n}\n\nexport interface Assignment {\n assignmentId: string;\n treatmentId: string;\n experimentId: string;\n userId: string;\n config: any;\n funnelId?: string;\n}\n\nfunction currentPageUrl(): string | undefined {\n if (typeof location !== 'undefined' && location.origin && location.pathname) {\n return location.origin + location.pathname;\n }\n return undefined;\n}\n\nexport class BanditsClient {\n private config: BanditsClientConfig;\n private namedEventQueue: TrackNamedEventRequest[] = [];\n private sessionId: string;\n readonly visitorId: string;\n private flushTimer?: NodeJS.Timeout;\n\n constructor(config: BanditsClientConfig) {\n this.config = {\n timeout: 5000,\n batchSize: 100,\n flushInterval: 10000,\n sessionTtl: 86400000,\n ...config\n };\n this.sessionId = this.getOrCreateSessionId();\n this.visitorId = getOrCreateVisitorId(defaultSessionStorage());\n this.startBatchFlush();\n }\n\n async getAssignment(\n experimentId: string,\n userId: string = this.visitorId,\n context?: ContextualFeatures\n ): Promise<Assignment> {\n const request: AssignmentRequest = {\n experimentId,\n userId,\n context,\n sessionId: this.sessionId\n };\n const pageUrl = currentPageUrl();\n if (pageUrl) request.pageUrl = pageUrl;\n\n const response = await this.makeRequest<any>(\n '/assignments',\n 'POST',\n request\n );\n\n if (!response.success || !response.data) {\n throw new Error(response.error || 'Failed to get assignment');\n }\n\n const assignment = response.data?.data ?? response.data;\n\n if (!assignment?.assignmentId) {\n throw new Error('Invalid assignment response');\n }\n\n return {\n assignmentId: assignment.assignmentId,\n treatmentId: assignment.treatmentId,\n experimentId,\n userId,\n config: assignment.treatment?.config || {},\n funnelId: assignment.funnelId\n };\n }\n\n trackNamedEvent(\n event: { name: string; category?: string; value?: number; metadata?: Record<string, any> },\n opts?: { immediate?: boolean; userId?: string; assignmentId?: string }\n ): void {\n const request: TrackNamedEventRequest = {\n name: event.name,\n category: event.category || 'custom',\n value: event.value,\n metadata: event.metadata,\n sessionId: this.sessionId,\n };\n if (opts?.userId) request.userId = opts.userId;\n if (opts?.assignmentId) request.assignmentId = opts.assignmentId;\n\n if (opts?.immediate) {\n const url = `${this.normalizeApiUrl()}/events`;\n try {\n void fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n keepalive: true,\n });\n } catch {\n }\n return;\n }\n\n this.namedEventQueue.push(request);\n if (this.namedEventQueue.length >= this.config.batchSize!) {\n this.flush();\n }\n }\n\n async flush(): Promise<void> {\n const namedEvents = [...this.namedEventQueue];\n this.namedEventQueue = [];\n if (namedEvents.length === 0) return;\n\n try {\n await fetch(`${this.normalizeApiUrl()}/events/batch`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ events: namedEvents }),\n keepalive: true,\n });\n } catch (error) {\n this.namedEventQueue.unshift(...namedEvents);\n throw error;\n }\n }\n\n async identify(\n anonymousId: string,\n identifiedUserId: string,\n mergeReason?: 'user_signup' | 'email_verification' | 'social_login' | 'account_recovery' | 'manual'\n ): Promise<void> {\n const response = await this.makeRequest(\n '/identify',\n 'POST',\n {\n anonymousId,\n identifiedUserId,\n mergeReason: mergeReason || 'manual',\n }\n );\n\n if (!response.success) {\n throw new Error(response.error || 'Failed to identify user');\n }\n }\n\n async label(userId: string, labels: Record<string, string | boolean>): Promise<void> {\n const response = await this.makeRequest('/labels', 'POST', { userId, labels });\n\n if (!response.success) {\n throw new Error(response.error || 'Failed to record labels');\n }\n }\n\n async trackFunnelReward(\n funnelId: string,\n userId: string,\n value: number\n ): Promise<void> {\n const response = await this.makeRequest(\n `/funnels/${funnelId}/reward`,\n 'POST',\n { userId, value }\n );\n\n if (!response.success) {\n throw new Error(response.error || 'Failed to record funnel reward');\n }\n }\n\n convert(funnelId: string, value = 1): Promise<void> {\n return this.trackFunnelReward(funnelId, this.visitorId, value);\n }\n\n async convertOn(funnelId: string, pathPattern: string, value = 1): Promise<boolean> {\n if (typeof location === 'undefined' || !matchesConversionPath(pathPattern, location.pathname)) return false;\n await this.convert(funnelId, value);\n return true;\n }\n\n async getBundle(\n bundleId: string,\n userId: string,\n context?: ContextualFeatures\n ): Promise<BundleAssignmentResponse> {\n const request: BundleAssignmentRequest = {\n bundleId,\n userId,\n context,\n sessionId: this.sessionId,\n };\n const pageUrl = currentPageUrl();\n if (pageUrl) request.pageUrl = pageUrl;\n\n const response = await this.makeRequest<any>('/bundles/assignment', 'POST', request);\n\n if (!response.success || !response.data) {\n throw new Error(response.error || 'Failed to get bundle assignment');\n }\n\n const payload = response.data?.data ?? response.data;\n if (!payload?.bundleAssignmentId || !payload?.slots) {\n throw new Error('Invalid bundle assignment response');\n }\n\n return {\n bundleAssignmentId: payload.bundleAssignmentId,\n slots: payload.slots,\n };\n }\n\n async trackBundleReward(bundleId: string, userId: string, value: number): Promise<void> {\n const response = await this.makeRequest(`/bundles/${bundleId}/reward`, 'POST', {\n userId,\n value,\n });\n if (!response.success) {\n throw new Error(response.error || 'Failed to record bundle reward');\n }\n }\n\n private generateId(): string {\n return generateId();\n }\n\n private normalizeApiUrl(): string {\n let baseUrl = this.config.apiUrl.replace(/\\/$/, '');\n if (!baseUrl.endsWith('/api')) {\n baseUrl = `${baseUrl}/api`;\n }\n return baseUrl;\n }\n\n private getOrCreateSessionId(): string {\n return getOrCreateSessionId({\n storage: defaultSessionStorage(),\n ttlMs: this.config.sessionTtl,\n });\n }\n\n private async makeRequest<T = any>(\n endpoint: string,\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n body?: any,\n extraHeaders?: Record<string, string>\n ): Promise<APIResponse<T>> {\n const url = `${this.normalizeApiUrl()}${endpoint}`;\n\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n ...extraHeaders,\n };\n\n const requestConfig: RequestInit = {\n method,\n headers,\n signal: AbortSignal.timeout(this.config.timeout!)\n };\n\n if (body && method !== 'GET') {\n requestConfig.body = JSON.stringify(body);\n }\n\n try {\n const response = await fetch(url, requestConfig);\n const data = await response.json();\n \n return {\n success: response.ok,\n data: response.ok ? data as T : undefined,\n error: response.ok ? undefined : (data as any)?.message || 'Request failed',\n timestamp: new Date()\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Network error',\n timestamp: new Date()\n };\n }\n }\n\n private startBatchFlush(): void {\n this.flushTimer = setInterval(() => {\n this.flush().catch(console.error);\n }, this.config.flushInterval);\n }\n\n destroy(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n }\n this.flush().catch(console.error);\n if (typeof window !== 'undefined' && window.localStorage) {\n localStorage.removeItem('bandits_session_id');\n localStorage.removeItem('bandits_session_created_at');\n }\n }\n}","export type {\n SnapshotTreatment,\n SnapshotExperiment,\n CompanySnapshot,\n} from '@runbandit/shared';\nimport type { CompanySnapshot, SnapshotTreatment } from '@runbandit/shared';\nimport { defaultSessionStorage, getOrCreateSessionId } from './session.js';\n\nexport interface SyncAssignment {\n clientAssignmentId: string;\n experimentId: string;\n treatmentId: string;\n config: unknown;\n}\n\ninterface PendingReport {\n clientAssignmentId: string;\n experimentId: string;\n treatmentId: string;\n userId: string;\n sessionId: string;\n}\n\nexport interface SnapshotClientConfig {\n apiUrl: string;\n publishableKey: string;\n storage?: Storage;\n now?: () => number;\n sessionTtl?: number;\n inlineSnapshot?: CompanySnapshot;\n fetchImpl?: typeof fetch;\n}\n\nconst STORAGE_KEY = 'bandits_snapshot';\nconst REPORT_QUEUE_KEY = 'bandits_pending_reports';\nconst DECISIONS_KEY = 'bandits_client_assignments';\n\ninterface StoredDecision {\n clientAssignmentId: string;\n treatmentId: string;\n expiresAt: number;\n}\n\nexport function pickTreatment(\n treatments: SnapshotTreatment[],\n draw: number\n): SnapshotTreatment | null {\n if (treatments.length === 0) return null;\n let cumulative = 0;\n for (const treatment of treatments) {\n cumulative += treatment.pct;\n if (draw < cumulative) return treatment;\n }\n return treatments[treatments.length - 1];\n}\n\nconst DEFAULT_ASSIGNMENT_TTL_SECONDS = 86400;\n\nfunction randomId(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';\n let id = '';\n for (let i = 0; i < 24; i++) {\n id += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return id;\n}\n\nexport class SnapshotClient {\n private config: SnapshotClientConfig;\n private storage: Storage | null;\n private now: () => number;\n private fetchImpl: typeof fetch;\n private snapshot: CompanySnapshot | null = null;\n private queue: PendingReport[] = [];\n private sessionId: string;\n private inFlight: Promise<void> | null = null;\n\n constructor(config: SnapshotClientConfig) {\n this.config = config;\n this.now = config.now ?? (() => Date.now());\n this.fetchImpl = config.fetchImpl ?? ((...args) => fetch(...args));\n this.storage =\n config.storage ??\n (typeof window !== 'undefined' && window.localStorage ? window.localStorage : null);\n\n this.queue = this.readQueue();\n this.sessionId = getOrCreateSessionId({\n storage: this.storage ?? defaultSessionStorage(),\n now: this.now,\n ttlMs: config.sessionTtl,\n });\n\n if (config.inlineSnapshot) {\n this.snapshot = config.inlineSnapshot;\n this.persistSnapshot(config.inlineSnapshot);\n } else {\n this.snapshot = this.readSnapshot();\n }\n }\n\n getAssignmentSync(experimentId: string, userId: string): SyncAssignment | null {\n const snapshot = this.freshSnapshot();\n if (!snapshot) return null;\n\n const experiment = snapshot.experiments[experimentId];\n if (!experiment || experiment.mode !== 'snapshot') return null;\n\n const key = `${experimentId}:${userId}`;\n const decisions = this.readDecisions();\n const stored = decisions[key];\n if (stored && stored.expiresAt > this.now()) {\n const held = experiment.treatments.find((t) => t.id === stored.treatmentId);\n if (held) {\n return {\n clientAssignmentId: stored.clientAssignmentId,\n experimentId,\n treatmentId: held.id,\n config: held.config,\n };\n }\n }\n\n const treatment = pickTreatment(experiment.treatments, Math.random());\n if (!treatment) return null;\n\n const clientAssignmentId = randomId();\n const ttlSeconds = experiment.assignmentTtl ?? DEFAULT_ASSIGNMENT_TTL_SECONDS;\n decisions[key] = {\n clientAssignmentId,\n treatmentId: treatment.id,\n expiresAt: this.now() + ttlSeconds * 1000,\n };\n this.writeDecisions(decisions);\n\n this.queue.push({\n clientAssignmentId,\n experimentId,\n treatmentId: treatment.id,\n userId,\n sessionId: this.sessionId,\n });\n this.persistQueue();\n\n return {\n clientAssignmentId,\n experimentId,\n treatmentId: treatment.id,\n config: treatment.config,\n };\n }\n\n async refresh(): Promise<void> {\n const response = await this.fetchImpl(`${this.config.apiUrl}/snapshot`, {\n method: 'GET',\n headers: { 'x-api-key': this.config.publishableKey },\n });\n if (!response.ok) return;\n const snapshot = (await response.json()) as CompanySnapshot;\n this.snapshot = snapshot;\n this.persistSnapshot(snapshot);\n }\n\n async flush(): Promise<void> {\n if (this.inFlight) return this.inFlight;\n this.inFlight = this.doFlush().finally(() => {\n this.inFlight = null;\n });\n return this.inFlight;\n }\n\n private async doFlush(): Promise<void> {\n if (this.queue.length === 0) return;\n const reports = this.queue.slice(0, 1000);\n\n try {\n const response = await this.fetchImpl(`${this.config.apiUrl}/assignments/report`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-api-key': this.config.publishableKey,\n },\n body: JSON.stringify({ reports }),\n keepalive: true,\n });\n if (!response.ok) return;\n } catch {\n return;\n }\n\n this.queue = this.queue.slice(reports.length);\n this.persistQueue();\n }\n\n getSessionId(): string {\n return this.sessionId;\n }\n\n pendingReportCount(): number {\n return this.queue.length;\n }\n\n private freshSnapshot(): CompanySnapshot | null {\n if (!this.snapshot) return null;\n if (this.snapshot.ttl === 0) return null;\n const ageMs = this.now() - this.snapshot.generatedAt;\n if (ageMs > this.snapshot.ttl * 1000) return null;\n return this.snapshot;\n }\n\n private readSnapshot(): CompanySnapshot | null {\n if (!this.storage) return null;\n try {\n const raw = this.storage.getItem(STORAGE_KEY);\n return raw ? (JSON.parse(raw) as CompanySnapshot) : null;\n } catch {\n return null;\n }\n }\n\n private persistSnapshot(snapshot: CompanySnapshot): void {\n if (!this.storage) return;\n try {\n this.storage.setItem(STORAGE_KEY, JSON.stringify(snapshot));\n } catch {}\n }\n\n private readQueue(): PendingReport[] {\n if (!this.storage) return [];\n try {\n const raw = this.storage.getItem(REPORT_QUEUE_KEY);\n return raw ? (JSON.parse(raw) as PendingReport[]) : [];\n } catch {\n return [];\n }\n }\n\n private readDecisions(): Record<string, StoredDecision> {\n if (!this.storage) return {};\n try {\n const raw = this.storage.getItem(DECISIONS_KEY);\n if (!raw) return {};\n const parsed = JSON.parse(raw) as Record<string, StoredDecision>;\n const now = this.now();\n const live: Record<string, StoredDecision> = {};\n for (const [k, d] of Object.entries(parsed)) {\n if (d && d.expiresAt > now) live[k] = d;\n }\n return live;\n } catch {\n return {};\n }\n }\n\n private writeDecisions(decisions: Record<string, StoredDecision>): void {\n if (!this.storage) return;\n try {\n this.storage.setItem(DECISIONS_KEY, JSON.stringify(decisions));\n } catch {}\n }\n\n private persistQueue(): void {\n if (!this.storage) return;\n try {\n this.storage.setItem(REPORT_QUEUE_KEY, JSON.stringify(this.queue));\n } catch {}\n }\n}\n","import { EMAIL_TOKEN_PARAM } from '@runbandit/shared';\n\n\nconst UNTRACKABLE_SCHEME = /^(mailto:|tel:|sms:|javascript:|data:|#)/i;\n\nconst NOTRACK_ATTRIBUTE = 'data-bandit-notrack';\n\nexport interface RewriteOptions {\n linkTable: string[];\n tokens: string[];\n}\n\nexport interface RewriteResult {\n html: string;\n rewritten: number;\n untracked: string[];\n}\n\ninterface Anchor {\n valueStart: number;\n valueEnd: number;\n url: string;\n}\n\nfunction isTrackable(url: string): boolean {\n const trimmed = url.trim();\n if (!trimmed || UNTRACKABLE_SCHEME.test(trimmed)) return false;\n return /^https?:\\/\\//i.test(trimmed);\n}\n\nfunction appendToken(url: string, token: string): string {\n const hashIndex = url.indexOf('#');\n const fragment = hashIndex === -1 ? '' : url.slice(hashIndex);\n const base = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n const separator = base.includes('?') ? (base.includes('&amp;') ? '&amp;' : '&') : '?';\n\n return `${base}${separator}${EMAIL_TOKEN_PARAM}=${token}${fragment}`;\n}\n\nfunction scanAnchors(html: string): Anchor[] {\n const anchors: Anchor[] = [];\n let i = 0;\n\n while (i < html.length) {\n const lt = html.indexOf('<', i);\n if (lt === -1) break;\n\n if (html.startsWith('<!--', lt)) {\n if (html.startsWith('<!--[if', lt) || html.startsWith('<!--<![endif]', lt)) {\n const close = html.indexOf('>', lt);\n i = close === -1 ? html.length : close + 1;\n continue;\n }\n\n const end = html.indexOf('-->', lt);\n i = end === -1 ? html.length : end + 3;\n continue;\n }\n\n const rawText = /^<(script|style)\\b/i.exec(html.slice(lt, lt + 8));\n if (rawText) {\n const closing = new RegExp(`</${rawText[1]}\\\\s*>`, 'i');\n const rest = html.slice(lt);\n const match = closing.exec(rest);\n i = match ? lt + match.index + match[0].length : html.length;\n continue;\n }\n\n const isLinkElement =\n /^<a[\\s/>]/i.test(html.slice(lt, lt + 3)) ||\n /^<v:roundrect[\\s/>]/i.test(html.slice(lt, lt + 13));\n if (!isLinkElement) {\n i = lt + 1;\n continue;\n }\n\n const tagEnd = findTagEnd(html, lt);\n if (tagEnd === -1) break;\n\n const tag = html.slice(lt, tagEnd);\n if (!new RegExp(NOTRACK_ATTRIBUTE, 'i').test(tag)) {\n const href = findHref(html, lt, tagEnd);\n if (href && isTrackable(href.url)) anchors.push(href);\n }\n\n i = tagEnd;\n }\n\n return anchors;\n}\n\nfunction findTagEnd(html: string, start: number): number {\n let quote: string | null = null;\n\n for (let i = start + 1; i < html.length; i++) {\n const ch = html[i];\n if (quote) {\n if (ch === quote) quote = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch;\n continue;\n }\n if (ch === '>') return i + 1;\n }\n\n return -1;\n}\n\nfunction findHref(html: string, tagStart: number, tagEnd: number): Anchor | null {\n const tag = html.slice(tagStart, tagEnd);\n const match = /\\shref\\s*=\\s*(\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/i.exec(tag);\n if (!match) return null;\n\n const quoted = match[2] !== undefined || match[3] !== undefined;\n const value = match[2] ?? match[3] ?? match[4] ?? '';\n\n const valueStart = tagStart + match.index + match[0].indexOf(match[1]) + (quoted ? 1 : 0);\n\n return { valueStart, valueEnd: valueStart + value.length, url: value };\n}\n\nexport function extractEmailLinks(html: string): string[] {\n const seen = new Set<string>();\n const urls: string[] = [];\n\n for (const anchor of scanAnchors(html)) {\n if (seen.has(anchor.url)) continue;\n seen.add(anchor.url);\n urls.push(anchor.url);\n }\n\n return urls;\n}\n\nexport function rewriteEmailLinks(html: string, options: RewriteOptions): RewriteResult {\n const { linkTable, tokens } = options;\n\n const indexOfUrl = new Map<string, number>();\n linkTable.forEach((url, index) => {\n if (!indexOfUrl.has(url)) indexOfUrl.set(url, index);\n });\n\n const anchors = scanAnchors(html);\n const untracked: string[] = [];\n let rewritten = 0;\n\n let output = html;\n for (let i = anchors.length - 1; i >= 0; i--) {\n const anchor = anchors[i];\n const index = indexOfUrl.get(anchor.url);\n\n if (index === undefined || tokens[index] === undefined) {\n untracked.push(anchor.url);\n continue;\n }\n\n output =\n output.slice(0, anchor.valueStart) +\n appendToken(anchor.url, tokens[index]) +\n output.slice(anchor.valueEnd);\n rewritten++;\n }\n\n return { html: output, rewritten, untracked: untracked.reverse() };\n}\n","import { rewriteEmailLinks, extractEmailLinks } from './email-links.js';\n\n\nconst SLOT_PATTERN = /\\{\\{\\s*bandit\\.([a-zA-Z0-9_-]+)\\s*\\}\\}/g;\n\nexport interface RenderEmailOptions {\n slots: Record<string, string>;\n linkTable: string[];\n tokens: string[];\n}\n\nexport interface RenderedEmail {\n html: string;\n rewritten: number;\n untracked: string[];\n missingSlots: string[];\n}\n\nexport function renderEmail(templateHtml: string, options: RenderEmailOptions): RenderedEmail {\n const missingSlots: string[] = [];\n\n const substituted = templateHtml.replace(SLOT_PATTERN, (match, name: string) => {\n const value = options.slots[name];\n if (value === undefined) {\n missingSlots.push(name);\n return match;\n }\n return value;\n });\n\n const { html, rewritten, untracked } = rewriteEmailLinks(substituted, {\n linkTable: options.linkTable,\n tokens: options.tokens,\n });\n\n return { html, rewritten, untracked, missingSlots };\n}\n\nexport function extractSlotNames(templateHtml: string): string[] {\n const names = new Set<string>();\n for (const match of templateHtml.matchAll(SLOT_PATTERN)) names.add(match[1]);\n return [...names];\n}\n\nexport { extractEmailLinks, rewriteEmailLinks };\nexport type { RewriteOptions, RewriteResult } from './email-links.js';\n","import {\n mintEmailToken,\n hashRecipient,\n impressionManifest,\n pickArm,\n uniformFor,\n type EmailPlan,\n} from '@runbandit/shared';\nimport { renderEmail, type RenderedEmail } from './email.js';\n\n\nexport interface PlanRenderResult extends RenderedEmail {\n combination: number[];\n tokens: string[];\n}\n\nexport async function renderFromPlan(\n plan: EmailPlan,\n recipientId: string,\n templateHtml: string,\n companySecret: string\n): Promise<PlanRenderResult> {\n const slots: Record<string, string> = {};\n const combination: number[] = [];\n\n for (let i = 0; i < plan.slots.length; i++) {\n const slot = plan.slots[i];\n const chosen = pickArm(slot.probabilities, await uniformFor(plan.salt, recipientId, i));\n const index = chosen === -1 ? 0 : chosen;\n\n combination.push(slot.arms[index]?.armIndex ?? 0);\n\n const config = slot.configs[index];\n slots[slot.name] =\n typeof config === 'string'\n ? config\n : ((config as { content?: string; text?: string } | undefined)?.content ??\n (config as { content?: string; text?: string } | undefined)?.text ??\n '');\n }\n\n const recipientHash = await hashRecipient(recipientId, companySecret);\n\n const tokens = await Promise.all(\n plan.linkTable.map((_, linkIndex) =>\n mintEmailToken(\n { c: plan.campaignId, r: recipientHash, k: combination, l: linkIndex },\n plan.signingKey\n )\n )\n );\n\n const rendered = renderEmail(templateHtml, {\n slots,\n linkTable: plan.linkTable,\n tokens,\n });\n\n return { ...rendered, combination, tokens };\n}\n\nexport function shouldRefreshPlan(\n plan: EmailPlan,\n recipientsRenderedSincePlan: number,\n now: number = Date.now()\n): boolean {\n return recipientsRenderedSincePlan >= plan.refreshEvery || now >= plan.expiresAt;\n}\n\nexport async function reportPlanImpressions(\n plan: EmailPlan,\n recipientIds: string[],\n options: { apiUrl: string; apiKey: string; batchKey?: string }\n): Promise<{ recorded: boolean; alreadyRecorded: boolean }> {\n const counts = await impressionManifest(plan, recipientIds);\n if (counts.length === 0) return { recorded: false, alreadyRecorded: false };\n\n const response = await fetch(`${options.apiUrl}/api/email/impressions`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'x-api-key': options.apiKey },\n body: JSON.stringify({\n campaignId: plan.campaignId,\n batchKey: options.batchKey ?? (await batchKeyFor(plan.campaignId, recipientIds)),\n counts,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to report impressions: ${response.status}`);\n }\n\n const body = (await response.json()) as { data?: { recorded: boolean; alreadyRecorded: boolean } };\n return body.data ?? { recorded: false, alreadyRecorded: false };\n}\n\nasync function batchKeyFor(campaignId: string, recipientIds: string[]): Promise<string> {\n const material = `${campaignId}|${[...recipientIds].sort().join(',')}`;\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(material));\n return Array.from(new Uint8Array(digest).slice(0, 16))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n"],"mappings":"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","names":["ExperimentStatus","BanditAlgorithm","Posterior","ForgettingMode","ContentType","FunnelStatus"]}
1
+ {"version":3,"sources":["../../shared/src/types.ts","../../shared/src/utils.ts","../../shared/src/email-token.ts","../../shared/src/email-plan.ts","../../shared/src/email-blocks.ts","../../shared/src/storefront-content.ts","../../shared/src/conversion-path.ts","../../shared/src/site.ts","../../shared/src/site-platforms.ts","../src/session.ts","../src/client.ts","../src/snapshot-cache.ts","../src/email-links.ts","../src/email.ts","../src/email-plan.ts"],"sourcesContent":["import type { Placement } from './storefront-content.js';\nimport type { FunnelGoal, SiteTarget } from './site.js';\n\nexport interface Company {\n id: string;\n name: string;\n subdomain?: string;\n domain?: string;\n plan: string;\n apiKey: string;\n status: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface User {\n id: string;\n email: string;\n name: string;\n image?: string;\n role: string;\n companyId: string | null;\n emailVerified?: boolean;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface Session {\n id: string;\n userId: string;\n token: string;\n expiresAt: Date;\n ipAddress?: string | null;\n userAgent?: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface Account {\n id: string;\n userId: string;\n provider: string;\n providerAccountId: string;\n accessToken?: string;\n refreshToken?: string;\n accessTokenExpiresAt?: Date;\n refreshTokenExpiresAt?: Date;\n scope?: string;\n idToken?: string;\n sessionState?: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface Experiment {\n id: string;\n name: string;\n description?: string;\n algorithm: BanditAlgorithm;\n forgetting?: ForgettingConfig;\n status: ExperimentStatus;\n contentType: ContentType;\n treatments: Treatment[];\n context?: Record<string, any>;\n bundleId?: string;\n image?: string;\n emoji?: string;\n assignmentTtl: number;\n companyId: string;\n createdAt: Date;\n updatedAt: Date;\n endAt?: Date;\n placement?: Placement;\n}\n\nexport interface Treatment {\n id: string;\n experimentId: string;\n name: string;\n description?: string;\n config: VariantConfig;\n weight?: number;\n companyId: string;\n createdAt: Date;\n}\n\nexport interface AlgorithmResult {\n treatmentId: string;\n confidence: number;\n probabilityBest?: number;\n}\n\nexport interface BanditResult {\n assignmentId: string;\n treatmentId: string;\n confidence: number;\n probabilityBest?: number;\n metadata?: Record<string, any>;\n}\n\nexport interface ContextualFeatures {\n [key: string]: string | number | boolean;\n}\n\nexport enum ExperimentStatus {\n DRAFT = 'draft',\n ACTIVE = 'active',\n PAUSED = 'paused',\n COMPLETED = 'completed',\n ARCHIVED = 'archived'\n}\n\n\nexport interface TreatmentMetadata {\n strategy?: string;\n tone?: string;\n ctaStyle?: string;\n emotionalHook?: string;\n reasoning?: string;\n tags?: string[];\n generated?: boolean;\n generatedAt?: string;\n}\n\nexport interface VariantConfig {\n content: string;\n metadata?: TreatmentMetadata & Record<string, any>;\n}\n\nexport enum BanditAlgorithm {\n THOMPSON_SAMPLING = 'thompson_sampling',\n CONTEXTUAL_LINEAR = 'contextual_linear'\n}\n\nexport enum Posterior {\n BETA = 'beta',\n NORMAL_GAMMA = 'normal_gamma'\n}\n\nexport function normalizePosterior(posterior: string | null | undefined): Posterior {\n return posterior === Posterior.NORMAL_GAMMA ? Posterior.NORMAL_GAMMA : Posterior.BETA;\n}\n\nexport enum ForgettingMode {\n NONE = 'none',\n DISCOUNTED = 'discounted',\n SLIDING_WINDOW = 'sliding_window'\n}\n\nexport interface ForgettingConfig {\n mode: ForgettingMode;\n halfLifeMs?: number;\n windowMs?: number;\n}\n\nexport const DEFAULT_HALF_LIFE_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport const DEFAULT_WINDOW_MS = 14 * 24 * 60 * 60 * 1000;\n\nexport const STATIONARY: ForgettingConfig = { mode: ForgettingMode.NONE };\n\nexport function resolveForgetting(config?: Partial<ForgettingConfig> | null): ForgettingConfig {\n switch (config?.mode) {\n case ForgettingMode.DISCOUNTED:\n return {\n mode: ForgettingMode.DISCOUNTED,\n halfLifeMs: config.halfLifeMs && config.halfLifeMs > 0\n ? config.halfLifeMs\n : DEFAULT_HALF_LIFE_MS\n };\n case ForgettingMode.SLIDING_WINDOW:\n return {\n mode: ForgettingMode.SLIDING_WINDOW,\n windowMs: config.windowMs && config.windowMs > 0\n ? config.windowMs\n : DEFAULT_WINDOW_MS\n };\n default:\n return STATIONARY;\n }\n}\n\nconst LEGACY_ALGORITHM_ALIASES: Record<string, BanditAlgorithm> = {\n epsilon_greedy: BanditAlgorithm.THOMPSON_SAMPLING,\n ucb1: BanditAlgorithm.THOMPSON_SAMPLING,\n contextual_neural: BanditAlgorithm.CONTEXTUAL_LINEAR\n};\n\nexport function normalizeAlgorithm(algorithm: string | null | undefined): BanditAlgorithm {\n if (algorithm === BanditAlgorithm.THOMPSON_SAMPLING) return BanditAlgorithm.THOMPSON_SAMPLING;\n if (algorithm === BanditAlgorithm.CONTEXTUAL_LINEAR) return BanditAlgorithm.CONTEXTUAL_LINEAR;\n return LEGACY_ALGORITHM_ALIASES[algorithm ?? ''] ?? BanditAlgorithm.THOMPSON_SAMPLING;\n}\n\nexport enum ContentType {\n PLAIN_TEXT = 'plain_text',\n MARKDOWN = 'markdown',\n HTML = 'html',\n CSS = 'css',\n JAVASCRIPT = 'javascript',\n JSON = 'json',\n IMAGE = 'image',\n VIDEO = 'video',\n AUDIO = 'audio',\n DOCUMENT = 'document',\n}\n\nexport function getContentTypeCategory(contentType: ContentType): 'text' | 'code' | 'file' {\n switch (contentType) {\n case ContentType.PLAIN_TEXT:\n case ContentType.MARKDOWN:\n return 'text';\n case ContentType.HTML:\n case ContentType.CSS:\n case ContentType.JAVASCRIPT:\n case ContentType.JSON:\n return 'code';\n case ContentType.IMAGE:\n case ContentType.VIDEO:\n case ContentType.AUDIO:\n case ContentType.DOCUMENT:\n return 'file';\n }\n}\n\nexport interface BanditConfig {\n algorithm: BanditAlgorithm;\n parameters: Record<string, any>;\n}\n\nexport interface ExperimentStats {\n totalAssignments: number;\n totalConversions: number;\n conversionRate: number;\n treatmentStats: Record<string, TreatmentStats>;\n}\n\nexport interface TreatmentStats {\n assignments: number;\n conversions: number;\n conversionRate: number;\n confidence: number;\n lift?: number;\n}\n\nexport interface APIResponse<T = any> {\n success: boolean;\n data?: T;\n error?: string;\n timestamp: Date;\n}\n\nexport interface CreateExperimentRequest {\n name: string;\n description?: string;\n algorithm: BanditAlgorithm;\n contentType: ContentType;\n algorithmConfig?: Record<string, any>;\n treatments?: Omit<Treatment, 'id' | 'experimentId' | 'createdAt'>[];\n}\n\nexport interface AssignmentRequest {\n experimentId: string;\n userId: string;\n context?: ContextualFeatures;\n sessionId?: string;\n pageUrl?: string;\n}\n\n\nexport interface TrackNamedEventRequest {\n name: string;\n category?: string;\n value?: number;\n metadata?: Record<string, any>;\n sessionId: string;\n userId?: string;\n assignmentId?: string;\n}\n\n\nexport enum FunnelStatus {\n DRAFT = 'draft',\n ACTIVE = 'active',\n PAUSED = 'paused',\n COMPLETED = 'completed',\n}\n\nexport interface Funnel {\n id: string;\n name: string;\n description?: string;\n status: FunnelStatus;\n inactivityTimeoutMs?: number;\n revisionWindowMs?: number;\n companyId: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface FunnelRewardRequest {\n funnelId: string;\n userId: string;\n value: number;\n}\n\n\nexport interface ApiKey {\n id: string;\n name: string;\n keyPrefix: string;\n status: 'active' | 'revoked';\n createdBy: string;\n createdAt: Date;\n lastUsedAt?: Date;\n revokedAt?: Date;\n revokedBy?: string;\n}\n\nexport interface CreateApiKeyRequest {\n name: string;\n}\n\nexport interface CreateApiKeyResponse {\n id: string;\n key: string;\n name: string;\n}\n\n\nexport type IdentifyMergeReason =\n | 'user_signup'\n | 'email_verification'\n | 'social_login'\n | 'account_recovery'\n | 'manual';\n\nexport interface IdentifyUserRequest {\n anonymousId: string;\n identifiedUserId: string;\n mergeReason?: IdentifyMergeReason;\n}\n\nexport interface IdentifyUserResponse {\n anonymousId: string;\n identifiedUserId: string;\n alreadyMapped: boolean;\n}\n\nexport interface ResolvedIdentity {\n canonicalId: string;\n anonymousIds: string[];\n isIdentified: boolean;\n}\n\n\nexport type BundleMode = 'factored' | 'joint';\n\nexport interface Bundle {\n id: string;\n name: string;\n description?: string;\n status: ExperimentStatus;\n mode: BundleMode;\n funnelId?: string;\n assignmentTtl: number;\n conversionWindowMs: number;\n inactivityTimeoutMs?: number;\n revisionWindowMs?: number;\n companyId: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface BundleAssignmentRequest {\n bundleId: string;\n userId: string;\n context?: ContextualFeatures;\n sessionId?: string;\n pageUrl?: string;\n}\n\nexport interface BundleSlotAssignment {\n assignmentId: string;\n experimentId: string;\n treatmentId: string;\n config: VariantConfig | Record<string, unknown>;\n}\n\nexport interface BundleAssignmentResponse {\n bundleAssignmentId: string;\n slots: Record<string, BundleSlotAssignment>;\n}\n\nexport interface SnapshotTreatment {\n id: string;\n name?: string;\n config: unknown;\n pct: number;\n}\n\nexport interface SnapshotExperiment {\n mode: 'snapshot' | 'network';\n assignmentTtl: number;\n treatments: SnapshotTreatment[];\n target?: SiteTarget;\n funnelId?: string;\n}\n\nexport interface CompanySnapshot {\n ttl: number;\n generatedAt: number;\n experiments: Record<string, SnapshotExperiment>;\n goals?: Record<string, FunnelGoal>;\n}\n","import { Treatment } from './types.js';\n\nexport function generateId(): string {\n return Math.random().toString(36).substring(2) + Date.now().toString(36);\n}\n\nexport function validateTreatmentWeights(treatments: Treatment[]): boolean {\n const totalWeight = treatments.reduce((sum, treatment) => sum + (treatment.weight || 1), 0);\n return totalWeight > 0;\n}\n\nexport function normalizeTreatmentWeights(treatments: Treatment[]): Treatment[] {\n const totalWeight = treatments.reduce((sum, treatment) => sum + (treatment.weight || 1), 0);\n \n return treatments.map(treatment => ({\n ...treatment,\n weight: (treatment.weight || 1) / totalWeight\n }));\n}\n\nexport function calculateConfidenceInterval(\n successes: number,\n trials: number,\n confidence: number = 0.95\n): { lower: number; upper: number } {\n if (trials === 0) return { lower: 0, upper: 0 };\n \n const z = confidence === 0.95 ? 1.96 : confidence === 0.99 ? 2.576 : 1.645;\n const p = successes / trials;\n const margin = z * Math.sqrt((p * (1 - p)) / trials);\n \n return {\n lower: Math.max(0, p - margin),\n upper: Math.min(1, p + margin)\n };\n}\n\nexport function isValidEmail(email: string): boolean {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n return emailRegex.test(email);\n}\n\nexport function sanitizeMetadata(metadata: Record<string, any>): Record<string, any> {\n const sanitized: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(metadata)) {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n sanitized[key] = value;\n } else if (value !== null && value !== undefined) {\n sanitized[key] = String(value);\n }\n }\n\n return sanitized;\n}\n\nexport function generateSubdomain(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .substring(0, 63);\n}\n\nexport function isValidSubdomain(subdomain: string): boolean {\n if (!subdomain || subdomain.length === 0 || subdomain.length > 63) {\n return false;\n }\n\n const subdomainRegex = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;\n\n const reserved = [\n 'www', 'api', 'app', 'admin', 'dashboard', 'mail', 'ftp', 'localhost',\n 'staging', 'dev', 'test', 'demo', 'blog', 'docs', 'status', 'help',\n 'support', 'cdn', 'static', 'assets', 'secure', 'payment', 'pay'\n ];\n\n return subdomainRegex.test(subdomain) && !reserved.includes(subdomain);\n}\n\nexport function extractSubdomain(hostname: string, baseDomain: string): string | null {\n if (!hostname || !baseDomain) {\n return null;\n }\n\n const cleanHostname = hostname.split(':')[0];\n\n if (!cleanHostname.endsWith(baseDomain)) {\n return null;\n }\n\n const subdomain = cleanHostname.slice(0, -(baseDomain.length + 1));\n\n if (!subdomain || subdomain === 'www') {\n return null;\n }\n\n return subdomain;\n}\n\nexport function buildSubdomainUrl(\n subdomain: string,\n baseDomain: string,\n path: string = '',\n protocol: string = 'https'\n): string {\n const url = `${protocol}://${subdomain}.${baseDomain}`;\n return path ? `${url}${path.startsWith('/') ? path : '/' + path}` : url;\n}","\nexport const EMAIL_TOKEN_PARAM = 'bt';\n\nexport interface SigningKey {\n version: number;\n secret: string;\n}\n\nexport interface EmailTokenPayload {\n c: string;\n r: string;\n k: number[];\n l: number;\n v?: number;\n}\n\nexport type VerifiedToken =\n | { valid: true; payload: Required<EmailTokenPayload> }\n | { valid: false; reason: 'malformed' | 'unknown_key' | 'bad_signature' };\n\n\nconst encoder = new TextEncoder();\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction base64UrlDecode(value: string): Uint8Array {\n const padded = value.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4));\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\nasync function hmac(secret: string, message: string): Promise<Uint8Array> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n return new Uint8Array(await crypto.subtle.sign('HMAC', key, encoder.encode(message)));\n}\n\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\n\nexport async function hashRecipient(recipientId: string, companySecret: string): Promise<string> {\n const digest = await hmac(companySecret, `recipient:${recipientId}`);\n return base64UrlEncode(digest.slice(0, 16));\n}\n\nexport async function mintEmailToken(\n payload: EmailTokenPayload,\n key: SigningKey\n): Promise<string> {\n const body = base64UrlEncode(\n encoder.encode(\n JSON.stringify({\n c: payload.c,\n r: payload.r,\n k: payload.k,\n l: payload.l,\n v: key.version,\n })\n )\n );\n\n const signature = base64UrlEncode(await hmac(key.secret, body));\n return `${body}.${signature}`;\n}\n\nexport async function verifyEmailToken(\n token: string,\n keys: SigningKey[]\n): Promise<VerifiedToken> {\n const parts = token.split('.');\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n return { valid: false, reason: 'malformed' };\n }\n const [body, signature] = parts;\n\n let payload: EmailTokenPayload;\n try {\n payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(body)));\n } catch {\n return { valid: false, reason: 'malformed' };\n }\n\n if (\n !payload ||\n typeof payload.c !== 'string' ||\n typeof payload.r !== 'string' ||\n !Array.isArray(payload.k) ||\n !payload.k.every((arm) => Number.isInteger(arm) && arm >= 0) ||\n !Number.isInteger(payload.l) ||\n (payload.l as number) < 0 ||\n !Number.isInteger(payload.v)\n ) {\n return { valid: false, reason: 'malformed' };\n }\n\n const key = keys.find((candidate) => candidate.version === payload.v);\n if (!key) return { valid: false, reason: 'unknown_key' };\n\n const expected = base64UrlEncode(await hmac(key.secret, body));\n if (!timingSafeEqual(expected, signature)) {\n return { valid: false, reason: 'bad_signature' };\n }\n\n return { valid: true, payload: payload as Required<EmailTokenPayload> };\n}\n\nexport function appendEmailToken(url: string, token: string): string {\n const hashIndex = url.indexOf('#');\n const fragment = hashIndex === -1 ? '' : url.slice(hashIndex);\n const base = hashIndex === -1 ? url : url.slice(0, hashIndex);\n const separator = base.includes('?') ? '&' : '?';\n\n return `${base}${separator}${EMAIL_TOKEN_PARAM}=${encodeURIComponent(token)}${fragment}`;\n}\n","\nexport interface PlanArm {\n treatmentId: string;\n armIndex: number;\n}\n\nexport interface PlanSlot {\n experimentId: string;\n name: string;\n arms: PlanArm[];\n probabilities: number[];\n configs: unknown[];\n}\n\nexport interface EmailPlan {\n campaignId: string;\n slots: PlanSlot[];\n linkTable: string[];\n salt: string;\n signingKey: { version: number; secret: string };\n refreshEvery: number;\n expiresAt: number;\n}\n\nexport async function uniformFor(\n salt: string,\n recipientId: string,\n slotIndex: number\n): Promise<number> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(salt),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n const mac = new Uint8Array(\n await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(`${recipientId}:${slotIndex}`))\n );\n\n let value = 0;\n for (let i = 0; i < 6; i++) value = value * 256 + mac[i];\n return value / 2 ** 48;\n}\n\nexport function pickArm(probabilities: number[], uniform: number): number {\n if (probabilities.length === 0) return -1;\n\n const total = probabilities.reduce((sum, p) => sum + Math.max(p, 0), 0);\n if (total <= 0) {\n return Math.min(probabilities.length - 1, Math.floor(uniform * probabilities.length));\n }\n\n const target = uniform * total;\n let cumulative = 0;\n for (let i = 0; i < probabilities.length; i++) {\n cumulative += Math.max(probabilities[i], 0);\n if (target < cumulative) return i;\n }\n\n return probabilities.length - 1;\n}\n\nexport async function sampleCombination(\n plan: EmailPlan,\n recipientId: string\n): Promise<number[]> {\n const combination: number[] = [];\n\n for (let i = 0; i < plan.slots.length; i++) {\n const slot = plan.slots[i];\n const uniform = await uniformFor(plan.salt, recipientId, i);\n const chosen = pickArm(slot.probabilities, uniform);\n combination.push(chosen === -1 ? 0 : slot.arms[chosen]?.armIndex ?? 0);\n }\n\n return combination;\n}\n\nexport async function impressionManifest(\n plan: EmailPlan,\n recipientIds: string[]\n): Promise<Array<{ experimentId: string; treatmentId: string; count: number }>> {\n const counts = new Map<string, { experimentId: string; treatmentId: string; count: number }>();\n\n for (const recipientId of recipientIds) {\n for (let i = 0; i < plan.slots.length; i++) {\n const slot = plan.slots[i];\n const uniform = await uniformFor(plan.salt, recipientId, i);\n const chosen = pickArm(slot.probabilities, uniform);\n const arm = slot.arms[chosen === -1 ? 0 : chosen];\n if (!arm) continue;\n\n const key = `${slot.experimentId}:${arm.treatmentId}`;\n const entry = counts.get(key);\n if (entry) entry.count++;\n else\n counts.set(key, {\n experimentId: slot.experimentId,\n treatmentId: arm.treatmentId,\n count: 1,\n });\n }\n }\n\n return Array.from(counts.values());\n}\n","\nexport interface SlotBinding {\n $slot: string;\n}\n\nexport type Bound<T> = T | SlotBinding;\n\nexport function isSlotBinding(value: unknown): value is SlotBinding {\n return typeof value === 'object' && value !== null && typeof (value as SlotBinding).$slot === 'string';\n}\n\nexport interface HeadingBlock {\n type: 'heading';\n text: Bound<string>;\n level?: 1 | 2 | 3;\n align?: 'left' | 'center' | 'right';\n color?: Bound<string>;\n}\n\nexport interface TextBlock {\n type: 'text';\n text: Bound<string>;\n align?: 'left' | 'center' | 'right';\n color?: Bound<string>;\n}\n\nexport interface ButtonBlock {\n type: 'button';\n text: Bound<string>;\n href: Bound<string>;\n backgroundColor?: Bound<string>;\n textColor?: Bound<string>;\n align?: 'left' | 'center' | 'right';\n}\n\nexport interface ImageBlock {\n type: 'image';\n src: Bound<string>;\n alt: Bound<string>;\n href?: Bound<string>;\n width?: number;\n}\n\nexport interface DividerBlock {\n type: 'divider';\n color?: string;\n}\n\nexport interface SpacerBlock {\n type: 'spacer';\n height?: number;\n}\n\nexport type EmailBlock =\n | HeadingBlock\n | TextBlock\n | ButtonBlock\n | ImageBlock\n | DividerBlock\n | SpacerBlock;\n\nexport interface EmailDocument {\n subject: Bound<string>;\n preheader?: Bound<string>;\n blocks: EmailBlock[];\n backgroundColor?: string;\n contentWidth?: number;\n}\n\nconst CONTENT_WIDTH = 600;\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\nfunction resolve(value: Bound<string> | undefined, slots: Record<string, string>, missing: string[]): string {\n if (value === undefined) return '';\n if (!isSlotBinding(value)) return value;\n\n const resolved = slots[value.$slot];\n if (resolved === undefined) {\n missing.push(value.$slot);\n return '';\n }\n return resolved;\n}\n\nexport function extractDocumentSlots(document: EmailDocument): string[] {\n const names: string[] = [];\n const seen = new Set<string>();\n\n const visit = (value: unknown) => {\n if (isSlotBinding(value)) {\n if (!seen.has(value.$slot)) {\n seen.add(value.$slot);\n names.push(value.$slot);\n }\n return;\n }\n if (Array.isArray(value)) value.forEach(visit);\n else if (typeof value === 'object' && value !== null) Object.values(value).forEach(visit);\n };\n\n visit(document.subject);\n visit(document.preheader);\n document.blocks.forEach(visit);\n\n return names;\n}\n\nfunction renderBlock(block: EmailBlock, slots: Record<string, string>, missing: string[]): string {\n const align = (block as { align?: string }).align ?? 'left';\n\n switch (block.type) {\n case 'heading': {\n const size = block.level === 3 ? 18 : block.level === 2 ? 22 : 28;\n const color = escapeHtml(resolve(block.color, slots, missing) || '#111111');\n return `<tr><td style=\"padding:12px 24px;text-align:${align};font-family:Arial,Helvetica,sans-serif;font-size:${size}px;line-height:1.3;font-weight:bold;color:${color}\">${escapeHtml(\n resolve(block.text, slots, missing)\n )}</td></tr>`;\n }\n\n case 'text': {\n const color = escapeHtml(resolve(block.color, slots, missing) || '#333333');\n return `<tr><td style=\"padding:8px 24px;text-align:${align};font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:${color}\">${escapeHtml(\n resolve(block.text, slots, missing)\n )}</td></tr>`;\n }\n\n case 'button': {\n const href = escapeHtml(resolve(block.href, slots, missing));\n const label = escapeHtml(resolve(block.text, slots, missing));\n const bg = escapeHtml(resolve(block.backgroundColor, slots, missing) || '#1a73e8');\n const fg = escapeHtml(resolve(block.textColor, slots, missing) || '#ffffff');\n\n return `<tr><td style=\"padding:16px 24px;text-align:${align}\">\n<!--[if mso]><v:roundrect xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" href=\"${href}\" style=\"height:44px;v-text-anchor:middle;width:220px;\" arcsize=\"12%\" strokecolor=\"${bg}\" fillcolor=\"${bg}\"><w:anchorlock/><center style=\"color:${fg};font-family:Arial,sans-serif;font-size:16px;font-weight:bold;\">${label}</center></v:roundrect><![endif]-->\n<!--[if !mso]><!--><a href=\"${href}\" style=\"display:inline-block;padding:13px 28px;background-color:${bg};color:${fg};font-family:Arial,Helvetica,sans-serif;font-size:16px;font-weight:bold;text-decoration:none;border-radius:6px\">${label}</a><!--<![endif]-->\n</td></tr>`;\n }\n\n case 'image': {\n const src = escapeHtml(resolve(block.src, slots, missing));\n const alt = escapeHtml(resolve(block.alt, slots, missing));\n const width = block.width ?? CONTENT_WIDTH;\n const img = `<img src=\"${src}\" alt=\"${alt}\" width=\"${width}\" style=\"display:block;border:0;outline:none;text-decoration:none;max-width:100%;height:auto\" />`;\n const href = block.href ? escapeHtml(resolve(block.href, slots, missing)) : '';\n\n return `<tr><td style=\"padding:0;text-align:center\">${\n href ? `<a href=\"${href}\">${img}</a>` : img\n }</td></tr>`;\n }\n\n case 'divider':\n return `<tr><td style=\"padding:12px 24px\"><table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr><td style=\"border-top:1px solid ${escapeHtml(\n block.color ?? '#e5e5e5'\n )};font-size:0;line-height:0\">&nbsp;</td></tr></table></td></tr>`;\n\n case 'spacer':\n return `<tr><td style=\"height:${block.height ?? 24}px;font-size:0;line-height:0\">&nbsp;</td></tr>`;\n\n default:\n return '';\n }\n}\n\nexport interface RenderedDocument {\n subject: string;\n html: string;\n missingSlots: string[];\n}\n\nexport function renderEmailDocument(\n document: EmailDocument,\n slots: Record<string, string>\n): RenderedDocument {\n const missingSlots: string[] = [];\n const width = document.contentWidth ?? CONTENT_WIDTH;\n const background = escapeHtml(document.backgroundColor ?? '#f4f4f5');\n\n const subject = resolve(document.subject, slots, missingSlots);\n const preheader = resolve(document.preheader, slots, missingSlots);\n\n const body = document.blocks.map((block) => renderBlock(block, slots, missingSlots)).join('\\n');\n\n const html = `<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<meta name=\"x-apple-disable-message-reformatting\" />\n<title>${escapeHtml(subject)}</title>\n<!--[if mso]><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->\n</head>\n<body style=\"margin:0;padding:0;background-color:${background}\">\n<div style=\"display:none;font-size:0;line-height:0;max-height:0;overflow:hidden;mso-hide:all\">${escapeHtml(\n preheader\n )}</div>\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background-color:${background}\">\n<tr><td align=\"center\" style=\"padding:24px 12px\">\n<table role=\"presentation\" width=\"${width}\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"width:${width}px;max-width:100%;background-color:#ffffff;border-radius:8px\">\n${body}\n</table>\n</td></tr>\n</table>\n</body>\n</html>`;\n\n return { subject, html, missingSlots };\n}\n","export const STOREFRONT_KINDS = ['text', 'image', 'ordering', 'offer'] as const;\nexport type StorefrontKind = (typeof STOREFRONT_KINDS)[number];\n\nexport const PLACEMENTS_BY_KIND = {\n text: ['hero_headline', 'hero_subhead', 'cta_primary', 'banner_text', 'badge_text'],\n image: ['hero_image', 'banner_image', 'feature_image'],\n ordering: ['product_list_primary', 'product_list_secondary'],\n offer: ['offer_banner'],\n} as const satisfies Record<StorefrontKind, readonly string[]>;\n\nexport type Placement = (typeof PLACEMENTS_BY_KIND)[StorefrontKind][number];\n\nexport const PLACEMENTS: readonly Placement[] = STOREFRONT_KINDS.flatMap(\n (kind) => PLACEMENTS_BY_KIND[kind]\n);\n\nexport function isPlacement(value: unknown): value is Placement {\n return typeof value === 'string' && (PLACEMENTS as readonly string[]).includes(value);\n}\n\nexport function kindOfPlacement(placement: Placement): StorefrontKind {\n const found = STOREFRONT_KINDS.find((kind) =>\n (PLACEMENTS_BY_KIND[kind] as readonly string[]).includes(placement)\n );\n if (!found) throw new Error(`Unknown placement: ${placement}`);\n return found;\n}\n\nexport const CONTENT_TYPE_BY_KIND = {\n text: { contentType: 'TEXT', contentSubtype: 'plain_text' },\n image: { contentType: 'FILE', contentSubtype: 'image' },\n ordering: { contentType: 'CODE', contentSubtype: 'json' },\n offer: { contentType: 'CODE', contentSubtype: 'json' },\n} as const satisfies Record<StorefrontKind, { contentType: string; contentSubtype: string }>;\n\nexport const MIN_ORDERING_ITEMS = 2;\nexport const MAX_ORDERING_ITEMS = 12;\n\nexport interface ImageContent {\n url: string;\n alt: string;\n width: number;\n height: number;\n sourceId: string;\n}\n\nexport interface OfferContent {\n discountId: string;\n code: string;\n headline: string;\n subtext: string;\n}\n\nexport type TreatmentContent =\n | { kind: 'text'; text: string }\n | { kind: 'image'; image: ImageContent }\n | { kind: 'ordering'; productIds: string[] }\n | { kind: 'offer'; offer: OfferContent | null };\n\nexport function isShopifyCdnUrl(value: string): boolean {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n return url.protocol === 'https:' && url.hostname === 'cdn.shopify.com';\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction isPositiveInt(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value > 0;\n}\n\nfunction parseImage(raw: unknown): ImageContent | null {\n if (!isRecord(raw)) return null;\n const { url, alt, width, height, sourceId } = raw;\n if (typeof url !== 'string' || !isShopifyCdnUrl(url)) return null;\n if (typeof alt !== 'string') return null;\n if (!isPositiveInt(width) || !isPositiveInt(height)) return null;\n if (!isNonEmptyString(sourceId)) return null;\n return { url, alt, width, height, sourceId };\n}\n\nfunction parseOrdering(raw: unknown): string[] | null {\n if (!Array.isArray(raw)) return null;\n if (raw.length < MIN_ORDERING_ITEMS || raw.length > MAX_ORDERING_ITEMS) return null;\n if (!raw.every((id) => typeof id === 'string' && /^\\d+$/.test(id))) return null;\n if (new Set(raw).size !== raw.length) return null;\n return raw as string[];\n}\n\nfunction parseOffer(raw: unknown): OfferContent | null | undefined {\n if (raw === null) return null;\n if (!isRecord(raw)) return undefined;\n const { discountId, code, headline, subtext } = raw;\n if (!isNonEmptyString(discountId) || !isNonEmptyString(code)) return undefined;\n if (typeof headline !== 'string' || typeof subtext !== 'string') return undefined;\n return { discountId, code, headline, subtext };\n}\n\nexport function parseTreatmentContent(\n kind: StorefrontKind,\n config: unknown\n): TreatmentContent | null {\n if (!isRecord(config)) return null;\n const content = config.content;\n switch (kind) {\n case 'text':\n return isNonEmptyString(content) ? { kind, text: content } : null;\n case 'image': {\n const image = parseImage(content);\n return image ? { kind, image } : null;\n }\n case 'ordering': {\n const productIds = parseOrdering(content);\n return productIds ? { kind, productIds } : null;\n }\n case 'offer': {\n const offer = parseOffer(content);\n return offer === undefined ? null : { kind, offer };\n }\n }\n}\n\nexport function serializeTreatmentConfig(content: TreatmentContent): { content: unknown } {\n switch (content.kind) {\n case 'text':\n return { content: content.text };\n case 'image':\n return { content: content.image };\n case 'ordering':\n return { content: content.productIds };\n case 'offer':\n return { content: content.offer };\n }\n}\n","const SCHEME = /^[a-z][a-z0-9+.-]*:\\/\\//i;\n\nconst isWildcard = (segment: string) => segment === '*' || segment === '**';\n\nfunction segmentsOf(path: string): string[] {\n return path.split('/').filter(Boolean);\n}\n\nexport function normalizeConversionPath(input: string | undefined): string | undefined {\n const trimmed = input?.trim();\n if (!trimmed) return undefined;\n if (/[?#]/.test(trimmed)) {\n throw new Error('Leave out the query string: enter a path like /thank-you or /orders/*/thanks');\n }\n const withScheme = SCHEME.test(trimmed) ? trimmed : `https://placeholder.invalid/${trimmed.replace(/^\\/+/, '')}`;\n let pathname: string;\n try {\n pathname = new URL(withScheme).pathname;\n } catch {\n throw new Error('Enter the conversion page as a path like /thank-you');\n }\n const segments = segmentsOf(pathname);\n if (segments.some((segment) => segment.includes('*') && !isWildcard(segment))) {\n throw new Error('Use * for a whole path segment, like /orders/*/thanks');\n }\n if (segments.length > 0 && segments.every(isWildcard)) {\n throw new Error('That pattern matches every page. Add a fixed part, like /orders/*/thanks');\n }\n return pathname.length > 1 ? pathname.replace(/\\/+$/, '') : pathname;\n}\n\nfunction matchSegments(pattern: string[], path: string[]): boolean {\n if (pattern.length === 0) return path.length === 0;\n const [head, ...rest] = pattern;\n if (head === '**') {\n for (let skip = 0; skip <= path.length; skip++) {\n if (matchSegments(rest, path.slice(skip))) return true;\n }\n return false;\n }\n if (path.length === 0) return false;\n return (head === '*' || head === path[0]) && matchSegments(rest, path.slice(1));\n}\n\nexport function matchesConversionPath(pattern: string, pathname: string): boolean {\n return matchSegments(segmentsOf(pattern), segmentsOf(pathname));\n}\n","import { matchesConversionPath, normalizeConversionPath } from './conversion-path.js';\n\nexport const SITE_TARGET_KINDS = ['text', 'image'] as const;\nexport type SiteTargetKind = (typeof SITE_TARGET_KINDS)[number];\n\nexport interface SiteTarget {\n page: string;\n selector: string;\n tag: string;\n kind: SiteTargetKind;\n original: string;\n}\n\nexport type FunnelGoal =\n | { kind: 'page'; page: string }\n | { kind: 'click'; page: string; selector: string; tag: string; label: string };\n\nconst TAG_NAME = /^[a-z][a-z0-9-]*$/;\n\nfunction collapseWhitespace(raw: string): string {\n return raw.replace(/\\s+/g, ' ').trim();\n}\n\nfunction requireText(raw: string, message: string): string {\n const value = collapseWhitespace(raw);\n if (!value) throw new Error(message);\n return value;\n}\n\nfunction requirePage(raw: string): string {\n const page = normalizeConversionPath(raw);\n if (!page) throw new Error('Enter the page as a path like /pricing');\n return page;\n}\n\nfunction requireTag(raw: string): string {\n const tag = raw.trim().toLowerCase();\n if (!TAG_NAME.test(tag)) throw new Error('Enter the element tag, like h1 or button');\n return tag;\n}\n\nexport function parseSiteContent(config: unknown): string | null {\n if (typeof config !== 'object' || config === null) return null;\n const { content, value } = config as { content?: unknown; value?: unknown };\n for (const candidate of [content, value]) {\n if (typeof candidate === 'string' && candidate.trim()) return candidate;\n }\n return null;\n}\n\nexport function normalizeSiteTarget(target: SiteTarget): SiteTarget {\n if (!SITE_TARGET_KINDS.includes(target.kind)) throw new Error('A target is text or an image');\n return {\n page: requirePage(target.page),\n selector: requireText(target.selector, 'The picked element has no selector'),\n tag: requireTag(target.tag),\n kind: target.kind,\n original: requireText(target.original, 'The picked element has no content to replace'),\n };\n}\n\nexport function normalizeFunnelGoal(goal: FunnelGoal): FunnelGoal {\n const page = requirePage(goal.page);\n if (goal.kind === 'page') return { kind: 'page', page };\n return {\n kind: 'click',\n page,\n selector: requireText(goal.selector, 'The picked element has no selector'),\n tag: requireTag(goal.tag),\n label: collapseWhitespace(goal.label),\n };\n}\n\nexport function targetMatchesPage(target: Pick<SiteTarget, 'page'>, pathname: string): boolean {\n return matchesConversionPath(target.page, pathname);\n}\n\nexport function goalMatchesPage(goal: Pick<FunnelGoal, 'page'>, pathname: string): boolean {\n return matchesConversionPath(goal.page, pathname);\n}\n\nexport const MAX_ALTERNATIVES = 5;\nexport const MAX_TEXT_ALTERNATIVE_LENGTH = 300;\nexport const MAX_IMAGE_URL_LENGTH = 2048;\n\nexport interface PickDraft {\n target?: SiteTarget;\n alternatives: string[];\n goal?: FunnelGoal;\n}\n\nexport interface PickState {\n funnelId: string;\n siteHost: string;\n expiresAt: number;\n draft: PickDraft;\n funnelGoal: FunnelGoal | null;\n experimentId: string | null;\n runningExperiments: number;\n}\n\nexport const SUGGESTED_SITE_EXPERIMENTS = 3;\n\nexport function moreExperimentsSentence(running: number, suggested: number): string {\n const noun = (count: number) => (count === 1 ? 'experiment' : 'experiments');\n const status = `${running} ${noun(running)} running on your site.`;\n if (running >= suggested) return `${status} Keep going if you have more elements to test.`;\n const remaining = suggested - running;\n return `${status} Pick ${remaining} more while you are here. Each element you test is another place to find a win.`;\n}\n\nexport type PickStep = 'pick' | 'alternatives' | 'goal' | 'ready' | 'live';\n\nexport function pickStep(state: Pick<PickState, 'draft' | 'funnelGoal' | 'experimentId'>): PickStep {\n if (state.experimentId !== null) return 'live';\n if (!state.draft.target) return 'pick';\n if (state.draft.alternatives.length === 0) return 'alternatives';\n if (!state.draft.goal && !state.funnelGoal) return 'goal';\n return 'ready';\n}\n\nexport function parseAlternative(kind: SiteTargetKind, raw: string): string {\n if (kind === 'text') {\n const text = requireText(raw, 'Type the text to try');\n if (text.length > MAX_TEXT_ALTERNATIVE_LENGTH) {\n throw new Error(`Keep each option under ${MAX_TEXT_ALTERNATIVE_LENGTH} characters`);\n }\n return text;\n }\n const trimmed = raw.trim();\n if (trimmed.length > MAX_IMAGE_URL_LENGTH) throw new Error('That image address is too long');\n let url: URL;\n try {\n url = new URL(trimmed);\n } catch {\n throw new Error('Enter the image address as a link that starts with https://');\n }\n if (url.protocol !== 'https:') throw new Error('Enter the image address as a link that starts with https://');\n return url.href;\n}\n\nexport const PICK_CHANNEL = 'bandit-pick';\nexport const PICK_PROTOCOL_VERSION = 1;\n\nexport const PICK_MODES = ['idle', 'target', 'goal-click'] as const;\nexport type PickMode = (typeof PICK_MODES)[number];\n\nexport const PICK_REJECTIONS = ['not-text-or-image', 'has-markup', 'not-clickable', 'no-selector'] as const;\nexport type PickRejection = (typeof PICK_REJECTIONS)[number];\n\nexport type ClickFunnelGoal = Extract<FunnelGoal, { kind: 'click' }>;\n\nexport type PickerToPanel =\n | { type: 'navigated'; page: string }\n | { type: 'target-picked'; target: SiteTarget }\n | { type: 'goal-picked'; goal: ClickFunnelGoal }\n | { type: 'rejected'; reason: PickRejection }\n | { type: 'cancelled' };\n\nexport type PanelToPicker =\n | { type: 'hello' }\n | { type: 'mode'; mode: PickMode }\n | { type: 'preview'; target: SiteTarget; content: string }\n | { type: 'clear-preview' }\n | { type: 'finish' }\n | { type: 'renew'; token: string };\n\nexport const PICK_TOKEN_PREFIX = 'bpick_';\n\nexport interface PickEnvelope<M> {\n channel: typeof PICK_CHANNEL;\n v: typeof PICK_PROTOCOL_VERSION;\n msg: M;\n}\n\nexport function pickEnvelope<M extends PickerToPanel | PanelToPicker>(msg: M): PickEnvelope<M> {\n return { channel: PICK_CHANNEL, v: PICK_PROTOCOL_VERSION, msg };\n}\n\ntype Fields = Record<string, unknown>;\n\nfunction isFields(value: unknown): value is Fields {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\nfunction isOneOf<T extends string>(options: readonly T[], value: unknown): value is T {\n return options.some((option) => option === value);\n}\n\nfunction openEnvelope(data: unknown): Fields | null {\n if (!isFields(data) || data.channel !== PICK_CHANNEL || data.v !== PICK_PROTOCOL_VERSION) return null;\n return isFields(data.msg) ? data.msg : null;\n}\n\nfunction asSiteTarget(value: unknown): SiteTarget | null {\n if (!isFields(value)) return null;\n const { page, selector, tag, kind, original } = value;\n if (!isString(page) || !isString(selector) || !isString(tag) || !isString(original)) return null;\n if (!isOneOf(SITE_TARGET_KINDS, kind)) return null;\n return { page, selector, tag, kind, original };\n}\n\nfunction asClickGoal(value: unknown): ClickFunnelGoal | null {\n if (!isFields(value) || value.kind !== 'click') return null;\n const { page, selector, tag, label } = value;\n if (!isString(page) || !isString(selector) || !isString(tag) || !isString(label)) return null;\n return { kind: 'click', page, selector, tag, label };\n}\n\nexport function parsePickerMessage(data: unknown): PickerToPanel | null {\n const msg = openEnvelope(data);\n switch (msg?.type) {\n case 'navigated':\n return isString(msg.page) ? { type: 'navigated', page: msg.page } : null;\n case 'target-picked': {\n const target = asSiteTarget(msg.target);\n return target ? { type: 'target-picked', target } : null;\n }\n case 'goal-picked': {\n const goal = asClickGoal(msg.goal);\n return goal ? { type: 'goal-picked', goal } : null;\n }\n case 'rejected':\n return isOneOf(PICK_REJECTIONS, msg.reason) ? { type: 'rejected', reason: msg.reason } : null;\n case 'cancelled':\n return { type: 'cancelled' };\n default:\n return null;\n }\n}\n\nexport function parsePanelMessage(data: unknown): PanelToPicker | null {\n const msg = openEnvelope(data);\n switch (msg?.type) {\n case 'hello':\n return { type: 'hello' };\n case 'mode':\n return isOneOf(PICK_MODES, msg.mode) ? { type: 'mode', mode: msg.mode } : null;\n case 'preview': {\n const target = asSiteTarget(msg.target);\n return target && isString(msg.content) ? { type: 'preview', target, content: msg.content } : null;\n }\n case 'clear-preview':\n return { type: 'clear-preview' };\n case 'finish':\n return { type: 'finish' };\n case 'renew':\n return isString(msg.token) && msg.token.startsWith(PICK_TOKEN_PREFIX) ? { type: 'renew', token: msg.token } : null;\n default:\n return null;\n }\n}\n","export type SitePlatformPath = 'shopify' | 'script' | 'code'\n\nexport interface SitePlatform {\n id: string\n label: string\n path: SitePlatformPath\n codeBox: string\n steps: readonly string[]\n planNote?: string\n}\n\nexport const SITE_PLATFORMS = [\n { id: 'shopify', label: 'Shopify', path: 'shopify', codeBox: '', steps: [] },\n {\n id: 'webflow',\n label: 'Webflow',\n path: 'script',\n codeBox: 'Site settings, Custom code, Head code. Save, then Publish.',\n steps: [\n 'Open the site in Webflow and click the gear for Site settings.',\n 'Open the Custom code tab.',\n 'Paste the script into the Head code box.',\n 'Click Save changes.',\n 'Publish the site. Custom code only goes live when you publish.',\n ],\n planNote: 'Needs a paid Site plan.',\n },\n {\n id: 'wordpress',\n label: 'WordPress',\n path: 'script',\n codeBox: 'Install the free WPCode plugin, then Code Snippets, Header & Footer, Header box.',\n steps: [\n 'In your WordPress admin go to Plugins, Add New, and search for WPCode. Install and activate it.',\n 'In the left menu open Code Snippets, then Header & Footer.',\n 'Paste the script into the Header box.',\n 'Click Save Changes.',\n 'Load your site once so the tag is in the page.',\n ],\n planNote: 'WordPress.com needs the Business plan for plugins.',\n },\n {\n id: 'squarespace',\n label: 'Squarespace',\n path: 'script',\n codeBox: 'Settings, Website Tools, Code Injection, Header.',\n steps: [\n 'From the Home menu open Settings, then Website Tools. On older sites it is under Advanced.',\n 'Click Code Injection.',\n 'Paste the script into the Header box.',\n 'Click Save.',\n ],\n planNote: 'Needs the Core plan or higher (Business on older plans).',\n },\n {\n id: 'framer',\n label: 'Framer',\n path: 'script',\n codeBox: 'Site Settings, General, Custom Code, Start of head. Publish after saving.',\n steps: [\n 'Open your project and click the gear for Site Settings.',\n 'On the General tab scroll to Custom Code.',\n 'Paste the script under Start of head tag.',\n 'Click Save.',\n 'Publish the site.',\n ],\n planNote: 'Needs a paid Site plan.',\n },\n {\n id: 'wix',\n label: 'Wix',\n path: 'script',\n codeBox: 'Settings, Custom Code, Add Custom Code. Choose All pages and place it in Head.',\n steps: [\n 'In your Wix dashboard open Settings, then Custom Code under Advanced.',\n 'Click Add Custom Code.',\n 'Paste the script and name it Bandit.',\n 'Under Add Code to Pages choose All pages and Load code once.',\n 'Under Place Code in choose Head.',\n 'Click Apply.',\n ],\n planNote: 'Needs a Premium plan with a connected domain.',\n },\n {\n id: 'other',\n label: 'Something else',\n path: 'script',\n codeBox: 'Any custom-code or head-injection box, or the HTML template, on every page.',\n steps: [\n 'Find where your builder lets you add code to the head of every page. It is usually called Custom code, Code injection, Header scripts, or Tracking code.',\n 'Paste the script there.',\n 'Save and publish.',\n 'If you edit the HTML yourself, paste it before the closing head tag of every page template.',\n ],\n },\n {\n id: 'nextjs_react',\n label: 'Next.js / React',\n path: 'code',\n codeBox: \"Your root layout's head.\",\n steps: ['Add the tag to the head of your root layout.'],\n },\n {\n id: 'plain_html',\n label: 'Plain HTML',\n path: 'code',\n codeBox: 'The head of every page.',\n steps: ['Paste the tag into the head of every page.'],\n },\n {\n id: 'tag_manager',\n label: 'Tag manager',\n path: 'code',\n codeBox: 'A Custom HTML tag that fires on all pages.',\n steps: ['Create a Custom HTML tag with the script and fire it on all pages.'],\n },\n] as const satisfies readonly SitePlatform[]\n\nexport type SitePlatformId = (typeof SITE_PLATFORMS)[number]['id']\n\nexport const SITE_PLATFORM_IDS: readonly SitePlatformId[] = SITE_PLATFORMS.map((platform) => platform.id)\n\ntype ScriptEntry = Extract<(typeof SITE_PLATFORMS)[number], { path: 'script' }>\n\nexport type ScriptPlatform = SitePlatform & { id: ScriptEntry['id']; path: 'script' }\n\nexport const SCRIPT_PLATFORMS: readonly ScriptPlatform[] = SITE_PLATFORMS.filter(\n (platform): platform is ScriptEntry => platform.path === 'script',\n)\n\nexport function platformById(id: string | undefined): SitePlatform | undefined {\n return SITE_PLATFORMS.find((platform) => platform.id === id)\n}\n","\nexport const SESSION_ID_KEY = 'bandits_session_id';\nexport const SESSION_CREATED_AT_KEY = 'bandits_session_created_at';\n\nexport const VISITOR_ID_KEY = 'bandits_visitor_id';\n\nexport const DEFAULT_SESSION_TTL_MS = 86400000;\n\nexport function generateId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\nexport interface SessionOptions {\n storage?: Storage | null;\n now?: () => number;\n ttlMs?: number;\n}\n\nexport function getOrCreateSessionId(options: SessionOptions = {}): string {\n const { storage, now = () => Date.now(), ttlMs = DEFAULT_SESSION_TTL_MS } = options;\n if (!storage) return generateId();\n\n try {\n const stored = storage.getItem(SESSION_ID_KEY);\n const storedAt = storage.getItem(SESSION_CREATED_AT_KEY);\n if (stored && storedAt) {\n const age = now() - parseInt(storedAt, 10);\n if (age < ttlMs) return stored;\n }\n const id = generateId();\n storage.setItem(SESSION_ID_KEY, id);\n storage.setItem(SESSION_CREATED_AT_KEY, String(now()));\n return id;\n } catch {\n return generateId();\n }\n}\n\nexport function getOrCreateVisitorId(storage: Storage | null | undefined): string {\n if (!storage) return generateId();\n try {\n const stored = storage.getItem(VISITOR_ID_KEY);\n if (stored) return stored;\n const id = generateId();\n storage.setItem(VISITOR_ID_KEY, id);\n return id;\n } catch {\n return generateId();\n }\n}\n\nexport function defaultSessionStorage(): Storage | null {\n return typeof window !== 'undefined' && window.localStorage ? window.localStorage : null;\n}\n","import {\n AssignmentRequest,\n TrackNamedEventRequest,\n BanditResult,\n APIResponse,\n ContextualFeatures,\n BundleAssignmentRequest,\n BundleAssignmentResponse,\n matchesConversionPath,\n} from '@runbandit/shared';\nimport { defaultSessionStorage, generateId, getOrCreateSessionId, getOrCreateVisitorId } from './session.js';\n\nexport interface BanditsClientConfig {\n apiUrl: string;\n apiKey: string;\n timeout?: number;\n batchSize?: number;\n flushInterval?: number;\n sessionTtl?: number;\n}\n\nexport class BanditsRequestError extends Error {\n readonly status: number | undefined;\n\n constructor(message: string, status: number | undefined) {\n super(message);\n this.name = 'BanditsRequestError';\n this.status = status;\n }\n}\n\ntype RequestResult<T> = APIResponse<T> & { status?: number };\n\nfunction requestError(response: RequestResult<unknown>, fallback: string): BanditsRequestError {\n return new BanditsRequestError(response.error || fallback, response.status);\n}\n\nexport interface Assignment {\n assignmentId: string;\n treatmentId: string;\n experimentId: string;\n userId: string;\n config: any;\n funnelId?: string;\n}\n\nfunction currentPageUrl(): string | undefined {\n if (typeof location !== 'undefined' && location.origin && location.pathname) {\n return location.origin + location.pathname;\n }\n return undefined;\n}\n\nexport class BanditsClient {\n private config: BanditsClientConfig;\n private namedEventQueue: TrackNamedEventRequest[] = [];\n private sessionId: string;\n readonly visitorId: string;\n private flushTimer?: NodeJS.Timeout;\n\n constructor(config: BanditsClientConfig) {\n this.config = {\n timeout: 5000,\n batchSize: 100,\n flushInterval: 10000,\n sessionTtl: 86400000,\n ...config\n };\n this.sessionId = this.getOrCreateSessionId();\n this.visitorId = getOrCreateVisitorId(defaultSessionStorage());\n this.startBatchFlush();\n }\n\n async getAssignment(\n experimentId: string,\n userId: string = this.visitorId,\n context?: ContextualFeatures\n ): Promise<Assignment> {\n const request: AssignmentRequest = {\n experimentId,\n userId,\n context,\n sessionId: this.sessionId\n };\n const pageUrl = currentPageUrl();\n if (pageUrl) request.pageUrl = pageUrl;\n\n const response = await this.makeRequest<any>(\n '/assignments',\n 'POST',\n request\n );\n\n if (!response.success || !response.data) {\n throw requestError(response, 'Failed to get assignment');\n }\n\n const assignment = response.data?.data ?? response.data;\n\n if (!assignment?.assignmentId) {\n throw new Error('Invalid assignment response');\n }\n\n return {\n assignmentId: assignment.assignmentId,\n treatmentId: assignment.treatmentId,\n experimentId,\n userId,\n config: assignment.treatment?.config || {},\n funnelId: assignment.funnelId\n };\n }\n\n trackNamedEvent(\n event: { name: string; category?: string; value?: number; metadata?: Record<string, any> },\n opts?: { immediate?: boolean; userId?: string; assignmentId?: string }\n ): void {\n const request: TrackNamedEventRequest = {\n name: event.name,\n category: event.category || 'custom',\n value: event.value,\n metadata: event.metadata,\n sessionId: this.sessionId,\n };\n if (opts?.userId) request.userId = opts.userId;\n if (opts?.assignmentId) request.assignmentId = opts.assignmentId;\n\n if (opts?.immediate) {\n const url = `${this.normalizeApiUrl()}/events`;\n try {\n void fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n keepalive: true,\n });\n } catch {\n }\n return;\n }\n\n this.namedEventQueue.push(request);\n if (this.namedEventQueue.length >= this.config.batchSize!) {\n this.flush();\n }\n }\n\n async flush(): Promise<void> {\n const namedEvents = [...this.namedEventQueue];\n this.namedEventQueue = [];\n if (namedEvents.length === 0) return;\n\n try {\n await fetch(`${this.normalizeApiUrl()}/events/batch`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ events: namedEvents }),\n keepalive: true,\n });\n } catch (error) {\n this.namedEventQueue.unshift(...namedEvents);\n throw error;\n }\n }\n\n async identify(\n anonymousId: string,\n identifiedUserId: string,\n mergeReason?: 'user_signup' | 'email_verification' | 'social_login' | 'account_recovery' | 'manual'\n ): Promise<void> {\n const response = await this.makeRequest(\n '/identify',\n 'POST',\n {\n anonymousId,\n identifiedUserId,\n mergeReason: mergeReason || 'manual',\n }\n );\n\n if (!response.success) {\n throw requestError(response, 'Failed to identify user');\n }\n }\n\n async label(userId: string, labels: Record<string, string | boolean>): Promise<void> {\n const response = await this.makeRequest('/labels', 'POST', { userId, labels });\n\n if (!response.success) {\n throw requestError(response, 'Failed to record labels');\n }\n }\n\n async trackFunnelReward(\n funnelId: string,\n userId: string,\n value: number\n ): Promise<void> {\n const response = await this.makeRequest(\n `/funnels/${funnelId}/reward`,\n 'POST',\n { userId, value }\n );\n\n if (!response.success) {\n throw requestError(response, 'Failed to record funnel reward');\n }\n }\n\n convert(funnelId: string, value = 1): Promise<void> {\n return this.trackFunnelReward(funnelId, this.visitorId, value);\n }\n\n async convertOn(funnelId: string, pathPattern: string, value = 1): Promise<boolean> {\n if (typeof location === 'undefined' || !matchesConversionPath(pathPattern, location.pathname)) return false;\n await this.convert(funnelId, value);\n return true;\n }\n\n async getBundle(\n bundleId: string,\n userId: string,\n context?: ContextualFeatures\n ): Promise<BundleAssignmentResponse> {\n const request: BundleAssignmentRequest = {\n bundleId,\n userId,\n context,\n sessionId: this.sessionId,\n };\n const pageUrl = currentPageUrl();\n if (pageUrl) request.pageUrl = pageUrl;\n\n const response = await this.makeRequest<any>('/bundles/assignment', 'POST', request);\n\n if (!response.success || !response.data) {\n throw requestError(response, 'Failed to get bundle assignment');\n }\n\n const payload = response.data?.data ?? response.data;\n if (!payload?.bundleAssignmentId || !payload?.slots) {\n throw new Error('Invalid bundle assignment response');\n }\n\n return {\n bundleAssignmentId: payload.bundleAssignmentId,\n slots: payload.slots,\n };\n }\n\n async trackBundleReward(bundleId: string, userId: string, value: number): Promise<void> {\n const response = await this.makeRequest(`/bundles/${bundleId}/reward`, 'POST', {\n userId,\n value,\n });\n if (!response.success) {\n throw requestError(response, 'Failed to record bundle reward');\n }\n }\n\n private generateId(): string {\n return generateId();\n }\n\n private normalizeApiUrl(): string {\n let baseUrl = this.config.apiUrl.replace(/\\/$/, '');\n if (!baseUrl.endsWith('/api')) {\n baseUrl = `${baseUrl}/api`;\n }\n return baseUrl;\n }\n\n private getOrCreateSessionId(): string {\n return getOrCreateSessionId({\n storage: defaultSessionStorage(),\n ttlMs: this.config.sessionTtl,\n });\n }\n\n private async makeRequest<T = any>(\n endpoint: string,\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n body?: any,\n extraHeaders?: Record<string, string>\n ): Promise<RequestResult<T>> {\n const url = `${this.normalizeApiUrl()}${endpoint}`;\n\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n ...extraHeaders,\n };\n\n const requestConfig: RequestInit = {\n method,\n headers,\n signal: AbortSignal.timeout(this.config.timeout!),\n keepalive: method === 'POST',\n };\n\n if (body && method !== 'GET') {\n requestConfig.body = JSON.stringify(body);\n }\n\n try {\n const response = await fetch(url, requestConfig);\n const data = await response.json();\n \n return {\n success: response.ok,\n status: response.status,\n data: response.ok ? data as T : undefined,\n error: response.ok ? undefined : (data as any)?.message || 'Request failed',\n timestamp: new Date()\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Network error',\n timestamp: new Date()\n };\n }\n }\n\n private startBatchFlush(): void {\n this.flushTimer = setInterval(() => {\n this.flush().catch(console.error);\n }, this.config.flushInterval);\n }\n\n destroy(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n }\n this.flush().catch(console.error);\n if (typeof window !== 'undefined' && window.localStorage) {\n localStorage.removeItem('bandits_session_id');\n localStorage.removeItem('bandits_session_created_at');\n }\n }\n}","export type {\n SnapshotTreatment,\n SnapshotExperiment,\n CompanySnapshot,\n} from '@runbandit/shared';\nimport type { CompanySnapshot, SnapshotTreatment } from '@runbandit/shared';\nimport { defaultSessionStorage, getOrCreateSessionId } from './session.js';\n\nexport interface SyncAssignment {\n clientAssignmentId: string;\n experimentId: string;\n treatmentId: string;\n config: unknown;\n}\n\ninterface PendingReport {\n clientAssignmentId: string;\n experimentId: string;\n treatmentId: string;\n userId: string;\n sessionId: string;\n}\n\nexport interface SnapshotClientConfig {\n apiUrl: string;\n publishableKey: string;\n storage?: Storage;\n now?: () => number;\n sessionTtl?: number;\n inlineSnapshot?: CompanySnapshot;\n fetchImpl?: typeof fetch;\n}\n\nconst STORAGE_KEY = 'bandits_snapshot';\nconst REPORT_QUEUE_KEY = 'bandits_pending_reports';\nconst DECISIONS_KEY = 'bandits_client_assignments';\n\ninterface StoredDecision {\n clientAssignmentId: string;\n treatmentId: string;\n expiresAt: number;\n}\n\nexport function pickTreatment(\n treatments: SnapshotTreatment[],\n draw: number\n): SnapshotTreatment | null {\n if (treatments.length === 0) return null;\n let cumulative = 0;\n for (const treatment of treatments) {\n cumulative += treatment.pct;\n if (draw < cumulative) return treatment;\n }\n return treatments[treatments.length - 1];\n}\n\nconst DEFAULT_ASSIGNMENT_TTL_SECONDS = 86400;\n\nfunction randomId(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';\n let id = '';\n for (let i = 0; i < 24; i++) {\n id += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return id;\n}\n\nexport class SnapshotClient {\n private config: SnapshotClientConfig;\n private storage: Storage | null;\n private now: () => number;\n private fetchImpl: typeof fetch;\n private snapshot: CompanySnapshot | null = null;\n private queue: PendingReport[] = [];\n private sessionId: string;\n private inFlight: Promise<void> | null = null;\n\n constructor(config: SnapshotClientConfig) {\n this.config = config;\n this.now = config.now ?? (() => Date.now());\n this.fetchImpl = config.fetchImpl ?? ((...args) => fetch(...args));\n this.storage =\n config.storage ??\n (typeof window !== 'undefined' && window.localStorage ? window.localStorage : null);\n\n this.queue = this.readQueue();\n this.sessionId = getOrCreateSessionId({\n storage: this.storage ?? defaultSessionStorage(),\n now: this.now,\n ttlMs: config.sessionTtl,\n });\n\n if (config.inlineSnapshot) {\n this.snapshot = config.inlineSnapshot;\n this.persistSnapshot(config.inlineSnapshot);\n } else {\n this.snapshot = this.readSnapshot();\n }\n }\n\n getAssignmentSync(experimentId: string, userId: string): SyncAssignment | null {\n const snapshot = this.freshSnapshot();\n if (!snapshot) return null;\n\n const experiment = snapshot.experiments[experimentId];\n if (!experiment || experiment.mode !== 'snapshot') return null;\n\n const key = `${experimentId}:${userId}`;\n const decisions = this.readDecisions();\n const stored = decisions[key];\n if (stored && stored.expiresAt > this.now()) {\n const held = experiment.treatments.find((t) => t.id === stored.treatmentId);\n if (held) {\n return {\n clientAssignmentId: stored.clientAssignmentId,\n experimentId,\n treatmentId: held.id,\n config: held.config,\n };\n }\n }\n\n const treatment = pickTreatment(experiment.treatments, Math.random());\n if (!treatment) return null;\n\n const clientAssignmentId = randomId();\n const ttlSeconds = experiment.assignmentTtl ?? DEFAULT_ASSIGNMENT_TTL_SECONDS;\n decisions[key] = {\n clientAssignmentId,\n treatmentId: treatment.id,\n expiresAt: this.now() + ttlSeconds * 1000,\n };\n this.writeDecisions(decisions);\n\n this.queue.push({\n clientAssignmentId,\n experimentId,\n treatmentId: treatment.id,\n userId,\n sessionId: this.sessionId,\n });\n this.persistQueue();\n\n return {\n clientAssignmentId,\n experimentId,\n treatmentId: treatment.id,\n config: treatment.config,\n };\n }\n\n async refresh(): Promise<void> {\n const response = await this.fetchImpl(`${this.config.apiUrl}/snapshot`, {\n method: 'GET',\n headers: { 'x-api-key': this.config.publishableKey },\n });\n if (!response.ok) return;\n const snapshot = (await response.json()) as CompanySnapshot;\n this.snapshot = snapshot;\n this.persistSnapshot(snapshot);\n }\n\n async flush(): Promise<void> {\n if (this.inFlight) return this.inFlight;\n this.inFlight = this.doFlush().finally(() => {\n this.inFlight = null;\n });\n return this.inFlight;\n }\n\n private async doFlush(): Promise<void> {\n if (this.queue.length === 0) return;\n const reports = this.queue.slice(0, 1000);\n\n try {\n const response = await this.fetchImpl(`${this.config.apiUrl}/assignments/report`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-api-key': this.config.publishableKey,\n },\n body: JSON.stringify({ reports }),\n keepalive: true,\n });\n if (!response.ok) return;\n } catch {\n return;\n }\n\n this.queue = this.queue.slice(reports.length);\n this.persistQueue();\n }\n\n getSessionId(): string {\n return this.sessionId;\n }\n\n pendingReportCount(): number {\n return this.queue.length;\n }\n\n private freshSnapshot(): CompanySnapshot | null {\n if (!this.snapshot) return null;\n if (this.snapshot.ttl === 0) return null;\n const ageMs = this.now() - this.snapshot.generatedAt;\n if (ageMs > this.snapshot.ttl * 1000) return null;\n return this.snapshot;\n }\n\n private readSnapshot(): CompanySnapshot | null {\n if (!this.storage) return null;\n try {\n const raw = this.storage.getItem(STORAGE_KEY);\n return raw ? (JSON.parse(raw) as CompanySnapshot) : null;\n } catch {\n return null;\n }\n }\n\n private persistSnapshot(snapshot: CompanySnapshot): void {\n if (!this.storage) return;\n try {\n this.storage.setItem(STORAGE_KEY, JSON.stringify(snapshot));\n } catch {}\n }\n\n private readQueue(): PendingReport[] {\n if (!this.storage) return [];\n try {\n const raw = this.storage.getItem(REPORT_QUEUE_KEY);\n return raw ? (JSON.parse(raw) as PendingReport[]) : [];\n } catch {\n return [];\n }\n }\n\n private readDecisions(): Record<string, StoredDecision> {\n if (!this.storage) return {};\n try {\n const raw = this.storage.getItem(DECISIONS_KEY);\n if (!raw) return {};\n const parsed = JSON.parse(raw) as Record<string, StoredDecision>;\n const now = this.now();\n const live: Record<string, StoredDecision> = {};\n for (const [k, d] of Object.entries(parsed)) {\n if (d && d.expiresAt > now) live[k] = d;\n }\n return live;\n } catch {\n return {};\n }\n }\n\n private writeDecisions(decisions: Record<string, StoredDecision>): void {\n if (!this.storage) return;\n try {\n this.storage.setItem(DECISIONS_KEY, JSON.stringify(decisions));\n } catch {}\n }\n\n private persistQueue(): void {\n if (!this.storage) return;\n try {\n this.storage.setItem(REPORT_QUEUE_KEY, JSON.stringify(this.queue));\n } catch {}\n }\n}\n","import { EMAIL_TOKEN_PARAM } from '@runbandit/shared';\n\n\nconst UNTRACKABLE_SCHEME = /^(mailto:|tel:|sms:|javascript:|data:|#)/i;\n\nconst NOTRACK_ATTRIBUTE = 'data-bandit-notrack';\n\nexport interface RewriteOptions {\n linkTable: string[];\n tokens: string[];\n}\n\nexport interface RewriteResult {\n html: string;\n rewritten: number;\n untracked: string[];\n}\n\ninterface Anchor {\n valueStart: number;\n valueEnd: number;\n url: string;\n}\n\nfunction isTrackable(url: string): boolean {\n const trimmed = url.trim();\n if (!trimmed || UNTRACKABLE_SCHEME.test(trimmed)) return false;\n return /^https?:\\/\\//i.test(trimmed);\n}\n\nfunction appendToken(url: string, token: string): string {\n const hashIndex = url.indexOf('#');\n const fragment = hashIndex === -1 ? '' : url.slice(hashIndex);\n const base = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n const separator = base.includes('?') ? (base.includes('&amp;') ? '&amp;' : '&') : '?';\n\n return `${base}${separator}${EMAIL_TOKEN_PARAM}=${token}${fragment}`;\n}\n\nfunction scanAnchors(html: string): Anchor[] {\n const anchors: Anchor[] = [];\n let i = 0;\n\n while (i < html.length) {\n const lt = html.indexOf('<', i);\n if (lt === -1) break;\n\n if (html.startsWith('<!--', lt)) {\n if (html.startsWith('<!--[if', lt) || html.startsWith('<!--<![endif]', lt)) {\n const close = html.indexOf('>', lt);\n i = close === -1 ? html.length : close + 1;\n continue;\n }\n\n const end = html.indexOf('-->', lt);\n i = end === -1 ? html.length : end + 3;\n continue;\n }\n\n const rawText = /^<(script|style)\\b/i.exec(html.slice(lt, lt + 8));\n if (rawText) {\n const closing = new RegExp(`</${rawText[1]}\\\\s*>`, 'i');\n const rest = html.slice(lt);\n const match = closing.exec(rest);\n i = match ? lt + match.index + match[0].length : html.length;\n continue;\n }\n\n const isLinkElement =\n /^<a[\\s/>]/i.test(html.slice(lt, lt + 3)) ||\n /^<v:roundrect[\\s/>]/i.test(html.slice(lt, lt + 13));\n if (!isLinkElement) {\n i = lt + 1;\n continue;\n }\n\n const tagEnd = findTagEnd(html, lt);\n if (tagEnd === -1) break;\n\n const tag = html.slice(lt, tagEnd);\n if (!new RegExp(NOTRACK_ATTRIBUTE, 'i').test(tag)) {\n const href = findHref(html, lt, tagEnd);\n if (href && isTrackable(href.url)) anchors.push(href);\n }\n\n i = tagEnd;\n }\n\n return anchors;\n}\n\nfunction findTagEnd(html: string, start: number): number {\n let quote: string | null = null;\n\n for (let i = start + 1; i < html.length; i++) {\n const ch = html[i];\n if (quote) {\n if (ch === quote) quote = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch;\n continue;\n }\n if (ch === '>') return i + 1;\n }\n\n return -1;\n}\n\nfunction findHref(html: string, tagStart: number, tagEnd: number): Anchor | null {\n const tag = html.slice(tagStart, tagEnd);\n const match = /\\shref\\s*=\\s*(\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/i.exec(tag);\n if (!match) return null;\n\n const quoted = match[2] !== undefined || match[3] !== undefined;\n const value = match[2] ?? match[3] ?? match[4] ?? '';\n\n const valueStart = tagStart + match.index + match[0].indexOf(match[1]) + (quoted ? 1 : 0);\n\n return { valueStart, valueEnd: valueStart + value.length, url: value };\n}\n\nexport function extractEmailLinks(html: string): string[] {\n const seen = new Set<string>();\n const urls: string[] = [];\n\n for (const anchor of scanAnchors(html)) {\n if (seen.has(anchor.url)) continue;\n seen.add(anchor.url);\n urls.push(anchor.url);\n }\n\n return urls;\n}\n\nexport function rewriteEmailLinks(html: string, options: RewriteOptions): RewriteResult {\n const { linkTable, tokens } = options;\n\n const indexOfUrl = new Map<string, number>();\n linkTable.forEach((url, index) => {\n if (!indexOfUrl.has(url)) indexOfUrl.set(url, index);\n });\n\n const anchors = scanAnchors(html);\n const untracked: string[] = [];\n let rewritten = 0;\n\n let output = html;\n for (let i = anchors.length - 1; i >= 0; i--) {\n const anchor = anchors[i];\n const index = indexOfUrl.get(anchor.url);\n\n if (index === undefined || tokens[index] === undefined) {\n untracked.push(anchor.url);\n continue;\n }\n\n output =\n output.slice(0, anchor.valueStart) +\n appendToken(anchor.url, tokens[index]) +\n output.slice(anchor.valueEnd);\n rewritten++;\n }\n\n return { html: output, rewritten, untracked: untracked.reverse() };\n}\n","import { rewriteEmailLinks, extractEmailLinks } from './email-links.js';\n\n\nconst SLOT_PATTERN = /\\{\\{\\s*bandit\\.([a-zA-Z0-9_-]+)\\s*\\}\\}/g;\n\nexport interface RenderEmailOptions {\n slots: Record<string, string>;\n linkTable: string[];\n tokens: string[];\n}\n\nexport interface RenderedEmail {\n html: string;\n rewritten: number;\n untracked: string[];\n missingSlots: string[];\n}\n\nexport function renderEmail(templateHtml: string, options: RenderEmailOptions): RenderedEmail {\n const missingSlots: string[] = [];\n\n const substituted = templateHtml.replace(SLOT_PATTERN, (match, name: string) => {\n const value = options.slots[name];\n if (value === undefined) {\n missingSlots.push(name);\n return match;\n }\n return value;\n });\n\n const { html, rewritten, untracked } = rewriteEmailLinks(substituted, {\n linkTable: options.linkTable,\n tokens: options.tokens,\n });\n\n return { html, rewritten, untracked, missingSlots };\n}\n\nexport function extractSlotNames(templateHtml: string): string[] {\n const names = new Set<string>();\n for (const match of templateHtml.matchAll(SLOT_PATTERN)) names.add(match[1]);\n return [...names];\n}\n\nexport { extractEmailLinks, rewriteEmailLinks };\nexport type { RewriteOptions, RewriteResult } from './email-links.js';\n","import {\n mintEmailToken,\n hashRecipient,\n impressionManifest,\n pickArm,\n uniformFor,\n type EmailPlan,\n} from '@runbandit/shared';\nimport { renderEmail, type RenderedEmail } from './email.js';\n\n\nexport interface PlanRenderResult extends RenderedEmail {\n combination: number[];\n tokens: string[];\n}\n\nexport async function renderFromPlan(\n plan: EmailPlan,\n recipientId: string,\n templateHtml: string,\n companySecret: string\n): Promise<PlanRenderResult> {\n const slots: Record<string, string> = {};\n const combination: number[] = [];\n\n for (let i = 0; i < plan.slots.length; i++) {\n const slot = plan.slots[i];\n const chosen = pickArm(slot.probabilities, await uniformFor(plan.salt, recipientId, i));\n const index = chosen === -1 ? 0 : chosen;\n\n combination.push(slot.arms[index]?.armIndex ?? 0);\n\n const config = slot.configs[index];\n slots[slot.name] =\n typeof config === 'string'\n ? config\n : ((config as { content?: string; text?: string } | undefined)?.content ??\n (config as { content?: string; text?: string } | undefined)?.text ??\n '');\n }\n\n const recipientHash = await hashRecipient(recipientId, companySecret);\n\n const tokens = await Promise.all(\n plan.linkTable.map((_, linkIndex) =>\n mintEmailToken(\n { c: plan.campaignId, r: recipientHash, k: combination, l: linkIndex },\n plan.signingKey\n )\n )\n );\n\n const rendered = renderEmail(templateHtml, {\n slots,\n linkTable: plan.linkTable,\n tokens,\n });\n\n return { ...rendered, combination, tokens };\n}\n\nexport function shouldRefreshPlan(\n plan: EmailPlan,\n recipientsRenderedSincePlan: number,\n now: number = Date.now()\n): boolean {\n return recipientsRenderedSincePlan >= plan.refreshEvery || now >= plan.expiresAt;\n}\n\nexport async function reportPlanImpressions(\n plan: EmailPlan,\n recipientIds: string[],\n options: { apiUrl: string; apiKey: string; batchKey?: string }\n): Promise<{ recorded: boolean; alreadyRecorded: boolean }> {\n const counts = await impressionManifest(plan, recipientIds);\n if (counts.length === 0) return { recorded: false, alreadyRecorded: false };\n\n const response = await fetch(`${options.apiUrl}/api/email/impressions`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'x-api-key': options.apiKey },\n body: JSON.stringify({\n campaignId: plan.campaignId,\n batchKey: options.batchKey ?? (await batchKeyFor(plan.campaignId, recipientIds)),\n counts,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to report impressions: ${response.status}`);\n }\n\n const body = (await response.json()) as { data?: { recorded: boolean; alreadyRecorded: boolean } };\n return body.data ?? { recorded: false, alreadyRecorded: false };\n}\n\nasync function batchKeyFor(campaignId: string, recipientIds: string[]): Promise<string> {\n const material = `${campaignId}|${[...recipientIds].sort().join(',')}`;\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(material));\n return Array.from(new Uint8Array(digest).slice(0, 16))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n"],"mappings":"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","names":["ExperimentStatus","BanditAlgorithm","Posterior","ForgettingMode","ContentType","FunnelStatus"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runbandit/sdk",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",