@fanfare-io/fanfare-sdk-core 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,8 @@ export interface AuctionDriverDependencies {
14
14
  runSerializedOperation(operation: () => Promise<void>): Promise<void>;
15
15
  /** Read the current routed sequence before validating an auction action. */
16
16
  getCurrentSequence(): SequenceState | undefined;
17
+ /** Read the live display state before ensureDisplayAtom resets it (settleAt lives here). */
18
+ getDisplayState(): DistributionDisplayState | null;
17
19
  /** Commit a concrete next auction sequence into the shell. */
18
20
  commit(next: SequenceState, opts?: {
19
21
  events?: JourneyEvent[];
@@ -1 +1 @@
1
- import{createError as i}from"../core/errors.js";import{isMoneyString as t,addMoney as e}from"../core/money.js";function n(i){return new r(i)}function s(t){const{distribution:e,consumer:n,waitlist:s,grant:a,outcome:o}=t;if(!e)return{phase:"unavailable"};const r=function(t){if("auction"!==t.type)throw i.validationError("Auction driver received a non-auction distribution");return t}(e),d=n&&"auction"===n.mechanism?function(i){const{mechanism:t,...e}=i;return e}(n):u(e);if(a)return"completed"===o?.type?c(r,d,o):a.expiresAt&&Date.now()>new Date(a.expiresAt).getTime()?"open"===r.lifecycle?{phase:"enterable",mechanism:"auction",distribution:r,consumer:u(r)}:c(r,d,{type:"expired"}):{phase:"granted",mechanism:"auction",distribution:r,consumer:d,grant:a};if(!o&&"auction"===n?.mechanism&&"closed"===r.lifecycle)return c(r,d,{type:"lost"});const h=o??function(i){return"lost"===i.status?{type:"lost"}:void 0}(d);if(h)return c(r,d,h);if(function(i){return"bidding"===i.status||"winning"===i.status||"outbid"===i.status}(d))return{phase:"participating",mechanism:"auction",distribution:r,consumer:d};if("scheduled"===r.lifecycle){const i=s&&s.waitlistId===r.waitlistId?s:r.waitlistId?{waitlistId:r.waitlistId,status:"not_waitlisted"}:void 0;return{phase:"scheduled",mechanism:"auction",distribution:r,...i?{waitlist:i}:{}}}return"open"===r.lifecycle?{phase:"enterable",mechanism:"auction",distribution:r,consumer:d}:"settling"===r.lifecycle?{phase:"settling",mechanism:"auction",distribution:r,consumer:d}:c(r,d,{type:"closed"})}function a(i,t){return"auction"===i?.mechanism?{...i,status:"won"}:{mechanism:"auction",status:"won",id:t.id,distributionId:t.id}}function o(i,t){return i&&"auction"===i.mechanism&&t?"won"===t.type?{...i,status:"won"}:{...i,status:"lost"}:i}class r{constructor(i){this.deps=i,this.mechanism="auction"}project(i){return s(i)}buildView(t,e){if("unavailable"===t.phase)return{phase:"unavailable",reason:t.reason};const n=function(t){if(!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("Auction driver received a non-auction sequence");return t}(t);switch(n.phase){case"scheduled":return{phase:"scheduled",mechanism:"auction",distribution:n.distribution,startsAt:n.distribution.startsAt??n.distribution.opensAt,waitlist:n.waitlist?{status:n.waitlist.status,join:()=>e.joinWaitlist(),leave:()=>e.exitWaitlist()}:void 0};case"enterable":return{phase:"enterable",mechanism:"auction",distribution:n.distribution,consumer:n.consumer,bid:i=>this.bid(i)};case"participating":return{phase:"participating",mechanism:"auction",consumer:n.consumer,display$:e.displayAtom,bid:i=>this.bid(i),leave:()=>this.leave()};case"settling":return{phase:"settling",mechanism:"auction",distribution:n.distribution,consumer:n.consumer};case"granted":return{phase:"granted",mechanism:"auction",distribution:n.distribution,consumer:n.consumer,grant:n.grant,claim:()=>e.claim()};case"ended":return{phase:"ended",mechanism:"auction",consumer:n.consumer,outcome:n.outcome}}}startMonitoring(i){this.activeMonitorId=i.participation.id,this.deps.auctions.startMonitoring(i.participation.id,{displayAtom:i.displayAtom},i.onUpdate)}stopMonitoring(){this.activeMonitorId&&(this.deps.auctions.stopMonitoring(this.activeMonitorId),this.activeMonitorId=void 0)}async enter(){await this.deps.runSerializedOperation(async()=>{await this.enterWithinOperation()})}async enterWithinOperation(){const i=this.currentAuctionSequence("enterable","No open auction to enter").distribution;await this.deps.auctions.enter(i.id,void 0),await this.deps.auctions.status(i.id);const t={mechanism:"auction",status:"bidding",id:e=i.id,distributionId:e};var e;this.deps.ensureDisplayAtom({id:i.id,type:"auction"}),this.deps.commit(s({distribution:i,consumer:t})),this.deps.emitEvent("sequence_change","success","user","Entered auction",{distributionId:i.id})}async leave(){await this.deps.runSerializedOperation(async()=>{await this.leaveWithinOperation()})}async leaveWithinOperation(){const i=this.currentAuctionSequence("participating"),t=i.consumer.distributionId??i.consumer.id??i.distribution.id;await this.deps.auctions.leave(t),this.deps.stopRuntimeMonitoring(!0),this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"left"}}),this.deps.emitEvent("sequence_change","info","user","Left participation",{type:"auction"})}async bid(i){await this.deps.runSerializedOperation(async()=>{const n=this.currentAuctionSequenceForBid(),a=n.consumer.distributionId??n.consumer.id??n.distribution.id,o=await this.deps.auctions.bid(a,i),r="winning"===o.status?"winning":"outbid"===o.status?"outbid":"bidding",u="auction"===n.distribution.details?.type?n.distribution.details:void 0,c=u?.bidIncrement,d=o.highestBid&&c&&t(o.highestBid)&&t(c)?e(o.highestBid,c):void 0,h=this.deps.ensureDisplayAtom({id:a,type:"auction"});"enterable"===n.phase&&this.deps.commit(s({distribution:n.distribution,consumer:{mechanism:"auction",status:r,id:a,distributionId:a,currentBid:i,highestBid:o.highestBid}})),h.set({type:"auction",currentBid:i,highestBid:o.highestBid,bidCount:o.bidCount,status:r,currencyCode:u?.currencyCode,bidIncrement:c,minNextBid:d,closeAt:u?.closeAt??u?.endsAt})})}currentAuctionSequence(t,e="Not participating in an auction"){const n=this.deps.getCurrentSequence();if(!n||n.phase!==t||!("mechanism"in n)||"auction"!==n.mechanism)throw i.validationError(e);return n}currentAuctionSequenceForBid(){const t=this.deps.getCurrentSequence();if(!t||"enterable"!==t.phase&&"participating"!==t.phase||!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("No auction is available to bid on");return t}}function u(i){return{status:"not_bid",distributionId:i.id}}function c(i,t,e){return{phase:"ended",mechanism:"auction",distribution:i,consumer:t,outcome:{...e,distributionType:e.distributionType??i.type,distributionId:e.distributionId??i.id,at:e.at??/* @__PURE__ */(new Date).toISOString()}}}export{n as createAuctionDriver,s as projectAuctionSequence,a as withAuctionGrantedConsumer,o as withAuctionTerminalConsumer};
1
+ import{createError as i}from"../core/errors.js";import{isMoneyString as t,addMoney as e}from"../core/money.js";function n(i){return new r(i)}function s(t){const{distribution:e,consumer:n,waitlist:s,grant:a,outcome:o}=t;if(!e)return{phase:"unavailable"};const r=function(t){if("auction"!==t.type)throw i.validationError("Auction driver received a non-auction distribution");return t}(e),d=n&&"auction"===n.mechanism?function(i){const{mechanism:t,...e}=i;return e}(n):u(e);if(a)return"completed"===o?.type?c(r,d,o):a.expiresAt&&Date.now()>new Date(a.expiresAt).getTime()?"open"===r.lifecycle?{phase:"enterable",mechanism:"auction",distribution:r,consumer:u(r)}:c(r,d,{type:"expired"}):{phase:"granted",mechanism:"auction",distribution:r,consumer:d,grant:a};if(!o&&"auction"===n?.mechanism&&"closed"===r.lifecycle)return c(r,d,{type:"lost"});const h=o??function(i){return"lost"===i.status?{type:"lost"}:void 0}(d);if(h)return c(r,d,h);if(function(i){return"bidding"===i.status||"winning"===i.status||"outbid"===i.status}(d))return{phase:"participating",mechanism:"auction",distribution:r,consumer:d};if("scheduled"===r.lifecycle){const i=s&&s.waitlistId===r.waitlistId?s:r.waitlistId?{waitlistId:r.waitlistId,status:"not_waitlisted"}:void 0;return{phase:"scheduled",mechanism:"auction",distribution:r,...i?{waitlist:i}:{}}}return"open"===r.lifecycle?{phase:"enterable",mechanism:"auction",distribution:r,consumer:d}:"settling"===r.lifecycle?{phase:"settling",mechanism:"auction",distribution:r,consumer:d}:c(r,d,{type:"closed"})}function a(i,t){return"auction"===i?.mechanism?{...i,status:"won"}:{mechanism:"auction",status:"won",id:t.id,distributionId:t.id}}function o(i,t){return i&&"auction"===i.mechanism&&t?"won"===t.type?{...i,status:"won"}:{...i,status:"lost"}:i}class r{constructor(i){this.deps=i,this.mechanism="auction"}project(i){return s(i)}buildView(t,e){if("unavailable"===t.phase)return{phase:"unavailable",reason:t.reason};const n=function(t){if(!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("Auction driver received a non-auction sequence");return t}(t);switch(n.phase){case"scheduled":return{phase:"scheduled",mechanism:"auction",distribution:n.distribution,startsAt:n.distribution.startsAt??n.distribution.opensAt,waitlist:n.waitlist?{status:n.waitlist.status,join:()=>e.joinWaitlist(),leave:()=>e.exitWaitlist()}:void 0};case"enterable":return{phase:"enterable",mechanism:"auction",distribution:n.distribution,consumer:n.consumer,bid:i=>this.bid(i)};case"participating":return{phase:"participating",mechanism:"auction",consumer:n.consumer,display$:e.displayAtom,bid:i=>this.bid(i),leave:()=>this.leave()};case"settling":return{phase:"settling",mechanism:"auction",distribution:n.distribution,consumer:n.consumer};case"granted":return{phase:"granted",mechanism:"auction",distribution:n.distribution,consumer:n.consumer,grant:n.grant,claim:()=>e.claim()};case"ended":return{phase:"ended",mechanism:"auction",consumer:n.consumer,outcome:n.outcome}}}startMonitoring(i){this.activeMonitorId=i.participation.id,this.deps.auctions.startMonitoring(i.participation.id,{displayAtom:i.displayAtom},i.onUpdate)}stopMonitoring(){this.activeMonitorId&&(this.deps.auctions.stopMonitoring(this.activeMonitorId),this.activeMonitorId=void 0)}async enter(){await this.deps.runSerializedOperation(async()=>{await this.enterWithinOperation()})}async enterWithinOperation(){const i=this.currentAuctionSequence("enterable","No open auction to enter").distribution;await this.deps.auctions.enter(i.id,void 0),await this.deps.auctions.status(i.id);const t={mechanism:"auction",status:"bidding",id:e=i.id,distributionId:e};var e;this.deps.ensureDisplayAtom({id:i.id,type:"auction"}),this.deps.commit(s({distribution:i,consumer:t})),this.deps.emitEvent("sequence_change","success","user","Entered auction",{distributionId:i.id})}async leave(){await this.deps.runSerializedOperation(async()=>{await this.leaveWithinOperation()})}async leaveWithinOperation(){const i=this.currentAuctionSequence("participating"),t=i.consumer.distributionId??i.consumer.id??i.distribution.id;await this.deps.auctions.leave(t),this.deps.stopRuntimeMonitoring(!0),this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"left"}}),this.deps.emitEvent("sequence_change","info","user","Left participation",{type:"auction"})}async bid(i){await this.deps.runSerializedOperation(async()=>{const n=this.currentAuctionSequenceForBid(),a=n.consumer.distributionId??n.consumer.id??n.distribution.id,o=await this.deps.auctions.bid(a,i),r="winning"===o.status?"winning":"outbid"===o.status?"outbid":"bidding",u="auction"===n.distribution.details?.type?n.distribution.details:void 0,c=u?.bidIncrement,d=o.highestBid&&c&&t(o.highestBid)&&t(c)?e(o.highestBid,c):void 0,h=this.deps.getDisplayState(),p="auction"===h?.type?h.settleAt:void 0,m=this.deps.ensureDisplayAtom({id:a,type:"auction"});"enterable"===n.phase&&this.deps.commit(s({distribution:n.distribution,consumer:{mechanism:"auction",status:r,id:a,distributionId:a,currentBid:i,highestBid:o.highestBid}})),m.set({type:"auction",currentBid:i,highestBid:o.highestBid,bidCount:o.bidCount,status:r,currencyCode:u?.currencyCode,bidIncrement:c,minNextBid:d,closeAt:u?.closeAt??u?.endsAt,settleAt:p})})}currentAuctionSequence(t,e="Not participating in an auction"){const n=this.deps.getCurrentSequence();if(!n||n.phase!==t||!("mechanism"in n)||"auction"!==n.mechanism)throw i.validationError(e);return n}currentAuctionSequenceForBid(){const t=this.deps.getCurrentSequence();if(!t||"enterable"!==t.phase&&"participating"!==t.phase||!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("No auction is available to bid on");return t}}function u(i){return{status:"not_bid",distributionId:i.id}}function c(i,t,e){return{phase:"ended",mechanism:"auction",distribution:i,consumer:t,outcome:{...e,distributionType:e.distributionType??i.type,distributionId:e.distributionId??i.id,at:e.at??/* @__PURE__ */(new Date).toISOString()}}}export{n as createAuctionDriver,s as projectAuctionSequence,a as withAuctionGrantedConsumer,o as withAuctionTerminalConsumer};
@@ -15,9 +15,9 @@ export declare class AuctionManagementModule implements AuctionModule {
15
15
  private readonly auctionDetailsCache;
16
16
  private readonly monitorGenerations;
17
17
  private readonly operationGenerations;
18
- private readonly diagnosticEmissionIds;
19
18
  private readonly BID_HISTORY_CACHE_TTL;
20
19
  private readonly AUCTION_DETAILS_CACHE_TTL;
20
+ private readonly NO_BID_END_GRACE_MS;
21
21
  private destroyed;
22
22
  private lifecycleGeneration;
23
23
  private get store();
@@ -26,6 +26,13 @@ export declare class AuctionManagementModule implements AuctionModule {
26
26
  private getAuctionDetailsCached;
27
27
  private getTrackedParticipation;
28
28
  private getAuctionCloseAt;
29
+ /**
30
+ * The bidding-close (and winner-resolution) instant: the server settles the auction at
31
+ * settleAt, so the monitor's terminal clock keys off it — winners are processed (and
32
+ * granted) at settle, not at the later closeAt "done" edge. Falls back to closeAt when
33
+ * settleAt is absent.
34
+ */
35
+ private getAuctionSettleAt;
29
36
  private buildDisplayState;
30
37
  private emitTransitionEvents;
31
38
  private applyTransition;
@@ -1 +1 @@
1
- import{AuctionBidderStateResponseSchema as t,AuctionHighestBidResponseSchema as i,AuctionBidHistoryResponseSchema as e}from"@fanfare-io/fanfare-sdk-contracts/auction";import{createError as n}from"../core/errors.js";import{getLogger as a}from"../core/logger.js";import{isMoneyString as s,addMoney as o,isMoneyGreaterThan as r,normalizeMoney as d,isMoneyLessThanOrEqualTo as u,subtractMoney as c}from"../core/money.js";import{parseResponse as h}from"../core/parse-response.js";import{DistributionMonitorRuntime as l}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as g}from"../state/events.js";import{getSDKStore as p}from"../state/store.js";class m{constructor(t){this.logger=a(),this.events=g(),this.trackedParticipations=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.pollingIntervals=/* @__PURE__ */new Map,this.lastStatusCache=/* @__PURE__ */new Map,this.autoRebidConfigs=/* @__PURE__ */new Map,this.monitorRuntime=new l,this.bidHistoryCache=/* @__PURE__ */new Map,this.auctionDetailsCache=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.diagnosticEmissionIds=/* @__PURE__ */new Set,this.BID_HISTORY_CACHE_TTL=5e3,this.AUCTION_DETAILS_CACHE_TTL=3e4,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return p()}computeMinNextBid(t,i){if(i&&s(t)&&s(i))return o(t,i)}async getAuctionDetailsCached(t){const i=this.auctionDetailsCache.get(t);if(i&&Date.now()-i.fetchedAt<this.AUCTION_DETAILS_CACHE_TTL)return i.details;const e=await this.http.get(`/auctions/${t}`);return this.auctionDetailsCache.set(t,{details:e,fetchedAt:Date.now()}),e}getTrackedParticipation(t){return this.trackedParticipations.get(t)}getAuctionCloseAt(t){return t.closeAt??t.settleAt}buildDisplayState(t,{status:i,highestBid:e,currentBid:n,bidCount:a}){return{highestBid:e,currentBid:n,bidCount:a,closeAt:this.getAuctionCloseAt(t),status:i,currencyCode:t.currencyCode,minNextBid:e?this.computeMinNextBid(e,t.minBidIncrement):void 0,bidIncrement:t.minBidIncrement??void 0}}emitTransitionEvents(t){for(const i of t)this.events.emit(i.type,i.payload)}applyTransition(t,i){i.trackedParticipation?this.trackedParticipations.set(t,i.trackedParticipation):this.trackedParticipations.delete(t);const e=this.monitorRuntime.getDisplayAtom(t);e&&i.displayState&&e.set({type:"auction",...i.displayState}),this.emitTransitionEvents(i.events),i.diagnosticError&&!this.diagnosticEmissionIds.has(t)?(this.diagnosticEmissionIds.add(t),this.logger.error("Auction monitoring diagnostic",{auctionId:t,error:i.diagnosticError}),this.events.emit("auction:error",{auctionId:t,error:i.diagnosticError})):i.diagnosticError||this.diagnosticEmissionIds.delete(t),i.monitorUpdate&&this.monitorRuntime.notify(t,i.monitorUpdate),i.stopWatching&&this.stopWatching(t),i.autoRebidStatus&&this.handleAutoRebid(t,i.autoRebidStatus)}checkOutbidConditions(t,i,e){if("winning"===t.status&&"outbid"===i.status)return!0;if(e?.currentBid&&i.highestBid){const n=e.currentBid,a=i.highestBid,o=t.highestBid||"0";if(s(n)&&s(a)&&s(o))return r(a,o)&&r(a,n)&&"winning"!==i.status}return"outbid"!==t.status&&"outbid"===i.status}buildBidTransition(t,i,e,n,a){const s="winning"===i.status?"winning":"outbid"===i.status?"outbid":"bidding";return{trackedParticipation:{auctionId:t,enteredAt:a?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:s,currentBid:e,highestBid:i.highestBid,bidCount:i.bidCount,lastBidAt:/* @__PURE__ */(new Date).toISOString(),metadata:a?.metadata,fingerprint:a?.fingerprint},displayState:this.buildDisplayState(n,{status:i.status,highestBid:i.highestBid,currentBid:e,bidCount:i.bidCount}),events:[{type:"auction:bid-placed",payload:{auctionId:t,amount:e,status:i.status}},..."winning"===i.status?[{type:"auction:winning",payload:{auctionId:t,amount:e,highestBid:i.highestBid}}]:[],..."outbid"===i.status?[{type:"auction:outbid",payload:{auctionId:t,yourBid:e,highestBid:i.highestBid}}]:[]],monitorUpdate:null,stopWatching:!1,autoRebidStatus:null}}buildStatusTransition(t,i,e,{previousParticipation:n,previousStatus:a}){const s=i.currentBid??n?.currentBid,o=i.bidCount??n?.bidCount;if(i.timeRemaining<=0){const a="winning"===i.status;return a&&!i.admissionGrant?{trackedParticipation:{auctionId:t,enteredAt:n?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:"winning",currentBid:s,highestBid:i.highestBid,bidCount:o,metadata:n?.metadata,fingerprint:n?.fingerprint},displayState:this.buildDisplayState(e,{status:"winning",highestBid:i.highestBid,currentBid:s,bidCount:o}),events:[],monitorUpdate:null,stopWatching:!1,autoRebidStatus:null,diagnosticError:new Error("Auction resolved to terminal winning without admissionGrant")}:{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:a?"won":"lost",highestBid:i.highestBid,currentBid:s,bidCount:o}),events:a?[{type:"auction:won",payload:{auctionId:t,winningBid:s??""}}]:[{type:"auction:lost",payload:{auctionId:t,highestBid:i.highestBid}}],monitorUpdate:a&&i.admissionGrant?{type:"granted",token:i.admissionGrant}:a?null:{type:"ended",outcome:{type:"lost"}},stopWatching:!0,autoRebidStatus:null}}const r={auctionId:t,enteredAt:n?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:i.status,currentBid:s,highestBid:i.highestBid,bidCount:o,metadata:n?.metadata,fingerprint:n?.fingerprint},d=[];let u=null;return a&&(this.checkOutbidConditions(a,i,n)&&(d.push({type:"auction:outbid",payload:{auctionId:t,yourBid:a.currentBid||n?.currentBid||"",highestBid:i.highestBid}}),u=i),"winning"!==a.status&&"winning"===i.status&&d.push({type:"auction:winning",payload:{auctionId:t,amount:s||"",highestBid:i.highestBid}}),a.highestBid!==i.highestBid&&"winning"!==i.status&&d.push({type:"auction:bid-updated",payload:{auctionId:t,highestBid:i.highestBid,bidCount:i.bidCount||0}})),{trackedParticipation:r,displayState:this.buildDisplayState(e,{status:i.status,highestBid:i.highestBid,currentBid:s,bidCount:o}),events:d,monitorUpdate:null,stopWatching:!1,autoRebidStatus:u}}buildMissingTransition(){return{trackedParticipation:null,displayState:{status:"lost"},events:[],monitorUpdate:{type:"ended",outcome:{type:"closed",reason:"not_participating"}},stopWatching:!0,autoRebidStatus:null}}async get(t){try{return await this.getAuctionDetailsCached(t)}catch(i){throw this.logger.error("Failed to get auction",{auctionId:t,error:i}),i}}async bid(e,a,o){try{const r=this.lifecycleGeneration;if(!s(a))throw n.validationError("Invalid bid amount format. Must be a numeric string (e.g., '100.00')");if(!this.store.session)throw n.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");let u=this.bumpOperationGeneration(e),c=this.getTrackedParticipation(e);c||(this.logger.warn("Attempting to bid without entering auction first",{auctionId:e}),await this.enter(e,void 0,o),c=this.getTrackedParticipation(e),u=this.bumpOperationGeneration(e)),this.logger.info("Placing bid",{auctionId:e,amount:a});const l=`/auctions/${e}/bid`,g=`/auctions/${e}/highest-bid`,[p,m,b]=await Promise.all([this.http.post(l,{amount:a},o),this.http.get(g),this.getAuctionDetailsCached(e)]),y=h(t,p,{endpoint:l},{throwOnFailure:!0});if(!y.ok)throw y.error;const I=h(i,m,{endpoint:g},{throwOnFailure:!0});if(!I.ok)throw I.error;const f=y.value,B=I.value,w=d(B.amount),v={status:"WINNING"===f.status||"WON"===f.status?"winning":"OUTBID"===f.status||"BIDDING"===f.status||"LOST"===f.status?"outbid":"accepted",amount:a,highestBid:w,bidCount:f.bidCount??(c?.bidCount||0)+1};return this.canApplyAsyncResult(e,r,void 0,u)?(this.applyTransition(e,this.buildBidTransition(e,v,a,b,c)),v):v}catch(r){throw this.logger.error("Failed to place bid",{auctionId:e,amount:a,error:r}),this.events.emit("auction:error",{auctionId:e,error:r}),r}}async enter(t,i,e){try{const a=this.lifecycleGeneration,s=this.store.session;if(!s)throw n.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const o=this.bumpOperationGeneration(t);if(this.logger.info("Entering auction",{auctionId:t}),await this.http.post(`/auctions/${t}/enter`,{metadata:i},e),!this.canApplyAsyncResult(t,a,void 0,o))return;this.trackedParticipations.set(t,{auctionId:t,enteredAt:/* @__PURE__ */(new Date).toISOString(),status:"watching",metadata:i,fingerprint:s.deviceFingerprint}),this.emitTransitionEvents([{type:"auction:entered",payload:{auctionId:t}}])}catch(a){throw this.logger.error("Failed to enter auction",{auctionId:t,error:a}),this.events.emit("auction:error",{auctionId:t,error:a}),a}}async leave(t,i){try{const n=this.lifecycleGeneration,a=this.bumpOperationGeneration(t);this.logger.info("Leaving auction",{auctionId:t});try{await this.http.post(`/auctions/${t}/leave`,void 0,i)}catch(e){if(404!==e?.status)throw e;this.logger.debug("Leave endpoint not available",{auctionId:t})}if(!this.canApplyAsyncResult(t,n,void 0,a))return;this.stopMonitoring(t),this.trackedParticipations.delete(t),this.emitTransitionEvents([{type:"auction:left",payload:{auctionId:t}}])}catch(e){throw this.logger.error("Failed to leave auction",{auctionId:t,error:e}),this.events.emit("auction:error",{auctionId:t,error:e}),e}}async status(t){const i=this.inFlightRequests.get(t);if(i)return this.logger.debug("Returning existing in-flight request",{auctionId:t}),i;const e=this.lifecycleGeneration,n=this.bumpOperationGeneration(t),a=this.doStatus(t,e,void 0,n).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,a),a}async doStatus(e,n,a,s){try{const o=`/auctions/${e}/status`,r=`/auctions/${e}/highest-bid`,[u,c,l]=await Promise.all([this.http.get(o),this.http.get(r),this.getAuctionDetailsCached(e)]),g=h(t,u,{endpoint:o},{throwOnFailure:!0});if(!g.ok)throw g.error;const p=h(i,c,{endpoint:r},{throwOnFailure:!0});if(!p.ok)throw p.error;const m=g.value,b=p.value,y=this.getAuctionCloseAt(l),I=y?new Date(y).getTime():Number.NaN,f=d(b.amount),B=this.getTrackedParticipation(e),w=m.lastBidAmount??m.winningBidAmount;let v="watching";"WINNING"===m.status||"WON"===m.status?v="winning":"OUTBID"!==m.status&&"LOST"!==m.status&&"BIDDING"!==m.status||(v="outbid");const A={auctionId:e,status:v,currentBid:B?.currentBid??(w?d(w):void 0),highestBid:f,timeRemaining:Number.isFinite(I)?Math.max(0,I-Date.now()):0,bidCount:m.bidCount,admissionGrant:m.admissionGrant};if(!this.canApplyAsyncResult(e,n,a,s))return A;const C=this.buildStatusTransition(e,A,l,{previousParticipation:B,previousStatus:this.lastStatusCache.get(e)});return this.applyTransition(e,C),C.stopWatching?this.lastStatusCache.delete(e):this.lastStatusCache.set(e,A),this.events.emit("auction:status-updated",{auctionId:e,status:A}),A}catch(o){if(o&&"object"==typeof o&&"status"in o&&404===o.status){const t={auctionId:e,status:"watching",highestBid:"0.00",timeRemaining:0};if(!this.canApplyAsyncResult(e,n,a,s))return t;const i=this.buildMissingTransition();return this.applyTransition(e,i),this.lastStatusCache.delete(e),this.events.emit("auction:status-updated",{auctionId:e,status:t}),t}if(!this.canApplyAsyncResult(e,n,a,s))return this.lastStatusCache.get(e)??{auctionId:e,status:"watching",highestBid:"0.00",timeRemaining:0};throw this.logger.error("Failed to get auction status",{auctionId:e,error:o}),this.events.emit("auction:error",{auctionId:e,error:o}),o}}async getBidHistory(t){try{const i=this.bidHistoryCache.get(t);if(i&&Date.now()-i.fetchedAt<this.BID_HISTORY_CACHE_TTL)return this.logger.debug("Returning cached bid history",{auctionId:t}),i.bids;this.logger.info("Fetching bid history",{auctionId:t});const n=`/auctions/${t}/bids/history`,a=await this.http.get(n),s=h(e,a,{endpoint:n},{throwOnFailure:!0});if(!s.ok)throw s.error;const o=s.value.map(t=>({amount:d(t.amount),timestamp:t.timestamp,isWinning:t.isHighest,isYours:!0}));return this.bidHistoryCache.set(t,{bids:o,fetchedAt:Date.now()}),this.events.emit("auction:history-fetched",{auctionId:t,bidCount:o.length}),o}catch(i){throw this.logger.error("Failed to get bid history",{auctionId:t,error:i}),this.events.emit("auction:error",{auctionId:t,error:i}),i}}startWatching(t,i=5e3){this.stopWatching(t);const e=this.bumpMonitorGeneration(t);this.logger.info("Starting auction status polling",{auctionId:t,intervalMs:i});const n=this.getOperationGeneration(t);this.doStatus(t,this.lifecycleGeneration,e,n).catch(i=>{this.canApplyAsyncResult(t,this.lifecycleGeneration,e,n)&&this.logger.error("Initial status check failed",{auctionId:t,error:i})});const a=setInterval(()=>{const i=this.getOperationGeneration(t);this.doStatus(t,this.lifecycleGeneration,e,i).catch(n=>{this.canApplyAsyncResult(t,this.lifecycleGeneration,e,i)&&this.logger.error("Polling failed",{auctionId:t,error:n})})},i);this.pollingIntervals.set(t,a),this.events.emit("auction:watching-started",{auctionId:t,intervalMs:i})}stopWatching(t){this.bumpMonitorGeneration(t);const i=this.pollingIntervals.get(t);i&&(clearInterval(i),this.pollingIntervals.delete(t),this.lastStatusCache.delete(t),this.logger.info("Stopped watching auction",{auctionId:t}),this.events.emit("auction:watching-stopped",{auctionId:t}))}stopAllWatching(){for(const t of Array.from(this.pollingIntervals.keys()))this.stopWatching(t)}bumpMonitorGeneration(t){const i=(this.monitorGenerations.get(t)??0)+1;return this.monitorGenerations.set(t,i),i}getOperationGeneration(t){return this.operationGenerations.get(t)??0}bumpOperationGeneration(t){const i=this.getOperationGeneration(t)+1;return this.operationGenerations.set(t,i),i}canApplyAsyncResult(t,i,e,n){return!this.destroyed&&this.lifecycleGeneration===i&&((void 0===e||this.monitorGenerations.get(t)===e)&&(void 0===n||this.getOperationGeneration(t)===n))}enableAutoRebid(t,i,e){if(!s(i)||!s(e))throw n.validationError("Invalid monetary format. Must be numeric strings (e.g., '100.00')");if(u(i,"0")||u(e,"0"))throw n.validationError("Max bid and increment must be positive");if(r(e,i))throw n.validationError("Increment cannot be greater than max bid");const a=this.getTrackedParticipation(t);if(!a)throw n.validationError("Must enter auction before enabling auto-rebid");const o=d(a.currentBid),h=c(i,o);this.autoRebidConfigs.set(t,{enabled:!0,maxBid:i,increment:e,remainingBudget:h,rebidCount:0}),this.logger.info("Auto-rebid enabled",{auctionId:t,maxBid:i,increment:e,remainingBudget:h}),this.events.emit("auction:auto-rebid-enabled",{auctionId:t,maxBid:i,increment:e})}disableAutoRebid(t){const i=this.autoRebidConfigs.get(t);i&&(i.enabled=!1,this.logger.info("Auto-rebid disabled",{auctionId:t}),this.events.emit("auction:auto-rebid-disabled",{auctionId:t,finalRebidCount:i.rebidCount||0}))}getAutoRebidConfig(t){return this.autoRebidConfigs.get(t)}async handleAutoRebid(t,i){const e=this.autoRebidConfigs.get(t);if(e&&e.enabled)try{if(!s(i.highestBid)||!s(e.maxBid)||!s(e.increment))return void this.logger.error("Invalid auto-rebid amounts",{auctionId:t,config:e});const n=o(i.highestBid,e.increment);if(r(n,e.maxBid))return this.logger.info("Auto-rebid would exceed max bid, disabling",{auctionId:t,wouldBid:n,maxBid:e.maxBid}),this.disableAutoRebid(t),void this.events.emit("auction:auto-rebid-max-reached",{auctionId:t,maxBid:e.maxBid,wouldNeedToBid:n});this.logger.info("Placing auto-rebid",{auctionId:t,amount:n});const a=await this.bid(t,n);e.lastRebidAt=/* @__PURE__ */(new Date).toISOString(),e.rebidCount=(e.rebidCount||0)+1,e.remainingBudget=c(e.maxBid,n),this.events.emit("auction:auto-rebid-placed",{auctionId:t,amount:n,remainingBudget:e.remainingBudget,status:a.status})}catch(n){this.logger.error("Auto-rebid failed",{auctionId:t,error:n}),this.events.emit("auction:auto-rebid-failed",{auctionId:t,error:n})}}startMonitoring(t,i,e){this.monitorRuntime.start(t,i,e),this.trackedParticipations.has(t)||this.trackedParticipations.set(t,{auctionId:t,enteredAt:/* @__PURE__ */(new Date).toISOString(),status:"watching"}),this.startWatching(t)}stopMonitoring(t){this.stopWatching(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)||this.pollingIntervals.has(t)}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.stopAllWatching(),this.inFlightRequests.clear(),this.lastStatusCache.clear(),this.diagnosticEmissionIds.clear(),this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.bidHistoryCache.clear(),this.auctionDetailsCache.clear(),this.autoRebidConfigs.clear(),this.monitorRuntime.clear()}}export{m as AuctionManagementModule};
1
+ import{AuctionBidderStateResponseSchema as t,AuctionHighestBidResponseSchema as i,AuctionBidHistoryResponseSchema as e}from"@fanfare-io/fanfare-sdk-contracts/auction";import{createError as n}from"../core/errors.js";import{getLogger as a}from"../core/logger.js";import{isMoneyString as s,addMoney as o,isMoneyGreaterThan as r,normalizeMoney as u,isMoneyLessThanOrEqualTo as d,subtractMoney as c}from"../core/money.js";import{parseResponse as h}from"../core/parse-response.js";import{DistributionMonitorRuntime as l}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as g}from"../state/events.js";import{getSDKStore as p}from"../state/store.js";class m{constructor(t){this.logger=a(),this.events=g(),this.trackedParticipations=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.pollingIntervals=/* @__PURE__ */new Map,this.lastStatusCache=/* @__PURE__ */new Map,this.autoRebidConfigs=/* @__PURE__ */new Map,this.monitorRuntime=new l,this.bidHistoryCache=/* @__PURE__ */new Map,this.auctionDetailsCache=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.BID_HISTORY_CACHE_TTL=5e3,this.AUCTION_DETAILS_CACHE_TTL=3e4,this.NO_BID_END_GRACE_MS=5e3,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return p()}computeMinNextBid(t,i){if(i&&s(t)&&s(i))return o(t,i)}async getAuctionDetailsCached(t){const i=this.auctionDetailsCache.get(t);if(i&&Date.now()-i.fetchedAt<this.AUCTION_DETAILS_CACHE_TTL)return i.details;const e=await this.http.get(`/auctions/${t}`);return this.auctionDetailsCache.set(t,{details:e,fetchedAt:Date.now()}),e}getTrackedParticipation(t){return this.trackedParticipations.get(t)}getAuctionCloseAt(t){return t.closeAt??t.settleAt}getAuctionSettleAt(t){return t.settleAt??t.closeAt}buildDisplayState(t,{status:i,highestBid:e,currentBid:n,bidCount:a}){return{highestBid:e,currentBid:n,bidCount:a,closeAt:this.getAuctionCloseAt(t),settleAt:t.settleAt,status:i,currencyCode:t.currencyCode,minNextBid:e?this.computeMinNextBid(e,t.minBidIncrement):void 0,bidIncrement:t.minBidIncrement??void 0}}emitTransitionEvents(t){for(const i of t)this.events.emit(i.type,i.payload)}applyTransition(t,i){i.trackedParticipation?this.trackedParticipations.set(t,i.trackedParticipation):this.trackedParticipations.delete(t);const e=this.monitorRuntime.getDisplayAtom(t);e&&i.displayState&&e.set({type:"auction",...i.displayState}),this.emitTransitionEvents(i.events),i.monitorUpdate&&this.monitorRuntime.notify(t,i.monitorUpdate),i.stopWatching&&this.stopWatching(t),i.autoRebidStatus&&this.handleAutoRebid(t,i.autoRebidStatus)}checkOutbidConditions(t,i,e){if("winning"===t.status&&"outbid"===i.status)return!0;if(e?.currentBid&&i.highestBid){const n=e.currentBid,a=i.highestBid,o=t.highestBid||"0";if(s(n)&&s(a)&&s(o))return r(a,o)&&r(a,n)&&"winning"!==i.status}return"outbid"!==t.status&&"outbid"===i.status}buildBidTransition(t,i,e,n,a){const s="winning"===i.status?"winning":"outbid"===i.status?"outbid":"bidding";return{trackedParticipation:{auctionId:t,enteredAt:a?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:s,currentBid:e,highestBid:i.highestBid,bidCount:i.bidCount,lastBidAt:/* @__PURE__ */(new Date).toISOString(),metadata:a?.metadata,fingerprint:a?.fingerprint},displayState:this.buildDisplayState(n,{status:i.status,highestBid:i.highestBid,currentBid:e,bidCount:i.bidCount}),events:[{type:"auction:bid-placed",payload:{auctionId:t,amount:e,status:i.status}},..."winning"===i.status?[{type:"auction:winning",payload:{auctionId:t,amount:e,highestBid:i.highestBid}}]:[],..."outbid"===i.status?[{type:"auction:outbid",payload:{auctionId:t,yourBid:e,highestBid:i.highestBid}}]:[]],monitorUpdate:null,stopWatching:!1,autoRebidStatus:null}}buildStatusTransition(t,i,e,{previousParticipation:n,previousStatus:a}){const s=i.currentBid??n?.currentBid,o=i.bidCount??n?.bidCount;if("won"===i.terminal)return{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:"won",highestBid:i.highestBid,currentBid:s,bidCount:o}),events:[{type:"auction:won",payload:{auctionId:t,winningBid:s??""}}],monitorUpdate:i.admissionGrant?{type:"granted",token:i.admissionGrant}:{type:"ended",outcome:{type:"won",distributionType:"auction",distributionId:t}},stopWatching:!0,autoRebidStatus:null};if("lost"===i.terminal)return{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:"lost",highestBid:i.highestBid,currentBid:s,bidCount:o}),events:[{type:"auction:lost",payload:{auctionId:t,highestBid:i.highestBid}}],monitorUpdate:{type:"ended",outcome:{type:"lost"}},stopWatching:!0,autoRebidStatus:null};const r=e.closeAt?new Date(e.closeAt).getTime():void 0;if("watching"===i.status&&void 0!==r&&Date.now()>=r+this.NO_BID_END_GRACE_MS)return{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:"ended",highestBid:i.highestBid,currentBid:s,bidCount:o}),events:[],monitorUpdate:{type:"ended",outcome:{type:"closed",reason:"ended"}},stopWatching:!0,autoRebidStatus:null};const u={auctionId:t,enteredAt:n?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:i.status,currentBid:s,highestBid:i.highestBid,bidCount:o,metadata:n?.metadata,fingerprint:n?.fingerprint},d=[];let c=null;return a&&(this.checkOutbidConditions(a,i,n)&&(d.push({type:"auction:outbid",payload:{auctionId:t,yourBid:a.currentBid||n?.currentBid||"",highestBid:i.highestBid}}),c=i),"winning"!==a.status&&"winning"===i.status&&d.push({type:"auction:winning",payload:{auctionId:t,amount:s||"",highestBid:i.highestBid}}),a.highestBid!==i.highestBid&&"winning"!==i.status&&d.push({type:"auction:bid-updated",payload:{auctionId:t,highestBid:i.highestBid,bidCount:i.bidCount||0}})),{trackedParticipation:u,displayState:this.buildDisplayState(e,{status:i.status,highestBid:i.highestBid,currentBid:s,bidCount:o}),events:d,monitorUpdate:null,stopWatching:!1,autoRebidStatus:c}}buildMissingTransition(){return{trackedParticipation:null,displayState:{status:"lost"},events:[],monitorUpdate:{type:"ended",outcome:{type:"closed",reason:"not_participating"}},stopWatching:!0,autoRebidStatus:null}}async get(t){try{return await this.getAuctionDetailsCached(t)}catch(i){throw this.logger.error("Failed to get auction",{auctionId:t,error:i}),i}}async bid(e,a,o){try{const r=this.lifecycleGeneration;if(!s(a))throw n.validationError("Invalid bid amount format. Must be a numeric string (e.g., '100.00')");if(!this.store.session)throw n.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");let d=this.bumpOperationGeneration(e),c=this.getTrackedParticipation(e);c||(this.logger.warn("Attempting to bid without entering auction first",{auctionId:e}),await this.enter(e,void 0,o),c=this.getTrackedParticipation(e),d=this.bumpOperationGeneration(e)),this.logger.info("Placing bid",{auctionId:e,amount:a});const l=`/auctions/${e}/bid`,g=`/auctions/${e}/highest-bid`,[p,m,b]=await Promise.all([this.http.post(l,{amount:a},o),this.http.get(g),this.getAuctionDetailsCached(e)]),y=h(t,p,{endpoint:l},{throwOnFailure:!0});if(!y.ok)throw y.error;const f=h(i,m,{endpoint:g},{throwOnFailure:!0});if(!f.ok)throw f.error;const I=y.value,B=f.value,w=u(B.amount),v={status:"WINNING"===I.status||"WON"===I.status?"winning":"OUTBID"===I.status||"BIDDING"===I.status||"LOST"===I.status?"outbid":"accepted",amount:a,highestBid:w,bidCount:I.bidCount??(c?.bidCount||0)+1};return this.canApplyAsyncResult(e,r,void 0,d)?(this.applyTransition(e,this.buildBidTransition(e,v,a,b,c)),v):v}catch(r){throw this.logger.error("Failed to place bid",{auctionId:e,amount:a,error:r}),this.events.emit("auction:error",{auctionId:e,error:r}),r}}async enter(t,i,e){try{const a=this.lifecycleGeneration,s=this.store.session;if(!s)throw n.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const o=this.bumpOperationGeneration(t);if(this.logger.info("Entering auction",{auctionId:t}),await this.http.post(`/auctions/${t}/enter`,{metadata:i},e),!this.canApplyAsyncResult(t,a,void 0,o))return;this.trackedParticipations.set(t,{auctionId:t,enteredAt:/* @__PURE__ */(new Date).toISOString(),status:"watching",metadata:i,fingerprint:s.deviceFingerprint}),this.emitTransitionEvents([{type:"auction:entered",payload:{auctionId:t}}])}catch(a){throw this.logger.error("Failed to enter auction",{auctionId:t,error:a}),this.events.emit("auction:error",{auctionId:t,error:a}),a}}async leave(t,i){try{const n=this.lifecycleGeneration,a=this.bumpOperationGeneration(t);this.logger.info("Leaving auction",{auctionId:t});try{await this.http.post(`/auctions/${t}/leave`,void 0,i)}catch(e){if(404!==e?.status)throw e;this.logger.debug("Leave endpoint not available",{auctionId:t})}if(!this.canApplyAsyncResult(t,n,void 0,a))return;this.stopMonitoring(t),this.trackedParticipations.delete(t),this.emitTransitionEvents([{type:"auction:left",payload:{auctionId:t}}])}catch(e){throw this.logger.error("Failed to leave auction",{auctionId:t,error:e}),this.events.emit("auction:error",{auctionId:t,error:e}),e}}async status(t){const i=this.inFlightRequests.get(t);if(i)return this.logger.debug("Returning existing in-flight request",{auctionId:t}),i;const e=this.lifecycleGeneration,n=this.bumpOperationGeneration(t),a=this.doStatus(t,e,void 0,n).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,a),a}async doStatus(e,n,a,s){try{const o=`/auctions/${e}/status`,r=`/auctions/${e}/highest-bid`,[d,c,l]=await Promise.all([this.http.get(o),this.http.get(r),this.getAuctionDetailsCached(e)]),g=h(t,d,{endpoint:o},{throwOnFailure:!0});if(!g.ok)throw g.error;const p=h(i,c,{endpoint:r},{throwOnFailure:!0});if(!p.ok)throw p.error;const m=g.value,b=p.value,y=this.getAuctionSettleAt(l),f=y?new Date(y).getTime():Number.NaN,I=u(b.amount),B=this.getTrackedParticipation(e),w=m.lastBidAmount??m.winningBidAmount;let v="watching";"WINNING"===m.status||"WON"===m.status?v="winning":"OUTBID"!==m.status&&"LOST"!==m.status&&"BIDDING"!==m.status||(v="outbid");const A="WON"===m.status?"won":"LOST"===m.status?"lost":void 0,C={auctionId:e,status:v,...A?{terminal:A}:{},currentBid:B?.currentBid??(w?u(w):void 0),highestBid:I,timeRemaining:Number.isFinite(f)?Math.max(0,f-Date.now()):0,bidCount:m.bidCount,admissionGrant:m.admissionGrant};if(!this.canApplyAsyncResult(e,n,a,s))return C;const S=this.buildStatusTransition(e,C,l,{previousParticipation:B,previousStatus:this.lastStatusCache.get(e)});return this.applyTransition(e,S),S.stopWatching?this.lastStatusCache.delete(e):this.lastStatusCache.set(e,C),this.events.emit("auction:status-updated",{auctionId:e,status:C}),C}catch(o){if(o&&"object"==typeof o&&"status"in o&&404===o.status){const t={auctionId:e,status:"watching",highestBid:"0.00",timeRemaining:0};if(!this.canApplyAsyncResult(e,n,a,s))return t;const i=this.buildMissingTransition();return this.applyTransition(e,i),this.lastStatusCache.delete(e),this.events.emit("auction:status-updated",{auctionId:e,status:t}),t}if(!this.canApplyAsyncResult(e,n,a,s))return this.lastStatusCache.get(e)??{auctionId:e,status:"watching",highestBid:"0.00",timeRemaining:0};throw this.logger.error("Failed to get auction status",{auctionId:e,error:o}),this.events.emit("auction:error",{auctionId:e,error:o}),o}}async getBidHistory(t){try{const i=this.bidHistoryCache.get(t);if(i&&Date.now()-i.fetchedAt<this.BID_HISTORY_CACHE_TTL)return this.logger.debug("Returning cached bid history",{auctionId:t}),i.bids;this.logger.info("Fetching bid history",{auctionId:t});const n=`/auctions/${t}/bids/history`,a=await this.http.get(n),s=h(e,a,{endpoint:n},{throwOnFailure:!0});if(!s.ok)throw s.error;const o=s.value.map(t=>({amount:u(t.amount),timestamp:t.timestamp,isWinning:t.isHighest,isYours:!0}));return this.bidHistoryCache.set(t,{bids:o,fetchedAt:Date.now()}),this.events.emit("auction:history-fetched",{auctionId:t,bidCount:o.length}),o}catch(i){throw this.logger.error("Failed to get bid history",{auctionId:t,error:i}),this.events.emit("auction:error",{auctionId:t,error:i}),i}}startWatching(t,i=5e3){this.stopWatching(t);const e=this.bumpMonitorGeneration(t);this.logger.info("Starting auction status polling",{auctionId:t,intervalMs:i});const n=this.getOperationGeneration(t);this.doStatus(t,this.lifecycleGeneration,e,n).catch(i=>{this.canApplyAsyncResult(t,this.lifecycleGeneration,e,n)&&this.logger.error("Initial status check failed",{auctionId:t,error:i})});const a=setInterval(()=>{const i=this.getOperationGeneration(t);this.doStatus(t,this.lifecycleGeneration,e,i).catch(n=>{this.canApplyAsyncResult(t,this.lifecycleGeneration,e,i)&&this.logger.error("Polling failed",{auctionId:t,error:n})})},i);this.pollingIntervals.set(t,a),this.events.emit("auction:watching-started",{auctionId:t,intervalMs:i})}stopWatching(t){this.bumpMonitorGeneration(t);const i=this.pollingIntervals.get(t);i&&(clearInterval(i),this.pollingIntervals.delete(t),this.lastStatusCache.delete(t),this.logger.info("Stopped watching auction",{auctionId:t}),this.events.emit("auction:watching-stopped",{auctionId:t}))}stopAllWatching(){for(const t of Array.from(this.pollingIntervals.keys()))this.stopWatching(t)}bumpMonitorGeneration(t){const i=(this.monitorGenerations.get(t)??0)+1;return this.monitorGenerations.set(t,i),i}getOperationGeneration(t){return this.operationGenerations.get(t)??0}bumpOperationGeneration(t){const i=this.getOperationGeneration(t)+1;return this.operationGenerations.set(t,i),i}canApplyAsyncResult(t,i,e,n){return!this.destroyed&&this.lifecycleGeneration===i&&((void 0===e||this.monitorGenerations.get(t)===e)&&(void 0===n||this.getOperationGeneration(t)===n))}enableAutoRebid(t,i,e){if(!s(i)||!s(e))throw n.validationError("Invalid monetary format. Must be numeric strings (e.g., '100.00')");if(d(i,"0")||d(e,"0"))throw n.validationError("Max bid and increment must be positive");if(r(e,i))throw n.validationError("Increment cannot be greater than max bid");const a=this.getTrackedParticipation(t);if(!a)throw n.validationError("Must enter auction before enabling auto-rebid");const o=u(a.currentBid),h=c(i,o);this.autoRebidConfigs.set(t,{enabled:!0,maxBid:i,increment:e,remainingBudget:h,rebidCount:0}),this.logger.info("Auto-rebid enabled",{auctionId:t,maxBid:i,increment:e,remainingBudget:h}),this.events.emit("auction:auto-rebid-enabled",{auctionId:t,maxBid:i,increment:e})}disableAutoRebid(t){const i=this.autoRebidConfigs.get(t);i&&(i.enabled=!1,this.logger.info("Auto-rebid disabled",{auctionId:t}),this.events.emit("auction:auto-rebid-disabled",{auctionId:t,finalRebidCount:i.rebidCount||0}))}getAutoRebidConfig(t){return this.autoRebidConfigs.get(t)}async handleAutoRebid(t,i){const e=this.autoRebidConfigs.get(t);if(e&&e.enabled)try{if(!s(i.highestBid)||!s(e.maxBid)||!s(e.increment))return void this.logger.error("Invalid auto-rebid amounts",{auctionId:t,config:e});const n=o(i.highestBid,e.increment);if(r(n,e.maxBid))return this.logger.info("Auto-rebid would exceed max bid, disabling",{auctionId:t,wouldBid:n,maxBid:e.maxBid}),this.disableAutoRebid(t),void this.events.emit("auction:auto-rebid-max-reached",{auctionId:t,maxBid:e.maxBid,wouldNeedToBid:n});this.logger.info("Placing auto-rebid",{auctionId:t,amount:n});const a=await this.bid(t,n);e.lastRebidAt=/* @__PURE__ */(new Date).toISOString(),e.rebidCount=(e.rebidCount||0)+1,e.remainingBudget=c(e.maxBid,n),this.events.emit("auction:auto-rebid-placed",{auctionId:t,amount:n,remainingBudget:e.remainingBudget,status:a.status})}catch(n){this.logger.error("Auto-rebid failed",{auctionId:t,error:n}),this.events.emit("auction:auto-rebid-failed",{auctionId:t,error:n})}}startMonitoring(t,i,e){this.monitorRuntime.start(t,i,e),this.trackedParticipations.has(t)||this.trackedParticipations.set(t,{auctionId:t,enteredAt:/* @__PURE__ */(new Date).toISOString(),status:"watching"}),this.startWatching(t)}stopMonitoring(t){this.stopWatching(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)||this.pollingIntervals.has(t)}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.stopAllWatching(),this.inFlightRequests.clear(),this.lastStatusCache.clear(),this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.bidHistoryCache.clear(),this.auctionDetailsCache.clear(),this.autoRebidConfigs.clear(),this.monitorRuntime.clear()}}export{m as AuctionManagementModule};
@@ -40,6 +40,12 @@ export interface BidResult {
40
40
  export interface AuctionStatus {
41
41
  auctionId: string;
42
42
  status: "winning" | "outbid" | "watching";
43
+ /**
44
+ * The server-resolved terminal outcome for this bidder, or absent while the auction is
45
+ * still in progress. The watch loop stops and finalizes on this alone — independent of the
46
+ * client clock and the admission grant.
47
+ */
48
+ terminal?: "won" | "lost";
43
49
  currentBid?: string;
44
50
  highestBid: string;
45
51
  timeRemaining: number;
@@ -8,7 +8,17 @@ export declare class AuthenticationModule implements AuthModule {
8
8
  private events;
9
9
  private refreshTimer?;
10
10
  private refreshPromise?;
11
+ private authChannel?;
12
+ private lastBroadcastTokens;
11
13
  constructor(http: HttpClient, beaconHttp: HttpClient);
14
+ /**
15
+ * Subscribe to the cross-tab auth channel so this tab caches the bearer a refresh
16
+ * winner in another tab broadcasts. Best-effort: when BroadcastChannel is
17
+ * unavailable a queued caller falls back to the reactive-401 path instead.
18
+ */
19
+ private setupTokenChannel;
20
+ /** Hand the freshly-minted bearer to other tabs over the ephemeral auth channel. */
21
+ private broadcastSessionTokens;
12
22
  /**
13
23
  * Check current authentication status
14
24
  */
@@ -41,7 +51,11 @@ export declare class AuthenticationModule implements AuthModule {
41
51
  */
42
52
  logout(): Promise<void>;
43
53
  /**
44
- * Clear all local auth state: store, access-token header, and beacon-token header.
54
+ * Clear all local auth state: store, access-token header, beacon-token header,
55
+ * and the durably-persisted refresh token. Clearing the refresh token ensures a
56
+ * forced re-auth does not leave a stale token that a reload or another tab would
57
+ * present; the access/beacon bearer is memory-only, so there is nothing to clear
58
+ * from durable storage — only the in-memory broadcast cache.
45
59
  */
46
60
  private clearAuthState;
47
61
  /**
@@ -49,11 +63,77 @@ export declare class AuthenticationModule implements AuthModule {
49
63
  */
50
64
  getSession(): Session | null;
51
65
  /**
52
- * Refresh the access token
66
+ * Rehydrate the durable refresh token after a reload and restore a working
67
+ * bearer. The session is rehydrated from localStorage by the store; the refresh
68
+ * token lives in IndexedDB (never localStorage), so it is restored here. The
69
+ * access token is intentionally NOT persisted, so after a reload there is no
70
+ * bearer on the fresh HTTP client — and scheduling alone would leave the FIRST
71
+ * authenticated call tokenless until the timer fires minutes later (a 401 that
72
+ * can spuriously log the user out). So when a refresh token is restored but no
73
+ * bearer is installed, refresh eagerly here (awaited before init returns the SDK,
74
+ * so no race with the first call) to mint and install an access token. The
75
+ * eager refresh reuses the single-flight Web Lock and publishes cross-tab. A
76
+ * no-op when no token is persisted; in steady-state (bearer already present) it
77
+ * only resumes proactive scheduling. A failed eager refresh is swallowed: the
78
+ * reactive 401 path still re-drives re-authentication.
79
+ */
80
+ rehydrate(): Promise<void>;
81
+ /** Whether the HTTP client currently carries an `Authorization` bearer header. */
82
+ private hasBearer;
83
+ /**
84
+ * Refresh the access token.
85
+ *
86
+ * Single-flight is enforced cross-tab via `navigator.locks` when available:
87
+ * the rotated refresh token is single-use, so two callers that race would
88
+ * otherwise consume each other's token. The token observed BEFORE acquiring the
89
+ * lock is the "entry token"; once inside the lock `_doRefresh` re-reads
90
+ * IndexedDB and — if the token already changed (a concurrent caller rotated it)
91
+ * — skips the POST and adopts the now-current session instead of POSTing a
92
+ * spent token (which would 401). When `navigator.locks` is unavailable, fall
93
+ * back to the in-process `refreshPromise` (same-tab dedupe only).
53
94
  */
54
95
  refresh(): Promise<void>;
55
96
  /**
56
- * Internal refresh implementation
97
+ * The absolute URL a DPoP `htu` is signed against for an auth endpoint. Built
98
+ * the same way the HTTP client resolves request URLs (`new URL(path, baseUrl)`),
99
+ * so an absolute path discards the base pathname — matching the htu the server
100
+ * reconstructs from its public base URL.
101
+ */
102
+ private absoluteAuthUrl;
103
+ /**
104
+ * Build best-effort DPoP proof headers for a session-minting POST (guest /
105
+ * otp-verify / external-exchange). Unlike `/auth/refresh`, these are best-effort
106
+ * on both ends: when a proof is built the server binds the session to the
107
+ * verified key (so a later refresh works); when it cannot (no crypto / non-secure
108
+ * context) the headers are simply omitted and the server mints an unbound
109
+ * session (back-compat). Returns `{}` when no proof is available.
110
+ */
111
+ private sessionMintProofHeaders;
112
+ /**
113
+ * Set the refresh token in the in-memory store AND mirror it to its durable
114
+ * IndexedDB home (or clear that home on null), so a reload or another tab can
115
+ * refresh. IndexedDB is best-effort: a failed persist still leaves the
116
+ * in-memory token usable for the current session (Stage-0-like behavior).
117
+ */
118
+ private setRefreshTokenDurable;
119
+ /**
120
+ * Apply a successful refresh's bearer material to this tab: install the new
121
+ * access token on the HTTP client, the new beacon token on the beacon client +
122
+ * store, resume proactive scheduling, and emit `auth:refreshed`. Shared by the
123
+ * tab that performed the refresh and by a queued cross-tab caller adopting the
124
+ * winner's published tokens, so both end in the identical post-refresh state.
125
+ */
126
+ private applySessionTokens;
127
+ /**
128
+ * Internal refresh implementation. The refresh token is durably persisted in
129
+ * IndexedDB, so it survives a reload. The token is re-read from IndexedDB here
130
+ * (inside the cross-tab lock) because another caller may have rotated it; the
131
+ * in-memory store is reconciled to whatever IndexedDB holds.
132
+ *
133
+ * `entryToken` is the token observed before the lock was acquired (locked path
134
+ * only). If IndexedDB now holds a DIFFERENT token, a concurrent caller already
135
+ * refreshed while this one queued — so adopt the rotated session and skip the
136
+ * POST rather than spend a single-use token that is already consumed.
57
137
  */
58
138
  private _doRefresh;
59
139
  /**
@@ -62,10 +142,10 @@ export declare class AuthenticationModule implements AuthModule {
62
142
  private handleUnauthorized;
63
143
  /**
64
144
  * Schedule a proactive token refresh ahead of expiry so an active session
65
- * rotates before a request hits a 401. Scheduled only while a refresh token is
66
- * held in memory: after a reload the in-memory token is gone, so there is
67
- * nothing to present and refresh is driven reactively (a 401 drives
68
- * re-authentication) rather than by a timer that could only clear auth state.
145
+ * rotates before a request hits a 401. Scheduled whenever a refresh token is
146
+ * available: the token is durably persisted in IndexedDB and rehydrated into the
147
+ * store on init, so after a reload there IS a token to present and scheduling
148
+ * resumes restoring seamless background rotation.
69
149
  */
70
150
  private scheduleTokenRefresh;
71
151
  /**
@@ -1 +1 @@
1
- import{getLogger as e}from"../core/logger.js";import{getEventBus as t}from"../state/events.js";import{getSDKStore as s}from"../state/store.js";class r{constructor(r,o){this.logger=e(),this.store=s(),this.events=t(),this.http=r,this.beaconHttp=o,this.http.updateConfig({...this.http.getConfig(),onUnauthorized:async()=>{await this.handleUnauthorized()}});const i=this.store.beaconToken;i&&this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":i}})}check(){const e=this.store.session;return e?{isAuthenticated:!0,session:e,isGuest:"guest"===e.type}:{isAuthenticated:!1}}async guest(){try{this.logger.info("Creating guest session");const{session:e,accessToken:t,refreshToken:s,beaconToken:r}=await this.http.post("/auth/guest");return this.store.setSession(e),this.store.setRefreshToken(s??null),this.store.setBeaconToken(r),this.http.updateConfig({...this.http.getConfig(),headers:{...this.http.getConfig().headers,Authorization:`Bearer ${t}`}}),this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":r}}),this.scheduleTokenRefresh(e.expiresAt),this.events.emit("auth:authenticated",{session:e,isNew:!0}),e}catch(e){const t=e instanceof Error?e:new Error(String(e));throw this.logger.error("Failed to create guest session",t),this.events.emit("auth:error",{error:t,context:"guest"}),t}}async requestOtp(e){try{this.logger.info("Requesting OTP");const t="string"==typeof e?{email:e}:e;await this.http.post("/auth/otp/request",t)}catch(t){const e=t instanceof Error?t:new Error(String(t));throw this.logger.error("Failed to request OTP",e),this.events.emit("auth:error",{error:e,context:"requestOtp"}),e}}async verifyOtp(e){try{const t=e;this.logger.info("Verifying OTP",{email:t.email,phone:t.phone});const{session:s,accessToken:r,refreshToken:o,beaconToken:i}=await this.http.post("/auth/otp/verify",t);return this.store.setSession(s),this.store.setRefreshToken(o??null),this.store.setBeaconToken(i),this.http.updateConfig({...this.http.getConfig(),headers:{...this.http.getConfig().headers,Authorization:`Bearer ${r}`}}),this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":i}}),this.scheduleTokenRefresh(s.expiresAt),this.events.emit("auth:authenticated",{session:s,isNew:!0}),s}catch(t){const e=t instanceof Error?t:new Error(String(t));throw this.logger.error("Failed to verify OTP",e),this.events.emit("auth:error",{error:e,context:"verifyOtp"}),e}}async exchangeExternal(e){try{const t="string"==typeof e?{exchangeCode:e}:e;this.logger.info("Exchanging external auth code");const{session:s,accessToken:r,refreshToken:o,beaconToken:i}=await this.http.post("/auth/external/exchange",t);return this.store.setSession(s),this.store.setRefreshToken(o??null),this.store.setBeaconToken(i),this.http.updateConfig({...this.http.getConfig(),headers:{...this.http.getConfig().headers,Authorization:`Bearer ${r}`}}),this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":i}}),this.scheduleTokenRefresh(s.expiresAt),this.events.emit("auth:authenticated",{session:s,isNew:!0}),s}catch(t){const e=t instanceof Error?t:new Error(String(t));throw this.logger.error("Failed to exchange external auth code",e),this.events.emit("auth:error",{error:e,context:"exchangeExternal"}),e}}async login(e){await this.requestOtp({email:e.email})}async logout(){try{if(!this.store.session)return;const e=this.store.refreshToken;await this.http.post("/auth/logout",e?{refreshToken:e}:{},{skipUnauthorizedHandler:!0}).catch(()=>{}),this.clearAuthState(),this.events.emit("auth:logout",{reason:"user"})}catch(e){const t=e instanceof Error?e:new Error(String(e));throw this.logger.error("Error during logout",t),this.store.clearAll(),t}}clearAuthState(){this.cancelTokenRefresh(),this.store.clearAll();const e=this.http.getConfig(),{Authorization:t,...s}=e.headers||{};this.http.updateConfig({...e,headers:s});const r=this.beaconHttp.getConfig(),{"X-Beacon-Token":o,...i}=r.headers||{};this.beaconHttp.updateConfig({...r,headers:i})}getSession(){return this.store.session}async refresh(){return this.refreshPromise?(this.logger.debug("Refresh already in progress, returning existing promise"),this.refreshPromise):(this.refreshPromise=this._doRefresh().finally(()=>{this.refreshPromise=void 0}),this.refreshPromise)}async _doRefresh(){const e=this.store.refreshToken;if(!e)throw this.logger.info("No refresh token available; clearing auth state"),this.clearAuthState(),this.events.emit("auth:logout",{reason:"expired"}),new Error("No refresh token available");try{this.logger.info("Refreshing access token");const{accessToken:t,refreshToken:s,beaconToken:r}=await this.http.post("/auth/refresh",{refreshToken:e});this.store.setRefreshToken(s??null),this.store.setBeaconToken(r),this.http.updateConfig({...this.http.getConfig(),headers:{...this.http.getConfig().headers,Authorization:`Bearer ${t}`}}),this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":r}});const o=this.store.session;o&&o.expiresAt&&this.scheduleTokenRefresh(o.expiresAt),this.store.session&&this.events.emit("auth:refreshed",{session:this.store.session})}catch(t){const e=t instanceof Error?t:new Error(String(t));throw this.logger.error("Failed to refresh token",e),await this.logout(),e}}async handleUnauthorized(){this.logger.warn("Received 401, attempting to refresh token");try{await this.refresh()}catch(e){const t=e instanceof Error?e:new Error(String(e));this.logger.error("Failed to handle 401",t),this.events.emit("auth:error",{error:new Error("Session expired"),context:"unauthorized"})}}scheduleTokenRefresh(e){if(this.cancelTokenRefresh(),!this.store.refreshToken)return;const t=Date.now(),s=new Date(e).getTime()-3e5,r=s-t;r<=0?this.refresh().catch(e=>{this.logger.error("Failed to refresh expired token",e)}):(this.logger.debug("Scheduling token refresh",{expiresAt:new Date(e).toISOString(),refreshAt:new Date(s).toISOString(),delayMs:r}),this.refreshTimer=setTimeout(()=>{this.refresh().catch(e=>{this.logger.error("Scheduled token refresh failed",e)})},r))}cancelTokenRefresh(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=void 0,this.logger.debug("Cancelled token refresh timer"))}destroy(){this.cancelTokenRefresh(),this.refreshPromise=void 0}}export{r as AuthenticationModule};
1
+ import{FanfareError as e}from"../core/errors.js";import{getLogger as t}from"../core/logger.js";import{loadRefreshToken as s,clearRefreshToken as r,clearLegacySessionTokens as o,buildAdmissionProofHeaders as i,persistRefreshToken as n}from"../security/admission-proof.js";import{getEventBus as a}from"../state/events.js";import{getSDKStore as h}from"../state/store.js";import{BroadcastChannelTransport as c}from"../sync/broadcast-transport.js";function g(e){const t={};for(const[s,r]of Object.entries(e))"string"==typeof r&&(t[s]=r);return t}class f{constructor(e,s){this.logger=t(),this.store=h(),this.events=a(),this.lastBroadcastTokens=null,this.http=e,this.beaconHttp=s,this.setupTokenChannel(),this.http.updateConfig({...this.http.getConfig(),onUnauthorized:async()=>{await this.handleUnauthorized()}});const r=this.store.beaconToken;r&&this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":r}})}setupTokenChannel(){const e=new c("fanfare-auth-tokens");e.isAvailable()&&(this.authChannel=e,e.onMessage(e=>{const t=e.data;t&&"string"==typeof t.accessToken&&"string"==typeof t.beaconToken&&"string"==typeof t.refreshToken&&(this.lastBroadcastTokens={accessToken:t.accessToken,beaconToken:t.beaconToken,refreshToken:t.refreshToken})}))}broadcastSessionTokens(e){this.authChannel?.send(e)}check(){const e=this.store.session;return e?{isAuthenticated:!0,session:e,isGuest:"guest"===e.type}:{isAuthenticated:!1}}async guest(){try{this.logger.info("Creating guest session");const e=await this.sessionMintProofHeaders("/auth/guest"),{session:t,accessToken:s,refreshToken:r,beaconToken:o}=await this.http.post("/auth/guest",void 0,{headers:e});return this.store.setSession(t),await this.setRefreshTokenDurable(r??null),this.store.setBeaconToken(o),this.http.updateConfig({...this.http.getConfig(),headers:{...this.http.getConfig().headers,Authorization:`Bearer ${s}`}}),this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":o}}),this.scheduleTokenRefresh(t.expiresAt),this.events.emit("auth:authenticated",{session:t,isNew:!0}),t}catch(e){const t=e instanceof Error?e:new Error(String(e));throw this.logger.error("Failed to create guest session",t),this.events.emit("auth:error",{error:t,context:"guest"}),t}}async requestOtp(e){try{this.logger.info("Requesting OTP");const t="string"==typeof e?{email:e}:e;await this.http.post("/auth/otp/request",t)}catch(t){const e=t instanceof Error?t:new Error(String(t));throw this.logger.error("Failed to request OTP",e),this.events.emit("auth:error",{error:e,context:"requestOtp"}),e}}async verifyOtp(e){try{const t=e;this.logger.info("Verifying OTP",{email:t.email,phone:t.phone});const s=await this.sessionMintProofHeaders("/auth/otp/verify"),{session:r,accessToken:o,refreshToken:i,beaconToken:n}=await this.http.post("/auth/otp/verify",t,{headers:s});return this.store.setSession(r),await this.setRefreshTokenDurable(i??null),this.store.setBeaconToken(n),this.http.updateConfig({...this.http.getConfig(),headers:{...this.http.getConfig().headers,Authorization:`Bearer ${o}`}}),this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":n}}),this.scheduleTokenRefresh(r.expiresAt),this.events.emit("auth:authenticated",{session:r,isNew:!0}),r}catch(t){const e=t instanceof Error?t:new Error(String(t));throw this.logger.error("Failed to verify OTP",e),this.events.emit("auth:error",{error:e,context:"verifyOtp"}),e}}async exchangeExternal(e){try{const t="string"==typeof e?{exchangeCode:e}:e;this.logger.info("Exchanging external auth code");const s=await this.sessionMintProofHeaders("/auth/external/exchange"),{session:r,accessToken:o,refreshToken:i,beaconToken:n}=await this.http.post("/auth/external/exchange",t,{headers:s});return this.store.setSession(r),await this.setRefreshTokenDurable(i??null),this.store.setBeaconToken(n),this.http.updateConfig({...this.http.getConfig(),headers:{...this.http.getConfig().headers,Authorization:`Bearer ${o}`}}),this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":n}}),this.scheduleTokenRefresh(r.expiresAt),this.events.emit("auth:authenticated",{session:r,isNew:!0}),r}catch(t){const e=t instanceof Error?t:new Error(String(t));throw this.logger.error("Failed to exchange external auth code",e),this.events.emit("auth:error",{error:e,context:"exchangeExternal"}),e}}async login(e){await this.requestOtp({email:e.email})}async logout(){try{if(!this.store.session)return;const e=this.store.refreshToken??await s();await this.http.post("/auth/logout",e?{refreshToken:e}:{},{skipUnauthorizedHandler:!0}).catch(()=>{}),await this.clearAuthState(),this.events.emit("auth:logout",{reason:"user"})}catch(e){const t=e instanceof Error?e:new Error(String(e));throw this.logger.error("Error during logout",t),this.store.clearAll(),this.lastBroadcastTokens=null,await r(),t}}async clearAuthState(){this.cancelTokenRefresh(),this.store.clearAll(),this.lastBroadcastTokens=null,await r();const e=this.http.getConfig(),{Authorization:t,...s}=e.headers||{};this.http.updateConfig({...e,headers:s});const o=this.beaconHttp.getConfig(),{"X-Beacon-Token":i,...n}=o.headers||{};this.beaconHttp.updateConfig({...o,headers:n})}getSession(){return this.store.session}async rehydrate(){const e=await s();if(o(),!e)return;if(this.store.setRefreshToken(e),!this.hasBearer()){try{await this.refresh()}catch(r){this.logger.debug("Eager rehydrate refresh failed; falling back to reactive 401",{error:r instanceof Error?r.message:String(r)})}return}const t=this.store.session;t?.expiresAt&&this.scheduleTokenRefresh(t.expiresAt)}hasBearer(){return Boolean(this.http.getConfig().headers?.Authorization)}async refresh(){if("undefined"!=typeof navigator&&"locks"in navigator){const e=await s(),t=e??this.store.refreshToken;return navigator.locks.request("fanfare-refresh",()=>this._doRefresh(t,e))}return this.refreshPromise?(this.logger.debug("Refresh already in progress, returning existing promise"),this.refreshPromise):(this.refreshPromise=this._doRefresh().finally(()=>{this.refreshPromise=void 0}),this.refreshPromise)}absoluteAuthUrl(e){return new URL(e,this.http.getConfig().baseUrl).toString()}async sessionMintProofHeaders(e){const t=await i({url:this.absoluteAuthUrl(e),method:"POST"});return t?g(t):{}}async setRefreshTokenDurable(e){this.store.setRefreshToken(e),e?await n(e):await r()}applySessionTokens(e){this.store.setBeaconToken(e.beaconToken),this.http.updateConfig({...this.http.getConfig(),headers:{...this.http.getConfig().headers,Authorization:`Bearer ${e.accessToken}`}}),this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":e.beaconToken}});const t=this.store.session;t?.expiresAt&&this.scheduleTokenRefresh(t.expiresAt),this.store.session&&this.events.emit("auth:refreshed",{session:this.store.session})}async _doRefresh(t,r){const o=await s(),n=o??this.store.refreshToken;if(null!==o&&o!==this.store.refreshToken&&this.store.setRefreshToken(o),!n)throw this.logger.info("No refresh token available; clearing auth state"),await this.clearAuthState(),this.events.emit("auth:logout",{reason:"expired"}),new Error("No refresh token available");if(r&&null===o)throw this.logger.debug("Durable refresh token cleared by a concurrent caller; skipping re-POST"),await this.clearAuthState(),this.events.emit("auth:logout",{reason:"expired"}),new Error("Refresh token cleared by a concurrent caller");if(void 0!==t&&null!==o&&o!==t){this.logger.debug("Refresh already completed by a concurrent caller; adopting rotated session");const e=this.lastBroadcastTokens;return void(e&&e.refreshToken===o?this.applySessionTokens(e):this.store.session&&this.events.emit("auth:refreshed",{session:this.store.session}))}const a=await i({url:this.absoluteAuthUrl("/auth/refresh"),method:"POST"});if(!a)throw this.logger.info("No DPoP proof available for refresh; clearing auth state"),await this.clearAuthState(),this.events.emit("auth:logout",{reason:"expired"}),new Error("DPoP proof unavailable for refresh");try{this.logger.info("Refreshing access token");const{accessToken:e,refreshToken:t,beaconToken:s,accessTokenExpiresAt:r}=await this.http.post("/auth/refresh",{refreshToken:n},{headers:g(a),skipUnauthorizedHandler:!0});await this.setRefreshTokenDurable(t??null),t&&this.broadcastSessionTokens({accessToken:e,beaconToken:s,refreshToken:t}),r&&this.store.updateSessionExpiry(r),this.applySessionTokens({accessToken:e,beaconToken:s})}catch(h){const t=h instanceof Error?h:new Error(String(h)),s=h instanceof e?h.status:void 0;throw 401===s?(this.logger.error("Refresh rejected as unauthorized; clearing auth state",t),await this.clearAuthState(),this.events.emit("auth:logout",{reason:"expired"})):this.logger.warn("Refresh failed transiently; preserving durable refresh token",{status:s,error:t.message}),t}}async handleUnauthorized(){this.logger.warn("Received 401, attempting to refresh token");try{await this.refresh()}catch(e){const t=e instanceof Error?e:new Error(String(e));this.logger.error("Failed to handle 401",t),this.events.emit("auth:error",{error:new Error("Session expired"),context:"unauthorized"})}}scheduleTokenRefresh(e){if(this.cancelTokenRefresh(),!this.store.refreshToken)return;const t=Date.now(),s=new Date(e).getTime(),r=Number.isFinite(s),o=r?Math.max(s-3e5-t,3e4):3e4;this.logger.debug("Scheduling token refresh",{expiresAt:r?new Date(s).toISOString():"invalid",delayMs:o}),this.refreshTimer=setTimeout(()=>{this.refresh().catch(e=>{this.logger.error("Scheduled token refresh failed",e)})},o)}cancelTokenRefresh(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=void 0,this.logger.debug("Cancelled token refresh timer"))}destroy(){this.cancelTokenRefresh(),this.refreshPromise=void 0,this.authChannel?.close(),this.authChannel=void 0}}export{f as AuthenticationModule};
@@ -90,7 +90,6 @@ export interface Session {
90
90
  */
91
91
  export interface GuestSession extends Session {
92
92
  type: "guest";
93
- guestId: string;
94
93
  email: undefined;
95
94
  }
96
95
  /**
@@ -1 +1 @@
1
- import{createError as i}from"../core/errors.js";const n={production:"https://consumer.fanfare.io/api",development:"http://localhost:4802"},o={production:"https://beacon.fanfare.io",development:"http://localhost:4803"},t=/* @__PURE__ */new Set(["https://consumer.fanfare.io"]),e=/* @__PURE__ */new Set(["https://beacon.fanfare.io"]),r={environment:"production",auth:{persistSession:!0,sessionDuration:3600},logging:{level:"error"}};class a{constructor(n){if(this.config={organizationId:n.organizationId,publishableKey:n.publishableKey,environment:n.environment??r.environment??"production",apiUrl:n.apiUrl,credentials:n.credentials??"include",debug:n.debug??!1,auth:{...r.auth,...n.auth},logging:{...r.logging,...n.logging},sync:n.sync,beacon:n.beacon,features:{fingerprinting:n.features?.fingerprinting??!0}},!this.config.organizationId)throw i.invalidConfig("organizationId is required");if(!this.config.publishableKey)throw i.invalidConfig("publishableKey is required");"production"===this.config.environment&&(s("apiUrl",this.apiUrl,t),s("beacon.baseUrl",this.beaconUrl,e))}get apiUrl(){return this.config.apiUrl||n[this.config.environment]}get beaconUrl(){return this.config.beacon?.baseUrl||o[this.config.environment]}get(){return this.config}getValue(i){return this.config[i]}}function s(n,o,t){let e;try{e=new URL(o)}catch{throw i.invalidConfig(`${n} must be a valid URL in production`)}if("https:"!==e.protocol)throw i.invalidConfig(`${n} must use HTTPS in production`);if(!t.has(e.origin))throw i.invalidConfig(`${n} must use a trusted Fanfare destination in production`)}export{n as API_URLS,o as BEACON_URLS,a as Config,r as DEFAULT_CONFIG};
1
+ import{createError as i}from"../core/errors.js";const n={production:"https://consumer.fanfare.io/api",development:"http://localhost:4802"},e={production:"https://beacon.fanfare.io",development:"http://localhost:4803"},o=/* @__PURE__ */new Set(["https://consumer.fanfare.io"]),t=/* @__PURE__ */new Set(["https://beacon.fanfare.io"]),r={environment:"production",auth:{persistSession:!0,sessionDuration:3600},logging:{level:"error"}};class a{constructor(n){if(this.config={organizationId:n.organizationId,publishableKey:n.publishableKey,environment:n.environment??r.environment??"production",apiUrl:n.apiUrl,credentials:n.credentials??"include",debug:n.debug??!1,autoResume:n.autoResume??!0,auth:{...r.auth,...n.auth},logging:{...r.logging,...n.logging},sync:n.sync,beacon:n.beacon,features:{fingerprinting:n.features?.fingerprinting??!0}},!this.config.organizationId)throw i.invalidConfig("organizationId is required");if(!this.config.publishableKey)throw i.invalidConfig("publishableKey is required");"production"===this.config.environment&&(s("apiUrl",this.apiUrl,o),s("beacon.baseUrl",this.beaconUrl,t))}get apiUrl(){return this.config.apiUrl||n[this.config.environment]}get beaconUrl(){return this.config.beacon?.baseUrl||e[this.config.environment]}get(){return this.config}getValue(i){return this.config[i]}}function s(n,e,o){let t;try{t=new URL(e)}catch{throw i.invalidConfig(`${n} must be a valid URL in production`)}if("https:"!==t.protocol)throw i.invalidConfig(`${n} must use HTTPS in production`);if(!o.has(t.origin))throw i.invalidConfig(`${n} must use a trusted Fanfare destination in production`)}export{n as API_URLS,e as BEACON_URLS,a as Config,r as DEFAULT_CONFIG};
@@ -1,2 +1,2 @@
1
- import{AppointmentManagementModule as e}from"../appointments/appointment.module.js";import{AuctionManagementModule as t}from"../auctions/auction.module.js";import{AuthenticationModule as r}from"../auth/auth.module.js";import{BeaconTrackingModule as n}from"../beacon/beacon.module.js";import{ChallengeManagementModule as i}from"../challenges/challenge.module.js";import{Config as o}from"../config/index.js";import{DrawManagementModule as s}from"../draws/draw.module.js";import{ExperienceManagementModule as a}from"../experiences/experience.module.js";import{ExperienceJourney as d}from"../experiences/journey.js";import{QueueManagementModule as l}from"../queues/queue.module.js";import{resolveCapabilityOptions as u}from"../state/capability-token-registry.js";import{getEventBus as c}from"../state/events.js";import{getSDKStore as g}from"../state/store.js";import{CrossTabCoordinator as y}from"../sync/cross-tab-coordinator.js";import{TimedReleaseManagementModule as p}from"../timed-releases/timed-release.module.js";import{version as f}from"../version.js";import{WaitlistManagementModule as m}from"../waitlists/waitlist.module.js";import{FanfareError as b,ErrorCodes as w}from"./errors.js";import{createHttpClient as v}from"./http.js";import{createLogger as M}from"./logger.js";let h=null;async function I(I){const S=new o(I),j=S.get(),A=M({enabled:j.debug||!1,level:j.debug?"debug":"warn",prefix:"Fanfare"});if(A.info("Initializing Fanfare SDK",{organizationId:I.organizationId,environment:j.environment}),h)throw new b("Fanfare SDK is already initialized. The SDK uses a shared event bus and store; multiple concurrent instances will corrupt state and analytics. Call destroy() on the existing instance before initializing a new one.",w.ALREADY_INITIALIZED);const k=/* @__PURE__ */Symbol("fanfare-sdk"),x=v({baseUrl:S.apiUrl,credentials:j.credentials,headers:{"X-Organization-Id":I.organizationId,"X-Publishable-Key":I.publishableKey,"X-Fanfare-API-Version":"2025-01-15","X-Fanfare-SDK-Version":f,"X-Fanfare-Client-Type":"browser"},timeout:3e4,retryConfig:{maxRetries:3,delayTimer:1e3,retryOnNetworkError:!0}},j),D=v({baseUrl:S.beaconUrl,credentials:"omit",headers:{"X-Organization-Id":I.organizationId,"X-Publishable-Key":I.publishableKey,"X-Fanfare-API-Version":"2025-01-15","X-Fanfare-SDK-Version":f,"X-Fanfare-Client-Type":"browser"},timeout:3e4,retryConfig:{maxRetries:3,delayTimer:1e3,retryOnNetworkError:!0}},j),E=new r(x,D),q=new i(x),C=new l(x),R=new s(x),z=new t(x),O=new e(x),F=new m(x),K=new p(x),X=new a(x,C,R,z,F,K),P=new n(D,j.beacon||{}),T=c(),J=g(),$=/* @__PURE__ */new Map,N=/* @__PURE__ */new Map,V=/* @__PURE__ */new Map;let B,U;const L=e=>{const t=N.get(e);if(t)return A.debug("Returning existing journey handle",{experienceId:e}),t;A.info("Creating new journey",{experienceId:e});const r=new d(e,B);$.set(e,r),U?.registerJourney(r);const n={view$:r.view$,events$:r.events$,latestEvent$:r.latestEvent$,snapshot$:r.state,ackEvent:e=>r.ackEvent(e),ackAllEvents:()=>r.ackAllEvents(),destroy:()=>{r.destroy(),U?.unregisterJourney(e),$.delete(e),N.delete(e)}};return N.set(e,n),n},G=async e=>{const t=e??await X.getMe(),r=/* @__PURE__ */new Set,n=new Set(t.active.journeys.map(e=>e.experienceId));for(const i of n)L(i);for(const i of n){const e=$.get(i);e&&await e.resumeFromMe(t)&&r.add(i)}return[...r]},H={check:()=>E.check(),guest:()=>E.guest(),requestOtp:e=>E.requestOtp(e),verifyOtp:e=>E.verifyOtp(e),exchangeExternal:e=>E.exchangeExternal(e),login:e=>E.login(e),logout:()=>E.logout(),getSession:()=>E.getSession(),refresh:()=>E.refresh()},_={initiate:e=>q.initiate(e),verify:e=>q.verify(e)},Y={get:e=>L(e),list:()=>Array.from(
2
- /* @__PURE__ */new Set([...N.keys(),...$.keys(),...Object.keys(J.activeJourneys)])),resumeAll:e=>(async e=>{const t=/* @__PURE__ */new Set;for(const r of Object.keys(J.activeJourneys))L(r),t.add(r);if(!e&&!E.getSession())return[...t];for(const r of await G(e))t.add(r);return[...t]})(e)},Z={track:e=>P.track(e),trackBatch:e=>P.trackBatch(e),flush:()=>P.flush()},Q=(e,t)=>{if(V.has(e.id))A.warn("CDP adapter already registered, skipping",{adapterId:e.id});else{try{e.init(T,t)}catch(r){return void A.error("CDP adapter failed to initialize",{adapterId:e.id,error:r})}V.set(e.id,e),A.info("CDP adapter registered",{adapterId:e.id})}},W=(e,t)=>T.on(e,t),ee=async()=>{A.info("Destroying SDK instance");for(const t of V.values())try{t.destroy()}catch(e){A.error("CDP adapter failed to destroy",{adapterId:t.id,error:e})}V.clear();for(const t of Array.from(N.values()))try{t.destroy()}catch(e){A.error("Journey handle failed to destroy",{error:e})}$.clear(),N.clear(),"destroy"in E&&"function"==typeof E.destroy&&E.destroy(),C.destroy(),R.destroy(),z.destroy(),O.destroy(),F.destroy(),K.destroy(),X.destroy(),P.destroy(),U?.close(),T.off(),h===k&&(h=null)};B={auth:H,challenges:_,journeys:Y,queues:{get:e=>C.get(e),enter:(e,t,r)=>C.enter(e,t,u({distributionId:e},r)),leave:(e,t)=>C.leave(e,u({distributionId:e},t)),status:e=>C.status(e),startMonitoring:(e,t,r)=>C.startMonitoring(e,t,r),stopMonitoring:e=>C.stopMonitoring(e),isMonitoring:e=>C.isMonitoring(e)},draws:{get:e=>R.get(e),enter:(e,t,r)=>R.enter(e,t,u({distributionId:e},r)),leave:(e,t)=>R.leave(e,u({distributionId:e},t)),status:e=>R.status(e),startMonitoring:(e,t,r)=>R.startMonitoring(e,t,r),stopMonitoring:e=>R.stopMonitoring(e),isMonitoring:e=>R.isMonitoring(e)},auctions:{get:e=>z.get(e),bid:(e,t,r)=>z.bid(e,t,u({distributionId:e},r)),enter:(e,t,r)=>z.enter(e,t,u({distributionId:e},r)),leave:(e,t)=>z.leave(e,u({distributionId:e},t)),status:e=>z.status(e),getBidHistory:e=>z.getBidHistory(e),enableAutoRebid:(e,t,r)=>z.enableAutoRebid(e,t,r),disableAutoRebid:e=>z.disableAutoRebid(e),getAutoRebidConfig:e=>z.getAutoRebidConfig(e),startMonitoring:(e,t,r)=>z.startMonitoring(e,t,r),stopMonitoring:e=>z.stopMonitoring(e),isMonitoring:e=>z.isMonitoring(e),destroy:()=>z.destroy()},experiences:{get:e=>X.get(e),enter:e=>X.enter(e),leave:e=>X.leave(e),getMe:()=>X.getMe(),findSequence:(e,t)=>X.findSequence(e,t),validateSequenceAccess:(e,t)=>X.validateSequenceAccess(e,t),selectSequence:e=>X.selectSequence(e),getCurrentDistributions:e=>X.getCurrentDistributions(e),enterDistribution:e=>{if(!e)throw new b("Distribution is required",w.VALIDATION_ERROR);return X.enterDistribution(e)},getActiveSession:()=>X.getActiveSession(),isInExperience:e=>X.isInExperience(e),getSelectedSequence:()=>X.getSelectedSequence(),createJourney:e=>L(e),resumeJourneysFromMe:e=>G(e),destroy:()=>X.destroy()},waitlists:{enter:(e,t)=>F.enter(e,u({distributionId:e},t)),leave:(e,t)=>F.leave(e,u({distributionId:e},t)),getStatus:e=>F.getStatus(e),destroy:()=>F.destroy()},timedReleases:{get:e=>K.get(e),enter:(e,t,r)=>K.enter(e,t,u({distributionId:e},r)),leave:(e,t)=>K.leave(e,u({distributionId:e},t)),complete:e=>K.complete(e),status:e=>K.status(e),startMonitoring:(e,t,r)=>K.startMonitoring(e,t,r),stopMonitoring:e=>K.stopMonitoring(e),isMonitoring:e=>K.isMonitoring(e)},appointments:{get:e=>O.get(e),getSlots:(e,t)=>O.getSlots(e,t),getMe:e=>O.getMe(e),book:(e,t,r)=>O.book(e,t,r),cancel:(e,t,r,n)=>O.cancel(e,t,r,n),reschedule:(e,t,r)=>O.reschedule(e,t,r),startMonitoring:(e,t,r)=>O.startMonitoring(e,t,r),stopMonitoring:e=>O.stopMonitoring(e),isMonitoring:e=>O.isMonitoring(e),bumpMonitorGeneration:e=>O.bumpMonitorGeneration(e),destroy:()=>O.destroy()},beacon:Z,use:Q,on:W,destroy:ee};const te=!1!==I.sync&&"object"==typeof I.sync?I.sync:{};U=new y({enabled:!1!==I.sync&&!1!==te.enabled,channelName:te.channelName}),A.info("Inter-tab sync initialized",{tabId:U.getTabId(),enabled:U.isEnabled()});const re={auth:H,challenges:_,journeys:Y,beacon:Z,appointments:B.appointments,use:Q,on:W,destroy:ee};return h=k,A.info("SDK initialized successfully"),re}export{I as init};
1
+ import{AppointmentManagementModule as e}from"../appointments/appointment.module.js";import{AuctionManagementModule as t}from"../auctions/auction.module.js";import{AuthenticationModule as r}from"../auth/auth.module.js";import{BeaconTrackingModule as n}from"../beacon/beacon.module.js";import{ChallengeManagementModule as i}from"../challenges/challenge.module.js";import{Config as o}from"../config/index.js";import{DrawManagementModule as s}from"../draws/draw.module.js";import{ExperienceManagementModule as a}from"../experiences/experience.module.js";import{ExperienceJourney as d}from"../experiences/journey.js";import{QueueManagementModule as l}from"../queues/queue.module.js";import{resolveCapabilityOptions as u}from"../state/capability-token-registry.js";import{getEventBus as c}from"../state/events.js";import{getSDKStore as g}from"../state/store.js";import{CrossTabCoordinator as y}from"../sync/cross-tab-coordinator.js";import{TimedReleaseManagementModule as m}from"../timed-releases/timed-release.module.js";import{version as f}from"../version.js";import{WaitlistManagementModule as p}from"../waitlists/waitlist.module.js";import{FanfareError as b,ErrorCodes as w}from"./errors.js";import{createHttpClient as h}from"./http.js";import{createLogger as v}from"./logger.js";let M=null;async function I(I){const S=new o(I),j=S.get(),A=v({enabled:j.debug||!1,level:j.debug?"debug":"warn",prefix:"Fanfare"});if(A.info("Initializing Fanfare SDK",{organizationId:I.organizationId,environment:j.environment}),M)throw new b("Fanfare SDK is already initialized. The SDK uses a shared event bus and store; multiple concurrent instances will corrupt state and analytics. Call destroy() on the existing instance before initializing a new one.",w.ALREADY_INITIALIZED);const k=/* @__PURE__ */Symbol("fanfare-sdk"),x=h({baseUrl:S.apiUrl,credentials:j.credentials,headers:{"X-Organization-Id":I.organizationId,"X-Publishable-Key":I.publishableKey,"X-Fanfare-API-Version":"2025-01-15","X-Fanfare-SDK-Version":f,"X-Fanfare-Client-Type":"browser"},timeout:3e4,retryConfig:{maxRetries:3,delayTimer:1e3,retryOnNetworkError:!0}},j),D=h({baseUrl:S.beaconUrl,credentials:"omit",headers:{"X-Organization-Id":I.organizationId,"X-Publishable-Key":I.publishableKey,"X-Fanfare-API-Version":"2025-01-15","X-Fanfare-SDK-Version":f,"X-Fanfare-Client-Type":"browser"},timeout:3e4,retryConfig:{maxRetries:3,delayTimer:1e3,retryOnNetworkError:!0}},j),E=new r(x,D),R=new i(x),q=new l(x),C=new s(x),z=new t(x),O=new e(x),F=new p(x),K=new m(x),X=new a(x,q,C,z,F,K),P=new n(D,j.beacon||{}),T=c(),J=g(),$=/* @__PURE__ */new Map,N=/* @__PURE__ */new Map,V=/* @__PURE__ */new Map;let B,U;const L=e=>{const t=N.get(e);if(t)return A.debug("Returning existing journey handle",{experienceId:e}),t;A.info("Creating new journey",{experienceId:e});const r=new d(e,B);$.set(e,r),U?.registerJourney(r);const n={view$:r.view$,events$:r.events$,latestEvent$:r.latestEvent$,snapshot$:r.state,ackEvent:e=>r.ackEvent(e),ackAllEvents:()=>r.ackAllEvents(),destroy:()=>{r.destroy(),U?.unregisterJourney(e),$.delete(e),N.delete(e)}};return N.set(e,n),n},G=async e=>{const t=e??await X.getMe(),r=/* @__PURE__ */new Set,n=new Set(t.active.journeys.map(e=>e.experienceId));for(const i of n)L(i);for(const i of n){const e=$.get(i);e&&await e.resumeFromMe(t)&&r.add(i)}return[...r]},H=async e=>{const t=/* @__PURE__ */new Set;for(const r of Object.keys(J.activeJourneys))L(r),t.add(r);if(!e&&!E.getSession())return[...t];for(const r of await G(e))t.add(r);return[...t]},_={check:()=>E.check(),guest:()=>E.guest(),requestOtp:e=>E.requestOtp(e),verifyOtp:e=>E.verifyOtp(e),exchangeExternal:e=>E.exchangeExternal(e),login:e=>E.login(e),logout:()=>E.logout(),getSession:()=>E.getSession(),refresh:()=>E.refresh()},Y={initiate:e=>R.initiate(e),verify:e=>R.verify(e)},Z={get:e=>L(e),list:()=>Array.from(
2
+ /* @__PURE__ */new Set([...N.keys(),...$.keys(),...Object.keys(J.activeJourneys)])),resumeAll:e=>H(e)},Q={track:e=>P.track(e),trackBatch:e=>P.trackBatch(e),flush:()=>P.flush()},W=(e,t)=>{if(V.has(e.id))A.warn("CDP adapter already registered, skipping",{adapterId:e.id});else{try{e.init(T,t)}catch(r){return void A.error("CDP adapter failed to initialize",{adapterId:e.id,error:r})}V.set(e.id,e),A.info("CDP adapter registered",{adapterId:e.id})}},ee=(e,t)=>T.on(e,t),te=async()=>{A.info("Destroying SDK instance");for(const t of V.values())try{t.destroy()}catch(e){A.error("CDP adapter failed to destroy",{adapterId:t.id,error:e})}V.clear();for(const t of Array.from(N.values()))try{t.destroy()}catch(e){A.error("Journey handle failed to destroy",{error:e})}$.clear(),N.clear(),"destroy"in E&&"function"==typeof E.destroy&&E.destroy(),q.destroy(),C.destroy(),z.destroy(),O.destroy(),F.destroy(),K.destroy(),X.destroy(),P.destroy(),U?.close(),T.off(),M===k&&(M=null)};B={auth:_,challenges:Y,journeys:Z,queues:{get:e=>q.get(e),enter:(e,t,r)=>q.enter(e,t,u({distributionId:e},r)),leave:(e,t)=>q.leave(e,u({distributionId:e},t)),status:e=>q.status(e),startMonitoring:(e,t,r)=>q.startMonitoring(e,t,r),stopMonitoring:e=>q.stopMonitoring(e),isMonitoring:e=>q.isMonitoring(e)},draws:{get:e=>C.get(e),enter:(e,t,r)=>C.enter(e,t,u({distributionId:e},r)),leave:(e,t)=>C.leave(e,u({distributionId:e},t)),status:e=>C.status(e),startMonitoring:(e,t,r)=>C.startMonitoring(e,t,r),stopMonitoring:e=>C.stopMonitoring(e),isMonitoring:e=>C.isMonitoring(e)},auctions:{get:e=>z.get(e),bid:(e,t,r)=>z.bid(e,t,u({distributionId:e},r)),enter:(e,t,r)=>z.enter(e,t,u({distributionId:e},r)),leave:(e,t)=>z.leave(e,u({distributionId:e},t)),status:e=>z.status(e),getBidHistory:e=>z.getBidHistory(e),enableAutoRebid:(e,t,r)=>z.enableAutoRebid(e,t,r),disableAutoRebid:e=>z.disableAutoRebid(e),getAutoRebidConfig:e=>z.getAutoRebidConfig(e),startMonitoring:(e,t,r)=>z.startMonitoring(e,t,r),stopMonitoring:e=>z.stopMonitoring(e),isMonitoring:e=>z.isMonitoring(e),destroy:()=>z.destroy()},experiences:{get:e=>X.get(e),enter:e=>X.enter(e),leave:e=>X.leave(e),getMe:()=>X.getMe(),findSequence:(e,t)=>X.findSequence(e,t),validateSequenceAccess:(e,t)=>X.validateSequenceAccess(e,t),selectSequence:e=>X.selectSequence(e),getCurrentDistributions:e=>X.getCurrentDistributions(e),enterDistribution:e=>{if(!e)throw new b("Distribution is required",w.VALIDATION_ERROR);return X.enterDistribution(e)},getActiveSession:()=>X.getActiveSession(),isInExperience:e=>X.isInExperience(e),getSelectedSequence:()=>X.getSelectedSequence(),createJourney:e=>L(e),resumeJourneysFromMe:e=>G(e),destroy:()=>X.destroy()},waitlists:{enter:(e,t)=>F.enter(e,u({distributionId:e},t)),leave:(e,t)=>F.leave(e,u({distributionId:e},t)),getStatus:e=>F.getStatus(e),destroy:()=>F.destroy()},timedReleases:{get:e=>K.get(e),enter:(e,t,r)=>K.enter(e,t,u({distributionId:e},r)),leave:(e,t)=>K.leave(e,u({distributionId:e},t)),complete:e=>K.complete(e),status:e=>K.status(e),startMonitoring:(e,t,r)=>K.startMonitoring(e,t,r),stopMonitoring:e=>K.stopMonitoring(e),isMonitoring:e=>K.isMonitoring(e)},appointments:{get:e=>O.get(e),getSlots:(e,t)=>O.getSlots(e,t),getMe:e=>O.getMe(e),book:(e,t,r)=>O.book(e,t,r),cancel:(e,t,r,n)=>O.cancel(e,t,r,n),reschedule:(e,t,r)=>O.reschedule(e,t,r),startMonitoring:(e,t,r)=>O.startMonitoring(e,t,r),stopMonitoring:e=>O.stopMonitoring(e),isMonitoring:e=>O.isMonitoring(e),bumpMonitorGeneration:e=>O.bumpMonitorGeneration(e),destroy:()=>O.destroy()},beacon:Q,use:W,on:ee,destroy:te};const re=!1!==I.sync&&"object"==typeof I.sync?I.sync:{};U=new y({enabled:!1!==I.sync&&!1!==re.enabled,channelName:re.channelName}),A.info("Inter-tab sync initialized",{tabId:U.getTabId(),enabled:U.isEnabled()});const ne={auth:_,challenges:Y,journeys:Z,beacon:Q,appointments:B.appointments,use:W,on:ee,destroy:te};if(await E.rehydrate(),j.autoResume&&E.getSession())try{await H()}catch(ie){A.warn("Auto-resume on init failed; journeys can be resumed manually",{error:ie instanceof Error?ie.message:String(ie)})}return M=k,A.info("SDK initialized successfully"),ne}export{I as init};
package/dist/core/http.js CHANGED
@@ -1 +1 @@
1
- import e from"wretch";import{retry as t}from"wretch/middlewares";import{getAdmissionKeyThumbprint as r,buildAdmissionProofHeaders as i}from"../security/admission-proof.js";import{generateFingerprint as s}from"../utils/fingerprint.module.js";import{FanfareError as o,ErrorCodes as n}from"./errors.js";import{getLogger as a}from"./logger.js";import{isStorageAvailable as c,generateId as l}from"./utils.js";class g{constructor(e,t){this.logger=a(),this.fingerprint=null,this.config=e,this.fingerprintingEnabled=!1!==t?.features?.fingerprinting;const r="fanfare_client_session_id";let i=null;if(c("localStorage"))try{i=window.localStorage.getItem(r)}catch{i=null}if(this.clientSessionId=i||l(),!i&&c("localStorage"))try{window.localStorage.setItem(r,this.clientSessionId)}catch{}this.fingerprintingEnabled?this.initializeFingerprint():this.logger.debug("Browser fingerprinting disabled by configuration")}async initializeFingerprint(){try{const e=await s();this.fingerprint=e.hash,this.logger.debug("Browser fingerprint generated successfully",{fingerprintHash:this.fingerprint})}catch(e){this.logger.debug("Failed to generate browser fingerprint",{error:e instanceof Error?e.message:"Unknown error"}),this.fingerprint=null}}getClient(r){const{retryConfig:i={}}=this.config,s={"Content-Type":"application/json","X-Client-Session-Id":this.clientSessionId,...this.config.headers};return this.fingerprint&&(s["X-Fingerprint"]=this.fingerprint),e(this.config.baseUrl).polyfills({fetch:(e,t)=>globalThis.fetch(e,t)}).options({credentials:this.config.credentials??"include",mode:"cors"}).headers(s).middlewares([t({delayTimer:i.delayTimer||1e3,maxAttempts:i.maxRetries||3,until:(e,t)=>e?!!e.ok||e.status>=400&&e.status<500&&429!==e.status:(t&&i.retryOnNetworkError,!1),onRetry:e=>{this.logger.debug("Retrying request",{status:e.response?.status,error:e.error?.message,retryAfter:e.response?.headers?.get("Retry-After")??void 0})}})]).catcher(401,async e=>{throw!r?.skipUnauthorizedHandler&&this.config.onUnauthorized&&(this.logger.debug("Received 401, triggering re-authentication"),await this.config.onUnauthorized()),await this.toFanfareError(e)}).catcherFallback(async e=>{throw await this.toFanfareError(e)})}async toFanfareError(e){if(e&&"object"==typeof e&&"response"in e&&e.response instanceof Response){let t=null;const r=await e.response.text().catch(()=>null),i=e;if(r)try{t=JSON.parse(r)}catch{t={message:r}}else if(null!==i.json&&void 0!==i.json&&"object"==typeof i.json)t=i.json;else if("string"==typeof i.text&&i.text.length>0)try{t=JSON.parse(i.text)}catch{t={message:i.text}}const s=("correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0)||void 0;return o.fromResponse(e.response,t,s)}return e&&"object"==typeof e&&"name"in e&&"AbortError"===e.name?new o("Request timed out",n.TIMEOUT,void 0,void 0,"correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0):new o(e&&"object"==typeof e&&"message"in e&&"string"==typeof e.message?e.message:"Network error occurred",n.NETWORK_ERROR,void 0,{originalError:e},e&&"object"==typeof e&&"correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0)}async request(e,t,s,a){const c=l(),g=l(),p=this.config.timeout||3e4,d=await r(),h=function(e){return/^\/(queues|draws)\/[^/]+\/enter$/.test(e)}(t),f=new URL(t,this.config.baseUrl).toString(),u=new AbortController,m=setTimeout(()=>u.abort(),p);try{const r={"X-Correlation-Id":c,"X-Request-Id":g,...a?.headers};if(h&&!d&&!r["X-Admission-Key-Thumbprint"])throw new o("Admission proof requires persistent browser crypto support",n.ADMISSION_PROOF_UNAVAILABLE,void 0,{path:t});if(h){const s=await i({url:f,method:e.toUpperCase()});if(!s)throw new o("Admission proof requires persistent browser crypto support",n.ADMISSION_PROOF_UNAVAILABLE,void 0,{path:t});Object.assign(r,s)}d&&!r["X-Admission-Key-Thumbprint"]&&(r["X-Admission-Key-Thumbprint"]=d);const l=this.getClient(a).url(t).headers(r).options({signal:a?.signal||u.signal});let p;switch(e){case"get":p=await l.get().json();break;case"post":p=await l.post(s).json();break;case"put":p=await l.put(s).json();break;case"delete":p=await l.delete().json();break;case"patch":p=await l.patch(s).json();break;default:throw new Error(`Unsupported HTTP method: ${e}`)}return p}finally{clearTimeout(m)}}async get(e,t){return this.request("get",e,void 0,t)}async post(e,t,r){return this.request("post",e,t,r)}async put(e,t,r){return this.request("put",e,t,r)}async delete(e,t){return this.request("delete",e,void 0,t)}async patch(e,t,r){return this.request("patch",e,t,r)}updateConfig(e){this.config={...this.config,...e}}getConfig(){return{...this.config}}}function p(e,t){return new g(e,t)}export{g as DefaultHttpClient,p as createHttpClient};
1
+ import e from"wretch";import{retry as t}from"wretch/middlewares";import{buildAdmissionProofHeaders as r,getAdmissionKeyThumbprint as i}from"../security/admission-proof.js";import{generateFingerprint as s}from"../utils/fingerprint.module.js";import{FanfareError as o,ErrorCodes as n}from"./errors.js";import{getLogger as a}from"./logger.js";import{isStorageAvailable as c,generateId as l}from"./utils.js";class g{constructor(e,t){this.logger=a(),this.fingerprint=null,this.config=e,this.fingerprintingEnabled=!1!==t?.features?.fingerprinting;const r="fanfare_client_session_id";let i=null;if(c("localStorage"))try{i=window.localStorage.getItem(r)}catch{i=null}if(this.clientSessionId=i||l(),!i&&c("localStorage"))try{window.localStorage.setItem(r,this.clientSessionId)}catch{}this.fingerprintingEnabled?this.initializeFingerprint():this.logger.debug("Browser fingerprinting disabled by configuration")}async initializeFingerprint(){try{const e=await s();this.fingerprint=e.hash,this.logger.debug("Browser fingerprint generated successfully",{fingerprintHash:this.fingerprint})}catch(e){this.logger.debug("Failed to generate browser fingerprint",{error:e instanceof Error?e.message:"Unknown error"}),this.fingerprint=null}}getClient(i){const{retryConfig:s={}}=this.config,o={"Content-Type":"application/json","X-Client-Session-Id":this.clientSessionId,...this.config.headers};return this.fingerprint&&(o["X-Fingerprint"]=this.fingerprint),e(this.config.baseUrl).polyfills({fetch:(e,t)=>globalThis.fetch(e,t)}).options({credentials:this.config.credentials??"include",mode:"cors"}).headers(o).middlewares([t({delayTimer:s.delayTimer||1e3,maxAttempts:s.maxRetries||3,until:(e,t)=>e?!!e.ok||e.status>=400&&e.status<500&&429!==e.status:(t&&s.retryOnNetworkError,!1),onRetry:async e=>{this.logger.debug("Retrying request",{status:e.response?.status,error:e.error?.message,retryAfter:e.response?.headers?.get("Retry-After")??void 0});const t=e.options.headers&&"object"==typeof e.options.headers&&!Array.isArray(e.options.headers)?e.options.headers:{};if(!Object.keys(t).some(e=>"dpop"===e.toLowerCase()))return;const i="string"==typeof e.options.method?e.options.method:"POST",s=this.config.baseUrl,o=e.url.startsWith(s)?new URL(e.url.slice(s.length),s).toString():e.url,n=await r({url:o,method:i});return n?{options:{...e.options,headers:{...t,...n}}}:void 0}})]).catcher(401,async e=>{throw!i?.skipUnauthorizedHandler&&this.config.onUnauthorized&&(this.logger.debug("Received 401, triggering re-authentication"),await this.config.onUnauthorized()),await this.toFanfareError(e)}).catcherFallback(async e=>{throw await this.toFanfareError(e)})}async toFanfareError(e){if(e&&"object"==typeof e&&"response"in e&&e.response instanceof Response){let t=null;const r=await e.response.text().catch(()=>null),i=e;if(r)try{t=JSON.parse(r)}catch{t={message:r}}else if(null!==i.json&&void 0!==i.json&&"object"==typeof i.json)t=i.json;else if("string"==typeof i.text&&i.text.length>0)try{t=JSON.parse(i.text)}catch{t={message:i.text}}const s=("correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0)||void 0;return o.fromResponse(e.response,t,s)}return e&&"object"==typeof e&&"name"in e&&"AbortError"===e.name?new o("Request timed out",n.TIMEOUT,void 0,void 0,"correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0):new o(e&&"object"==typeof e&&"message"in e&&"string"==typeof e.message?e.message:"Network error occurred",n.NETWORK_ERROR,void 0,{originalError:e},e&&"object"==typeof e&&"correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0)}async request(e,t,s,a){const c=l(),g=l(),p=this.config.timeout||3e4,d=await i(),h=function(e){return/^\/(queues|draws|timed-releases|waitlists)\/[^/]+\/enter$/.test(e)}(t),u=new URL(t,this.config.baseUrl).toString(),f=new AbortController,m=setTimeout(()=>f.abort(),p);try{const i={"X-Correlation-Id":c,"X-Request-Id":g,...a?.headers};if(h&&!d&&!i["X-Admission-Key-Thumbprint"])throw new o("Admission proof requires persistent browser crypto support",n.ADMISSION_PROOF_UNAVAILABLE,void 0,{path:t});if(h){const s=await r({url:u,method:e.toUpperCase()});if(!s)throw new o("Admission proof requires persistent browser crypto support",n.ADMISSION_PROOF_UNAVAILABLE,void 0,{path:t});Object.assign(i,s)}if(!h&&function(e){return/^\/auctions\/[^/]+\/bid$/.test(e)}(t)&&d){const t=await r({url:u,method:e.toUpperCase()});t&&Object.assign(i,t)}d&&!i["X-Admission-Key-Thumbprint"]&&(i["X-Admission-Key-Thumbprint"]=d);const l=this.getClient(a).url(t).headers(i).options({signal:a?.signal||f.signal});let p;switch(e){case"get":p=await l.get().json();break;case"post":p=await l.post(s).json();break;case"put":p=await l.put(s).json();break;case"delete":p=await l.delete().json();break;case"patch":p=await l.patch(s).json();break;default:throw new Error(`Unsupported HTTP method: ${e}`)}return p}finally{clearTimeout(m)}}async get(e,t){return this.request("get",e,void 0,t)}async post(e,t,r){return this.request("post",e,t,r)}async put(e,t,r){return this.request("put",e,t,r)}async delete(e,t){return this.request("delete",e,void 0,t)}async patch(e,t,r){return this.request("patch",e,t,r)}updateConfig(e){this.config={...this.config,...e}}getConfig(){return{...this.config}}}function p(e,t){return new g(e,t)}export{g as DefaultHttpClient,p as createHttpClient};
@@ -80,8 +80,10 @@ export interface AuctionDisplayState {
80
80
  currentBid?: string;
81
81
  /** Number of bids reported by the auction service. */
82
82
  bidCount?: number;
83
- /** ISO 8601 timestamp when bidding closes, when known. */
83
+ /** ISO 8601 timestamp when the distribution is done (terminal edge), when known. */
84
84
  closeAt?: string;
85
+ /** ISO 8601 timestamp when bidding closes (the within-window settle point); the bid countdown target. */
86
+ settleAt?: string;
85
87
  /** Auction display status from the auction service. */
86
88
  status?: string;
87
89
  /** ISO 4217 currency code for auction money fields. */
@@ -1 +1 @@
1
- import{atom as e,computed as t}from"nanostores";import*as i from"valibot";import{createAppointmentDriver as s,withAppointmentTerminalConsumer as r}from"../appointments/appointment.driver.js";import{createAuctionDriver as n,withAuctionGrantedConsumer as o,withAuctionTerminalConsumer as a}from"../auctions/auction.driver.js";import{createError as c,isFanfareError as d,ErrorCodes as u}from"../core/errors.js";import{createLogger as h}from"../core/logger.js";import{generateId as p}from"../core/utils.js";import{createDrawDriver as l,withDrawGrantedConsumer as m,withDrawTerminalConsumer as y}from"../draws/draw.driver.js";import{createQueueDriver as g,withQueueGrantedConsumer as f,withQueueTerminalConsumer as b}from"../queues/queue.driver.js";import{clearExperienceContext as I,clearSequenceContext as v,bindDistributionContext as S,upsertSequenceToken as A,getSequenceTokenExpiryMs as q}from"../state/capability-token-registry.js";import{getEventBus as w}from"../state/events.js";import{getSDKStore as x}from"../state/store.js";import{createTimedReleaseDriver as T,withTimedReleaseGrantedConsumer as R,withTimedReleaseTerminalConsumer as j}from"../timed-releases/timed-release.driver.js";import{NoSequenceAvailableError as C}from"./experience.module.js";import{buildJourneyView as E}from"./journey-view.js";import{createInitialSnapshot as D,getParticipation as k,getSequencePhase as M,normalizeSequenceState as O,buildSnapshot as F,getSequenceId as G,getDistribution as _,getConsumer as z,getWaitlistAttachment as B,getOutcome as U,getGrant as J,getSequenceState as P,getGates as L,createGrant as N,getAdmissionGrantExpiresAt as V,validateSequenceState as W}from"./journey.machine.js";const $="Cannot commit a stale sequence transition",Q="Cannot commit a sequence transition before routing",H=3e4;class K{constructor(i,s){this.experienceId=i,this.sdk=s,this.store=x(),this.logger=h(),this.activeMonitor=null,this.consecutiveRefreshFailures=0,this.checkoutStarted=!1,this.displayAtom=null,this.destroyed=!1,this.operationQueue=Promise.resolve(),this.EVENT_BUFFER_LIMIT=20;const r=this.restoreFromStore(),n=r??D(this.experienceId),o=r?k(r):void 0;o&&"participating"===M(n)&&this.ensureDisplayAtom(o),this.stateAtom=e(n),this.state=this.stateAtom,this.sequenceDrivers=this.createSequenceDrivers(),this.view$=t(this.stateAtom,e=>this.buildView(e)),this.events$=t(this.stateAtom,e=>e.events),this.latestEvent$=t(this.stateAtom,e=>{if(!e.events.length)return null;const t=e.lastSeenEventId??"",i=e.events.filter(e=>e.id>t);return i.length?i[i.length-1]:null}),r?this.rearmTimersIfNeeded():this.persist()}getExperienceId(){return this.experienceId}async start(e){await this.runSerializedOperation(async()=>{e?.reset&&(this.clearRoutedRuntimeState(),this.setSnapshot(D(this.experienceId))),"accessCode"in(e??{})&&this.patchJourneyBase({accessCode:e?.accessCode??null}),await this.routeAndLoadDistribution(this.stateAtom.get().accessCode)})}async reroute(e={}){await this.runSerializedOperation(async()=>{"accessCode"in e&&this.patchJourneyBase({accessCode:e.accessCode??null}),await this.routeAndLoadDistribution(e.accessCode??this.stateAtom.get().accessCode)})}async submitAccessCode(e){return await this.runSerializedOperation(async()=>{const t=this.stateAtom.get();if("routed"!==t.journeyStage){await this.routeAndLoadDistribution(e,{transient:!0});const t=this.stateAtom.get();return"routed"!==t.journeyStage?"gated"===t.journeyStage?{outcome:"gated"}:{outcome:"unchanged"}:(this.patchJourneyBase({accessCode:e}),{outcome:"advanced",sequenceId:t.sequenceId})}const i=t.sequenceId,s=t.accessCode,r=new Set(t.offers.map(e=>e.sequence.id));await this.routeAndLoadDistribution(e,{transient:!0});let n=this.stateAtom.get();return"routed"===n.journeyStage&&n.sequenceId===i?{outcome:"unchanged"}:"routed"===n.journeyStage&&r.has(n.sequenceId)?(this.patchJourneyBase({accessCode:e}),{outcome:"advanced",sequenceId:n.sequenceId}):("string"==typeof s&&s!==e&&(await this.routeAndLoadDistribution(s,{carryFrom:t,transient:!0}),n=this.stateAtom.get()),"gated"===n.journeyStage?{outcome:"gated"}:{outcome:"unchanged"})})}async retry(){await this.runSerializedOperation(()=>this.routeAndLoadDistribution(this.stateAtom.get().accessCode))}destroy(){this.destroyed||(this.destroyed=!0,this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.stopPolling(),I(this.experienceId),this.store.removeJourney(this.experienceId),this.stateAtom.set(D(this.experienceId)))}async resumeFromMe(e){const t=e.active.journeys.find(e=>e.experienceId===this.experienceId&&"routed"===e.journeyStage);if(!t||"routed"!==t.journeyStage)return!1;const i=this.stateAtom.get(),s=O({sequence:t.sequence}),r=F({base:this.baseFromSnapshot(i),journeyStage:"routed",sequenceId:t.sequenceId,offers:this.normalizeOffers(t.offers),sequence:s});this.setSnapshot(r),"routed"===i.journeyStage&&i.sequenceId!==t.sequenceId&&v(this.experienceId,i.sequenceId);try{await this.reroute()}catch{}return this.rearmTimersIfNeeded(),!0}async refreshDistribution(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=G(e);if(!t)throw c.validationError("Cannot refresh distribution before routing to a sequence");const i=await this.sdk.experiences.getCurrentDistributions(t),s=this.buildDistributionSummary(i,t),r=_(e);s&&S({experienceId:this.experienceId,sequenceId:t,distributionContext:s});const n=z(e),o="scheduled"===s?.lifecycle?X(B(e),s):void 0,a=this.deriveTerminalOutcomeForClosedDistribution(n,s,U(e)),d=this.withTerminalConsumer(n,a),u=this.projectSequenceState({distribution:s,consumer:d,waitlist:o,grant:J(e),outcome:a});this.commit(u),this.emitIfDistributionChanged(r,s)})}async joinWaitlistAttachment(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=P(e),i=_(e),s="scheduled"===t?.phase&&"waitlist"in t?t.waitlist?.waitlistId:i?.waitlistId,r="string"==typeof s?s:void 0;if("scheduled"!==t?.phase||!i||!r)throw c.validationError("Waitlist is not available for this sequence");const n=await this.sdk.waitlists.enter(r),o=this.projectSequenceState({distribution:i,waitlist:{waitlistId:r,status:"waitlisted",entryId:n.id}});this.commit(o),this.emitEvent("sequence_change","info","user","Entered waitlist",{waitlistId:r})})}async exitWaitlistAttachment(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=P(e),i=_(e),s="scheduled"===t?.phase&&"waitlist"in t?t.waitlist:void 0,r=s?.waitlistId??i?.waitlistId;if("scheduled"!==t?.phase||!i||!r)return;await this.sdk.waitlists.leave(r);const n=this.projectSequenceState({distribution:i,waitlist:{waitlistId:r,status:"not_waitlisted"}});this.commit(n),this.emitEvent("sequence_change","info","user","Left waitlist",{waitlistId:r})})}claim(){const e=J(this.stateAtom.get());if(!e)throw c.validationError("No grant is available to claim");return this.checkoutStarted=!0,this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.emitEvent("sequence_change","success","user","Grant claimed",{distributionType:e.distributionType,distributionId:e.distributionId,sequenceId:e.sequenceId}),w().emit("journey:grant-claimed",{experienceId:this.experienceId,sequenceId:e.sequenceId,grant:e.token}),e}ackEvent(e){this.stateAtom.get().events.some(t=>t.id===e)&&this.patchJourneyBase({lastSeenEventId:e})}ackAllEvents(){const e=this.stateAtom.get();if(!e.events.length)return;const t=e.events[e.events.length-1];this.patchJourneyBase({lastSeenEventId:t.id})}async routeAndLoadDistribution(e,t){const i=t?.carryFrom??this.stateAtom.get(),s=G(i),r=await this.resolveRouting(e,{transient:t?.transient});if(!r||this.destroyed)return;if(await this.enterExperienceIfReady(),this.destroyed)return;const n=await this.sdk.experiences.getCurrentDistributions(r.sequenceId);if(this.destroyed)return;const o=this.buildDistributionSummary(n,r.sequenceId);o&&S({experienceId:this.experienceId,sequenceId:r.sequenceId,distributionContext:o});const a=s===r.sequenceId,c=a?z(i):void 0,d=a&&"scheduled"===o?.lifecycle?X(B(i),o):void 0,u=a?J(i):void 0,h=a?this.deriveTerminalOutcomeForClosedDistribution(c,o,U(i)):void 0,p=this.withTerminalConsumer(c,h),l=!o&&a&&"routed"===i.journeyStage&&("granted"===i.sequence.phase||"ended"===i.sequence.phase)?i.sequence:this.projectSequenceState({distribution:o,consumer:p,waitlist:d,grant:u,outcome:h});a||(this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer());const m=this.stateAtom.get(),y=F({base:this.baseFromSnapshot(m),journeyStage:"routed",sequenceId:r.sequenceId,offers:r.offers,sequence:l,sequenceRef:r.selectedSequence});this.setSnapshot(y),this.consecutiveRefreshFailures=0,this.rearmTimersIfNeeded();const g=a?_(i):void 0;this.emitIfDistributionChanged(g,o)}async resolveRouting(e,t){const i=this.stateAtom.get(),s=G(i);let r;this.transitionToRouting();try{r=await this.sdk.experiences.findSequence(this.experienceId,e)}catch(o){throw this.setSnapshot(i),"routed"===i.journeyStage&&this.armCapabilityRefreshTimer(),this.emitEvent("error","error","user","Failed to route experience"),o}if(this.destroyed)return null;if("gated"===r.outcome&&r.selected){const e=r.selected.gates;return"routed"===i.journeyStage&&this.clearRoutedRuntimeState(),this.transitionToGated({gates:e,selected:{sequence:r.selected.sequence,status:"gated"}}),this.emitEvent("requirement","info","user","Gates required",{gates:e}),null}if("no_access"===r.outcome||!r.selected)throw this.setSnapshot(i),this.emitEvent("error","error","user","No access available for this experience"),new C("No access available for this experience");const n=r.selected.sequence.id;if(s&&s!==n&&v(this.experienceId,s),r.selected.capabilityGrant&&A({experienceId:this.experienceId,sequenceId:n,token:r.selected.capabilityGrant,expiresAt:r.selected.capabilityGrantExpiresAt}),s!==n){const e="routed"===i.journeyStage?i.sequenceRef?.priority:void 0,o=r.selected.sequence.priority,a=function(e,t){return void 0===e?"unknown":t>e?"upgrade":t<e?"downgrade":"lateral"}(e,o);this.emitEvent("reroute","downgrade"===a?"warning":"info",t?.transient?"system":"user","downgrade"===a?"Sequence downgraded":"Sequence changed",{previousSequenceId:s,sequenceId:n,...void 0===e?{}:{previousPriority:e},priority:o,direction:a})}return{sequenceId:n,selectedSequence:r.selected.sequence,offers:r.offers}}async enterExperienceIfReady(){const e=this.stateAtom.get();if(L(e).length>0)return;if(e.experience)return;const t=await this.sdk.experiences.enter(this.experienceId);this.destroyed||this.patchJourneyBase({experience:t})}createSequenceDrivers(){return{appointment:s({appointments:this.sdk.appointments,runSerializedOperation:e=>this.runSerializedOperation(e),getCurrentSequence:()=>P(this.stateAtom.get()),getDisplayState:()=>this.displayAtom?.get()??null,commit:(e,t)=>this.commit(e,t),applyMonitorUpdate:e=>this.applyMonitorUpdate(e),ensureDisplayAtom:e=>this.ensureDisplayAtom(e),stopRuntimeMonitoring:e=>this.stopRuntimeMonitoring(e),emitEvent:(e,t,i,s,r)=>this.emitEvent(e,t,i,s,r)}),auction:n({auctions:this.sdk.auctions,runSerializedOperation:e=>this.runSerializedOperation(e),getCurrentSequence:()=>P(this.stateAtom.get()),commit:(e,t)=>this.commit(e,t),applyMonitorUpdate:e=>this.applyMonitorUpdate(e),ensureDisplayAtom:e=>this.ensureDisplayAtom(e),stopRuntimeMonitoring:e=>this.stopRuntimeMonitoring(e),emitEvent:(e,t,i,s,r)=>this.emitEvent(e,t,i,s,r)}),queue:g({queues:this.sdk.queues,runSerializedOperation:e=>this.runSerializedOperation(e),getCurrentSequence:()=>P(this.stateAtom.get()),commit:(e,t)=>this.commit(e,t),applyMonitorUpdate:e=>this.applyMonitorUpdate(e),ensureDisplayAtom:e=>{this.ensureDisplayAtom(e)},stopRuntimeMonitoring:e=>this.stopRuntimeMonitoring(e),emitEvent:(e,t,i,s,r)=>this.emitEvent(e,t,i,s,r)}),timed_release:T({timedReleases:this.sdk.timedReleases,runSerializedOperation:e=>this.runSerializedOperation(e),getCurrentSequence:()=>P(this.stateAtom.get()),commit:(e,t)=>this.commit(e,t),applyMonitorUpdate:e=>this.applyMonitorUpdate(e),ensureDisplayAtom:e=>{this.ensureDisplayAtom(e)},stopRuntimeMonitoring:e=>this.stopRuntimeMonitoring(e),emitEvent:(e,t,i,s,r)=>this.emitEvent(e,t,i,s,r)}),draw:l({draws:this.sdk.draws,runSerializedOperation:e=>this.runSerializedOperation(e),getCurrentSequence:()=>P(this.stateAtom.get()),commit:(e,t)=>this.commit(e,t),applyMonitorUpdate:e=>this.applyMonitorUpdate(e),ensureDisplayAtom:e=>{this.ensureDisplayAtom(e)},stopRuntimeMonitoring:e=>this.stopRuntimeMonitoring(e),emitEvent:(e,t,i,s,r)=>this.emitEvent(e,t,i,s,r)})}}buildView(e){return E(e,{start:e=>this.start(e),reroute:e=>this.reroute(e),submitAccessCode:e=>this.submitAccessCode(e),retry:()=>this.retry(),joinWaitlist:()=>this.joinWaitlistAttachment(),exitWaitlist:()=>this.exitWaitlistAttachment(),claim:()=>this.claim()},this.displayAtom,this.sequenceDrivers)}projectSequenceState(e){if(!e.distribution)return{phase:"unavailable"};const t=this.sequenceDrivers[e.distribution.type];if(!t)throw new Error(`${e.distribution.type} sequence driver is required`);return t.project(e)}patchJourneyBase(e){if(this.destroyed)return;const t=this.stateAtom.get(),i=this.baseFromSnapshot(t,e);switch(t.journeyStage){case"ready":return void this.setSnapshot(F({base:i,journeyStage:"ready"}));case"routing":return void this.setSnapshot(F({base:i,journeyStage:"routing"}));case"gated":return void this.setSnapshot(F({base:i,journeyStage:"gated",gates:t.gates,selected:t.selected}));case"routed":return void this.setSnapshot(F({base:i,journeyStage:"routed",sequenceId:t.sequenceId,offers:t.offers,sequence:t.sequence,sequenceRef:t.sequenceRef}))}}transitionToRouting(){if(this.destroyed)return;const e=this.stateAtom.get();this.setSnapshot(F({base:this.baseFromSnapshot(e),journeyStage:"routing"}))}transitionToGated(e){if(this.destroyed)return;const t=this.stateAtom.get();this.setSnapshot(F({base:this.baseFromSnapshot(t),journeyStage:"gated",gates:e.gates,selected:e.selected}))}baseFromSnapshot(e,t={}){const i=Object.prototype.hasOwnProperty.call(t,"accessCode")?t.accessCode??void 0:e.accessCode;return{revision:e.revision,updatedAt:e.updatedAt,experienceId:e.experienceId,accessCode:i,experience:t.experience??e.experience,events:t.events??e.events,lastSeenEventId:t.lastSeenEventId??e.lastSeenEventId}}commit(e,t={}){if(this.destroyed)throw c.notInitialized("Journey has been destroyed");const i=this.stateAtom.get();if("routed"!==i.journeyStage)throw c.validationError(Q);if(!function(e,t){if("distribution"in e&&e.distribution.sequenceId&&e.distribution.sequenceId!==t)return!1;return!0}(e,i.sequenceId))throw c.validationError($);const s=F({base:{revision:i.revision,updatedAt:i.updatedAt,experienceId:i.experienceId,accessCode:i.accessCode,experience:i.experience,events:t.events??i.events,lastSeenEventId:i.lastSeenEventId},journeyStage:"routed",sequenceId:i.sequenceId,offers:i.offers,sequence:e,sequenceRef:i.sequenceRef});this.setSnapshot(s)}emitEvent(e,t,i,s,r){const n=this.stateAtom.get(),o={id:p(),ts:Date.now(),kind:e,severity:t,audience:i,message:s,detail:r},a=[...n.events,o],c=a.length>this.EVENT_BUFFER_LIMIT?a.slice(a.length-this.EVENT_BUFFER_LIMIT):a;this.patchJourneyBase({events:c})}emitIfDistributionChanged(e,t){e?.id===t?.id&&e?.lifecycle===t?.lifecycle||this.emitEvent("distribution_change","info","user","Distribution context changed",{previous:e,next:t})}buildDistributionSummary(e,t){return e.active?{type:e.active.type,id:e.active.id,sequenceId:t,lifecycle:"open",details:e.active.details,opensAt:e.active.details.openAt,closesAt:e.active.details.closeAt,updatedAt:Date.now()}:e.upcoming?{type:e.upcoming.type,id:e.upcoming.id,sequenceId:t,lifecycle:"scheduled",startsAt:e.upcoming.startsAt,opensAt:e.upcoming.startsAt,waitlistId:e.upcoming.waitlistId,updatedAt:Date.now()}:e.recentPast?{type:e.recentPast.type,id:e.recentPast.id,sequenceId:t,lifecycle:"closed",closesAt:e.recentPast.endedAt,updatedAt:Date.now()}:void 0}normalizeOffers(e){return Array.isArray(e)?e.filter(Boolean):[]}restoreFromStore(){const e=this.store.activeJourneys;if(!e||!e[this.experienceId])return null;const t=qe(e[this.experienceId]);return t||(this.store.removeJourney(this.experienceId),null)}persist(){this.store.setJourneyState(this.experienceId,this.stateAtom.get())}setSnapshot(e,t={}){if(this.destroyed)return;const i=this.stateAtom.get();this.stateAtom.set(e),!1!==t.persist&&this.persist(),this.emitPhaseChanged(i,e)}emitPhaseChanged(e,t){const i=e.journeyStage,s="routed"===i?M(e):void 0,r=t.journeyStage,n="routed"===r?M(t):void 0;i===r&&s===n||w().emit("journey:phase-changed",{experienceId:this.experienceId,journeyStage:r,sequencePhase:n})}runSerializedOperation(e){const t=this.operationQueue.then(async()=>{if(this.destroyed)throw c.notInitialized("Journey has been destroyed");return e()});return this.operationQueue=t.then(()=>{},()=>{}),t}transitionToGranted(e,t){if(this.destroyed)return;const i=this.stateAtom.get(),s=_(i),r=G(i);if(!s||!r)return void this.transitionToEnded({type:"closed",reason:"missing_distribution"});if(void 0!==t&&Date.now()>=t)return void this.transitionToEnded({type:"expired"});const n=N({token:e,distribution:s,sequenceId:r,experienceId:this.experienceId,expiresAt:t}),o=this.withGrantedConsumer(z(i),s),a=this.projectSequenceState({distribution:s,consumer:o,grant:n});this.commit(a),this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.checkoutStarted=!1,void 0!==t&&(this.grantExpiryTimer=setTimeout(()=>this.expireGrantFromTimer(),t-Date.now())),this.emitEvent("sequence_change","success","user","Grant issued",{grant:e}),w().emit("journey:granted",{experienceId:this.experienceId,sequenceId:r,grant:e})}transitionToEnded(e){if(this.destroyed)return;const t=this.stateAtom.get(),i=_(t),s=this.reinterpretLapsedGrantOutcome(t,e),r={...s,distributionType:s.distributionType??i?.type,distributionId:s.distributionId??i?.id,at:s.at??/* @__PURE__ */(new Date).toISOString()},n=this.withTerminalConsumer(z(t),r),o=this.projectSequenceState({distribution:i,consumer:n,outcome:r});this.commit(o),this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.emitEvent("sequence_change","expired"===r.type?"warning":"info","user","Sequence ended",{outcome:r}),w().emit("journey:ended",{experienceId:this.experienceId,sequenceId:G(this.stateAtom.get()),outcome:r})}reinterpretLapsedGrantOutcome(e,t){if("granted"!==M(e))return t;return"closed"===t.type&&("not_queued"===t.reason||"not_entered"===t.reason)?{type:"expired",at:t.at,distributionType:t.distributionType,distributionId:t.distributionId}:t}applyMonitorUpdate(e){this.destroyed||("granted"!==e.type?this.transitionToEnded(e.outcome):this.transitionToGranted(e.token,e.expiresAt))}applyMonitorUpdateFromCallback(e){try{this.applyMonitorUpdate(e)}catch(t){if(this.dropStaleCallbackCommit(t,"monitor"))return;throw t}}startMonitorForParticipation(e){const t=this.ensureDisplayAtom(e),i=e=>this.applyMonitorUpdateFromCallback(e),s=this.sequenceDrivers[e.type];if(s?.startMonitoring)return s.startMonitoring({participation:e,displayAtom:t,onUpdate:i}),void(this.activeMonitor={type:e.type,id:e.id,stop:()=>s.stopMonitoring?.()});throw new Error(`Unknown distribution type: ${e.type}`)}stopRuntimeMonitoring(e=!1){this.activeMonitor&&(this.activeMonitor.stop(),this.activeMonitor=null),e&&this.clearRuntimeDisplayState()}clearGrantExpiryTimer(){this.grantExpiryTimer&&(clearTimeout(this.grantExpiryTimer),this.grantExpiryTimer=void 0)}clearCapabilityRefreshTimer(){this.capabilityRefreshTimer&&(clearTimeout(this.capabilityRefreshTimer),this.capabilityRefreshTimer=void 0)}armCapabilityRefreshTimer(){if(this.clearCapabilityRefreshTimer(),this.destroyed||this.checkoutStarted)return;const e=this.stateAtom.get();if("routed"!==e.journeyStage)return;const t=q(this.experienceId,e.sequenceId);if(void 0===t)return;const i=Math.max(t-Date.now()-6e4,5e3);this.scheduleCapabilityRefresh(i)}scheduleCapabilityRefresh(e){if(this.clearCapabilityRefreshTimer(),this.destroyed||this.checkoutStarted)return;"routed"===this.stateAtom.get().journeyStage&&(this.capabilityRefreshTimer=setTimeout(()=>this.refreshCapabilityFromTimer(),e))}rearmTimersIfNeeded(){if(this.destroyed||this.checkoutStarted)return;const e=this.stateAtom.get();if("routed"===e.journeyStage&&this.armCapabilityRefreshTimer(),"granted"!==M(e)||this.grantExpiryTimer)return;const t=V(e);if(!t)return;const i=t-Date.now();i<=0?this.transitionToEnded({type:"expired"}):this.grantExpiryTimer=setTimeout(()=>this.expireGrantFromTimer(),i)}async refreshCapabilityFromTimer(){this.capabilityRefreshTimer=void 0;try{if("routed"!==this.stateAtom.get().journeyStage)return void this.clearCapabilityRefreshTimer();if(this.checkoutStarted)return;const e=Date.now();if(void 0!==this.lastCapabilityRefreshAtMs&&e-this.lastCapabilityRefreshAtMs<H)return void this.scheduleCapabilityRefresh(H);this.lastCapabilityRefreshAtMs=e,await this.retry(),this.consecutiveRefreshFailures=0}catch(e){if(this.dropStaleCallbackCommit(e,"capability refresh timer"))return;const t=Math.min(H*2**this.consecutiveRefreshFailures,6e5);this.consecutiveRefreshFailures+=1,this.emitEvent("error","warning","system","Capability grant refresh failed",{error:e instanceof Error?e.message:String(e),consecutiveFailures:this.consecutiveRefreshFailures}),this.scheduleCapabilityRefresh(t)}}expireGrantFromTimer(){try{this.transitionToEnded({type:"expired"})}catch(e){if(this.dropStaleCallbackCommit(e,"grant-expiry timer"))return;throw e}}dropStaleCallbackCommit(e,t){return!(!d(e)||e.code!==u.VALIDATION_ERROR||e.message!==$&&e.message!==Q)&&(this.logger.debug("Dropped stale journey callback",{experienceId:this.experienceId,source:t,message:e.message}),!0)}getRuntimeBinding(){const e=this.stateAtom.get(),t=M(e),i=k(e),s=G(e);return"participating"===t&&i&&s?{experienceId:this.experienceId,sequenceId:s,participationId:i.id,participationType:i.type}:null}startRuntimeMonitoring(){const e=this.getRuntimeBinding();if(!e)return;const t=this.activeMonitor;t&&t.id===e.participationId&&t.type===e.participationType&&this.displayAtom||(this.stopRuntimeMonitoring(!1),this.startMonitorForParticipation({id:e.participationId,type:e.participationType}))}getRuntimeDisplayAtom(){return this.displayAtom}applyRuntimeState(e){if(!e)return void this.clearRuntimeDisplayState();const t=this.getRuntimeBinding();if(!t)return;this.ensureDisplayAtom({id:t.participationId,type:t.participationType}).set(e)}reconcileSyncedSnapshot(e){if(this.destroyed)return;const t=this.getRuntimeBinding();if(!e)return this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),void this.setSnapshot(D(this.experienceId),{persist:!1});const i=qe(e);if(!i)return;const s=this.stateAtom.get();if(s.revision===i.revision&&s.updatedAt===i.updatedAt)return;const r=k(i),n=M(i),o="participating"===n&&r?{sequenceId:G(i),participationId:r.id,participationType:r.type}:null;o?t&&t.sequenceId===o.sequenceId&&t.participationId===o.participationId&&t.participationType===o.participationType||this.resetDisplayAtom(o.participationType):this.displayAtom=null,this.setSnapshot(i,{persist:!1}),"participating"===n&&r?this.ensureDisplayAtom(r):this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.rearmTimersIfNeeded()}ensureDisplayAtom(e){return this.resetDisplayAtom(e.type),this.displayAtom}resetDisplayAtom(t){this.displayAtom&&this.displayAtom.get().type===t?this.displayAtom.set({type:t}):this.displayAtom=e({type:t})}clearRuntimeDisplayState(){const e=this.getRuntimeBinding();e?this.resetDisplayAtom(e.participationType):this.displayAtom=null}clearRoutedRuntimeState(){this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.checkoutStarted=!1}deriveTerminalOutcomeForClosedDistribution(e,t,i){if(i)return i;if(!t||"closed"!==t.lifecycle||!e)return;const s=this.projectSequenceState({distribution:t,consumer:e});return"ended"===s.phase&&"lost"===s.outcome.type?s.outcome:{type:"closed",distributionType:t.type,distributionId:t.id}}withGrantedConsumer(e,t){switch(e?.mechanism){case"auction":return o(e,t);case"draw":return m(e,t);case"queue":return f(e,t);case"timed_release":return R(e,t);case void 0:switch(t.type){case"auction":return o(e,t);case"draw":return m(e,t);case"queue":return f(e,t);case"timed_release":return R(e,t);default:throw c.validationError("Distribution does not produce grants")}default:if(!e)throw c.validationError("Grant requires a consumer state");return e}}withTerminalConsumer(e,t){switch(e?.mechanism){case"appointment":return r(e,t);case"auction":return a(e,t);case"draw":return y(e,t);case"queue":return b(e,t);case"timed_release":return j(e,t);default:return e}}stopPolling(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=void 0)}}function X(e,t){if(e&&e.waitlistId===t.waitlistId)return e}const Y=i.picklist(["queue","draw","auction","timed_release","appointment"]),Z=Y,ee=i.picklist(["queue","draw","auction","timed_release"]),te=i.picklist(["draw","auction"]),ie=i.strictObject({id:i.string(),ts:i.number(),kind:i.picklist(["reroute","sequence_change","distribution_change","requirement","error","info"]),severity:i.picklist(["info","success","warning","error"]),audience:i.picklist(["user","system","analytics"]),message:i.string(),detail:i.optional(i.record(i.string(),i.unknown()))}),se=i.strictObject({id:i.string(),name:i.string(),experienceId:i.string(),priority:i.number(),color:i.string()}),re=i.strictObject({type:Y,id:i.string(),sequenceId:i.optional(i.string()),lifecycle:i.string(),startsAt:i.optional(i.string()),opensAt:i.optional(i.string()),closesAt:i.optional(i.string()),settlesAt:i.optional(i.string()),waitlistId:i.optional(i.string()),details:i.optional(i.unknown()),updatedAt:i.optional(i.number())}),ne=i.strictObject({status:i.string(),id:i.optional(i.string()),distributionId:i.optional(i.string()),waitlistId:i.optional(i.string()),enteredAt:i.optional(i.string()),admissionGrant:i.optional(i.string()),grantedAt:i.optional(i.string()),deniedReason:i.optional(i.string()),position:i.optional(i.number()),highestBid:i.optional(i.string()),currentBid:i.optional(i.string()),slotId:i.optional(i.string()),locationId:i.optional(i.string()),bookedAt:i.optional(i.string()),checkedInAt:i.optional(i.string()),completedAt:i.optional(i.string()),cancelledAt:i.optional(i.string()),noShowAt:i.optional(i.string()),notifiedAt:i.optional(i.string())}),oe=i.strictObject({token:i.string(),distributionType:ee,distributionId:i.string(),sequenceId:i.string(),experienceId:i.string(),issuedAt:i.optional(i.string()),expiresAt:i.optional(i.string()),handoffUrl:i.optional(i.string())}),ae=i.strictObject({type:i.string(),reason:i.optional(i.string()),at:i.optional(i.string()),distributionType:i.optional(Y),distributionId:i.optional(i.string())}),ce=i.strictObject({phase:i.literal("unavailable"),reason:i.optional(i.string())}),de=i.strictObject({waitlistId:i.string(),status:i.picklist(["not_waitlisted","waitlisted","notified"]),entryId:i.optional(i.string())}),ue=i.strictObject({phase:i.literal("scheduled"),mechanism:Z,distribution:re,waitlist:i.optional(de)}),he=i.strictObject({phase:i.literal("enterable"),mechanism:Z,distribution:re,consumer:ne}),pe=i.strictObject({phase:i.literal("participating"),mechanism:Z,distribution:re,consumer:ne}),le=i.strictObject({phase:i.literal("settling"),mechanism:te,distribution:re,consumer:ne}),me=i.strictObject({phase:i.literal("granted"),mechanism:ee,distribution:re,consumer:ne,grant:oe}),ye=i.strictObject({phase:i.literal("ended"),mechanism:Z,distribution:re,consumer:ne,outcome:ae}),ge=i.union([ce,ue,he,pe,le,me,ye]),fe={schemaVersion:i.literal(2),revision:i.number(),updatedAt:i.number(),experienceId:i.string(),accessCode:i.optional(i.string()),experience:i.optional(i.unknown()),events:i.array(ie),lastSeenEventId:i.optional(i.string())},be=i.strictObject({...fe,journeyStage:i.literal("ready")}),Ie=i.strictObject({...fe,journeyStage:i.literal("routing")}),ve=i.strictObject({...fe,journeyStage:i.literal("gated"),gates:i.array(i.unknown()),selected:i.optional(i.strictObject({sequence:i.unknown(),status:i.literal("gated")}))}),Se=i.strictObject({...fe,journeyStage:i.literal("routed"),sequenceId:i.string(),offers:i.array(i.unknown()),sequence:ge,sequenceRef:i.optional(se)}),Ae=i.union([be,Ie,ve,Se]);function qe(e){const t=i.safeParse(Ae,e);if(!t.success)return null;const{schemaVersion:s,...r}=t.output;if("routed"===r.journeyStage)try{W(r.sequence)}catch{return null}return r}export{K as ExperienceJourney};
1
+ import{atom as e,computed as t}from"nanostores";import*as i from"valibot";import{createAppointmentDriver as s,withAppointmentTerminalConsumer as r}from"../appointments/appointment.driver.js";import{createAuctionDriver as n,withAuctionGrantedConsumer as o,withAuctionTerminalConsumer as a}from"../auctions/auction.driver.js";import{createError as c,isFanfareError as d,ErrorCodes as u}from"../core/errors.js";import{createLogger as h}from"../core/logger.js";import{generateId as p}from"../core/utils.js";import{createDrawDriver as l,withDrawGrantedConsumer as m,withDrawTerminalConsumer as y}from"../draws/draw.driver.js";import{createQueueDriver as g,withQueueGrantedConsumer as f,withQueueTerminalConsumer as b}from"../queues/queue.driver.js";import{clearExperienceContext as I,clearSequenceContext as v,bindDistributionContext as S,upsertSequenceToken as A,getSequenceTokenExpiryMs as q}from"../state/capability-token-registry.js";import{getEventBus as w}from"../state/events.js";import{getSDKStore as x}from"../state/store.js";import{createTimedReleaseDriver as T,withTimedReleaseGrantedConsumer as R,withTimedReleaseTerminalConsumer as j}from"../timed-releases/timed-release.driver.js";import{NoSequenceAvailableError as C}from"./experience.module.js";import{buildJourneyView as E}from"./journey-view.js";import{createInitialSnapshot as D,getParticipation as k,getSequencePhase as M,normalizeSequenceState as O,buildSnapshot as F,getSequenceId as G,getDistribution as _,getConsumer as z,getWaitlistAttachment as B,getOutcome as U,getGrant as J,getSequenceState as P,getGates as L,createGrant as N,getAdmissionGrantExpiresAt as V,validateSequenceState as W}from"./journey.machine.js";const $="Cannot commit a stale sequence transition",Q="Cannot commit a sequence transition before routing",H=3e4;class K{constructor(i,s){this.experienceId=i,this.sdk=s,this.store=x(),this.logger=h(),this.activeMonitor=null,this.consecutiveRefreshFailures=0,this.checkoutStarted=!1,this.displayAtom=null,this.destroyed=!1,this.operationQueue=Promise.resolve(),this.EVENT_BUFFER_LIMIT=20;const r=this.restoreFromStore(),n=r??D(this.experienceId),o=r?k(r):void 0;o&&"participating"===M(n)&&this.ensureDisplayAtom(o),this.stateAtom=e(n),this.state=this.stateAtom,this.sequenceDrivers=this.createSequenceDrivers(),this.view$=t(this.stateAtom,e=>this.buildView(e)),this.events$=t(this.stateAtom,e=>e.events),this.latestEvent$=t(this.stateAtom,e=>{if(!e.events.length)return null;const t=e.lastSeenEventId??"",i=e.events.filter(e=>e.id>t);return i.length?i[i.length-1]:null}),r?this.rearmTimersIfNeeded():this.persist()}getExperienceId(){return this.experienceId}async start(e){await this.runSerializedOperation(async()=>{e?.reset&&(this.clearRoutedRuntimeState(),this.setSnapshot(D(this.experienceId))),"accessCode"in(e??{})&&this.patchJourneyBase({accessCode:e?.accessCode??null}),await this.routeAndLoadDistribution(this.stateAtom.get().accessCode)})}async reroute(e={}){await this.runSerializedOperation(async()=>{"accessCode"in e&&this.patchJourneyBase({accessCode:e.accessCode??null}),await this.routeAndLoadDistribution(e.accessCode??this.stateAtom.get().accessCode)})}async submitAccessCode(e){return await this.runSerializedOperation(async()=>{const t=this.stateAtom.get();if("routed"!==t.journeyStage){await this.routeAndLoadDistribution(e,{transient:!0});const t=this.stateAtom.get();return"routed"!==t.journeyStage?"gated"===t.journeyStage?{outcome:"gated"}:{outcome:"unchanged"}:(this.patchJourneyBase({accessCode:e}),{outcome:"advanced",sequenceId:t.sequenceId})}const i=t.sequenceId,s=t.accessCode,r=new Set(t.offers.map(e=>e.sequence.id));await this.routeAndLoadDistribution(e,{transient:!0});let n=this.stateAtom.get();return"routed"===n.journeyStage&&n.sequenceId===i?{outcome:"unchanged"}:"routed"===n.journeyStage&&r.has(n.sequenceId)?(this.patchJourneyBase({accessCode:e}),{outcome:"advanced",sequenceId:n.sequenceId}):("string"==typeof s&&s!==e&&(await this.routeAndLoadDistribution(s,{carryFrom:t,transient:!0}),n=this.stateAtom.get()),"gated"===n.journeyStage?{outcome:"gated"}:{outcome:"unchanged"})})}async retry(){await this.runSerializedOperation(()=>this.routeAndLoadDistribution(this.stateAtom.get().accessCode))}destroy(){this.destroyed||(this.destroyed=!0,this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.stopPolling(),I(this.experienceId),this.store.removeJourney(this.experienceId),this.stateAtom.set(D(this.experienceId)))}async resumeFromMe(e){const t=e.active.journeys.find(e=>e.experienceId===this.experienceId&&"routed"===e.journeyStage);if(!t||"routed"!==t.journeyStage)return!1;const i=this.stateAtom.get(),s=O({sequence:t.sequence}),r=F({base:this.baseFromSnapshot(i),journeyStage:"routed",sequenceId:t.sequenceId,offers:this.normalizeOffers(t.offers),sequence:s});this.setSnapshot(r),"routed"===i.journeyStage&&i.sequenceId!==t.sequenceId&&v(this.experienceId,i.sequenceId);try{await this.reroute()}catch{}return this.rearmTimersIfNeeded(),!0}async refreshDistribution(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=G(e);if(!t)throw c.validationError("Cannot refresh distribution before routing to a sequence");const i=await this.sdk.experiences.getCurrentDistributions(t),s=this.buildDistributionSummary(i,t),r=_(e);s&&S({experienceId:this.experienceId,sequenceId:t,distributionContext:s});const n=z(e),o="scheduled"===s?.lifecycle?X(B(e),s):void 0,a=this.deriveTerminalOutcomeForClosedDistribution(n,s,U(e)),d=this.withTerminalConsumer(n,a),u=this.projectSequenceState({distribution:s,consumer:d,waitlist:o,grant:J(e),outcome:a});this.commit(u),this.emitIfDistributionChanged(r,s)})}async joinWaitlistAttachment(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=P(e),i=_(e),s="scheduled"===t?.phase&&"waitlist"in t?t.waitlist?.waitlistId:i?.waitlistId,r="string"==typeof s?s:void 0;if("scheduled"!==t?.phase||!i||!r)throw c.validationError("Waitlist is not available for this sequence");const n=await this.sdk.waitlists.enter(r),o=this.projectSequenceState({distribution:i,waitlist:{waitlistId:r,status:"waitlisted",entryId:n.id}});this.commit(o),this.emitEvent("sequence_change","info","user","Entered waitlist",{waitlistId:r})})}async exitWaitlistAttachment(){await this.runSerializedOperation(async()=>{const e=this.stateAtom.get(),t=P(e),i=_(e),s="scheduled"===t?.phase&&"waitlist"in t?t.waitlist:void 0,r=s?.waitlistId??i?.waitlistId;if("scheduled"!==t?.phase||!i||!r)return;await this.sdk.waitlists.leave(r);const n=this.projectSequenceState({distribution:i,waitlist:{waitlistId:r,status:"not_waitlisted"}});this.commit(n),this.emitEvent("sequence_change","info","user","Left waitlist",{waitlistId:r})})}claim(){const e=J(this.stateAtom.get());if(!e)throw c.validationError("No grant is available to claim");return this.checkoutStarted=!0,this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.emitEvent("sequence_change","success","user","Grant claimed",{distributionType:e.distributionType,distributionId:e.distributionId,sequenceId:e.sequenceId}),w().emit("journey:grant-claimed",{experienceId:this.experienceId,sequenceId:e.sequenceId,grant:e.token}),e}ackEvent(e){this.stateAtom.get().events.some(t=>t.id===e)&&this.patchJourneyBase({lastSeenEventId:e})}ackAllEvents(){const e=this.stateAtom.get();if(!e.events.length)return;const t=e.events[e.events.length-1];this.patchJourneyBase({lastSeenEventId:t.id})}async routeAndLoadDistribution(e,t){const i=t?.carryFrom??this.stateAtom.get(),s=G(i),r=await this.resolveRouting(e,{transient:t?.transient});if(!r||this.destroyed)return;if(await this.enterExperienceIfReady(),this.destroyed)return;const n=await this.sdk.experiences.getCurrentDistributions(r.sequenceId);if(this.destroyed)return;const o=this.buildDistributionSummary(n,r.sequenceId);o&&S({experienceId:this.experienceId,sequenceId:r.sequenceId,distributionContext:o});const a=s===r.sequenceId,c=a?z(i):void 0,d=a&&"scheduled"===o?.lifecycle?X(B(i),o):void 0,u=a?J(i):void 0,h=a?this.deriveTerminalOutcomeForClosedDistribution(c,o,U(i)):void 0,p=this.withTerminalConsumer(c,h),l=!o&&a&&"routed"===i.journeyStage&&("granted"===i.sequence.phase||"ended"===i.sequence.phase)?i.sequence:this.projectSequenceState({distribution:o,consumer:p,waitlist:d,grant:u,outcome:h});a||(this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer());const m=this.stateAtom.get(),y=F({base:this.baseFromSnapshot(m),journeyStage:"routed",sequenceId:r.sequenceId,offers:r.offers,sequence:l,sequenceRef:r.selectedSequence});this.setSnapshot(y),this.consecutiveRefreshFailures=0,this.rearmTimersIfNeeded();const g=a?_(i):void 0;this.emitIfDistributionChanged(g,o)}async resolveRouting(e,t){const i=this.stateAtom.get(),s=G(i);let r;this.transitionToRouting();try{r=await this.sdk.experiences.findSequence(this.experienceId,e)}catch(o){throw this.setSnapshot(i),"routed"===i.journeyStage&&this.armCapabilityRefreshTimer(),this.emitEvent("error","error","user","Failed to route experience"),o}if(this.destroyed)return null;if("gated"===r.outcome&&r.selected){const e=r.selected.gates;return"routed"===i.journeyStage&&this.clearRoutedRuntimeState(),this.transitionToGated({gates:e,selected:{sequence:r.selected.sequence,status:"gated"}}),this.emitEvent("requirement","info","user","Gates required",{gates:e}),null}if("no_access"===r.outcome||!r.selected)throw this.setSnapshot(i),this.emitEvent("error","error","user","No access available for this experience"),new C("No access available for this experience");const n=r.selected.sequence.id;if(s&&s!==n&&v(this.experienceId,s),r.selected.capabilityGrant&&A({experienceId:this.experienceId,sequenceId:n,token:r.selected.capabilityGrant,expiresAt:r.selected.capabilityGrantExpiresAt}),s!==n){const e="routed"===i.journeyStage?i.sequenceRef?.priority:void 0,o=r.selected.sequence.priority,a=function(e,t){return void 0===e?"unknown":t>e?"upgrade":t<e?"downgrade":"lateral"}(e,o);this.emitEvent("reroute","downgrade"===a?"warning":"info",t?.transient?"system":"user","downgrade"===a?"Sequence downgraded":"Sequence changed",{previousSequenceId:s,sequenceId:n,...void 0===e?{}:{previousPriority:e},priority:o,direction:a})}return{sequenceId:n,selectedSequence:r.selected.sequence,offers:r.offers}}async enterExperienceIfReady(){const e=this.stateAtom.get();if(L(e).length>0)return;if(e.experience)return;const t=await this.sdk.experiences.enter(this.experienceId);this.destroyed||this.patchJourneyBase({experience:t})}createSequenceDrivers(){return{appointment:s({appointments:this.sdk.appointments,runSerializedOperation:e=>this.runSerializedOperation(e),getCurrentSequence:()=>P(this.stateAtom.get()),getDisplayState:()=>this.displayAtom?.get()??null,commit:(e,t)=>this.commit(e,t),applyMonitorUpdate:e=>this.applyMonitorUpdate(e),ensureDisplayAtom:e=>this.ensureDisplayAtom(e),stopRuntimeMonitoring:e=>this.stopRuntimeMonitoring(e),emitEvent:(e,t,i,s,r)=>this.emitEvent(e,t,i,s,r)}),auction:n({auctions:this.sdk.auctions,runSerializedOperation:e=>this.runSerializedOperation(e),getCurrentSequence:()=>P(this.stateAtom.get()),getDisplayState:()=>this.displayAtom?.get()??null,commit:(e,t)=>this.commit(e,t),applyMonitorUpdate:e=>this.applyMonitorUpdate(e),ensureDisplayAtom:e=>this.ensureDisplayAtom(e),stopRuntimeMonitoring:e=>this.stopRuntimeMonitoring(e),emitEvent:(e,t,i,s,r)=>this.emitEvent(e,t,i,s,r)}),queue:g({queues:this.sdk.queues,runSerializedOperation:e=>this.runSerializedOperation(e),getCurrentSequence:()=>P(this.stateAtom.get()),commit:(e,t)=>this.commit(e,t),applyMonitorUpdate:e=>this.applyMonitorUpdate(e),ensureDisplayAtom:e=>{this.ensureDisplayAtom(e)},stopRuntimeMonitoring:e=>this.stopRuntimeMonitoring(e),emitEvent:(e,t,i,s,r)=>this.emitEvent(e,t,i,s,r)}),timed_release:T({timedReleases:this.sdk.timedReleases,runSerializedOperation:e=>this.runSerializedOperation(e),getCurrentSequence:()=>P(this.stateAtom.get()),commit:(e,t)=>this.commit(e,t),applyMonitorUpdate:e=>this.applyMonitorUpdate(e),ensureDisplayAtom:e=>{this.ensureDisplayAtom(e)},stopRuntimeMonitoring:e=>this.stopRuntimeMonitoring(e),emitEvent:(e,t,i,s,r)=>this.emitEvent(e,t,i,s,r)}),draw:l({draws:this.sdk.draws,runSerializedOperation:e=>this.runSerializedOperation(e),getCurrentSequence:()=>P(this.stateAtom.get()),commit:(e,t)=>this.commit(e,t),applyMonitorUpdate:e=>this.applyMonitorUpdate(e),ensureDisplayAtom:e=>{this.ensureDisplayAtom(e)},stopRuntimeMonitoring:e=>this.stopRuntimeMonitoring(e),emitEvent:(e,t,i,s,r)=>this.emitEvent(e,t,i,s,r)})}}buildView(e){return E(e,{start:e=>this.start(e),reroute:e=>this.reroute(e),submitAccessCode:e=>this.submitAccessCode(e),retry:()=>this.retry(),joinWaitlist:()=>this.joinWaitlistAttachment(),exitWaitlist:()=>this.exitWaitlistAttachment(),claim:()=>this.claim()},this.displayAtom,this.sequenceDrivers)}projectSequenceState(e){if(!e.distribution)return{phase:"unavailable"};const t=this.sequenceDrivers[e.distribution.type];if(!t)throw new Error(`${e.distribution.type} sequence driver is required`);return t.project(e)}patchJourneyBase(e){if(this.destroyed)return;const t=this.stateAtom.get(),i=this.baseFromSnapshot(t,e);switch(t.journeyStage){case"ready":return void this.setSnapshot(F({base:i,journeyStage:"ready"}));case"routing":return void this.setSnapshot(F({base:i,journeyStage:"routing"}));case"gated":return void this.setSnapshot(F({base:i,journeyStage:"gated",gates:t.gates,selected:t.selected}));case"routed":return void this.setSnapshot(F({base:i,journeyStage:"routed",sequenceId:t.sequenceId,offers:t.offers,sequence:t.sequence,sequenceRef:t.sequenceRef}))}}transitionToRouting(){if(this.destroyed)return;const e=this.stateAtom.get();this.setSnapshot(F({base:this.baseFromSnapshot(e),journeyStage:"routing"}))}transitionToGated(e){if(this.destroyed)return;const t=this.stateAtom.get();this.setSnapshot(F({base:this.baseFromSnapshot(t),journeyStage:"gated",gates:e.gates,selected:e.selected}))}baseFromSnapshot(e,t={}){const i=Object.prototype.hasOwnProperty.call(t,"accessCode")?t.accessCode??void 0:e.accessCode;return{revision:e.revision,updatedAt:e.updatedAt,experienceId:e.experienceId,accessCode:i,experience:t.experience??e.experience,events:t.events??e.events,lastSeenEventId:t.lastSeenEventId??e.lastSeenEventId}}commit(e,t={}){if(this.destroyed)throw c.notInitialized("Journey has been destroyed");const i=this.stateAtom.get();if("routed"!==i.journeyStage)throw c.validationError(Q);if(!function(e,t){if("distribution"in e&&e.distribution.sequenceId&&e.distribution.sequenceId!==t)return!1;return!0}(e,i.sequenceId))throw c.validationError($);const s=F({base:{revision:i.revision,updatedAt:i.updatedAt,experienceId:i.experienceId,accessCode:i.accessCode,experience:i.experience,events:t.events??i.events,lastSeenEventId:i.lastSeenEventId},journeyStage:"routed",sequenceId:i.sequenceId,offers:i.offers,sequence:e,sequenceRef:i.sequenceRef});this.setSnapshot(s)}emitEvent(e,t,i,s,r){const n=this.stateAtom.get(),o={id:p(),ts:Date.now(),kind:e,severity:t,audience:i,message:s,detail:r},a=[...n.events,o],c=a.length>this.EVENT_BUFFER_LIMIT?a.slice(a.length-this.EVENT_BUFFER_LIMIT):a;this.patchJourneyBase({events:c})}emitIfDistributionChanged(e,t){e?.id===t?.id&&e?.lifecycle===t?.lifecycle||this.emitEvent("distribution_change","info","user","Distribution context changed",{previous:e,next:t})}buildDistributionSummary(e,t){return e.active?{type:e.active.type,id:e.active.id,sequenceId:t,lifecycle:"open",details:e.active.details,opensAt:e.active.details.openAt,closesAt:e.active.details.closeAt,updatedAt:Date.now()}:e.upcoming?{type:e.upcoming.type,id:e.upcoming.id,sequenceId:t,lifecycle:"scheduled",startsAt:e.upcoming.startsAt,opensAt:e.upcoming.startsAt,waitlistId:e.upcoming.waitlistId,updatedAt:Date.now()}:e.recentPast?{type:e.recentPast.type,id:e.recentPast.id,sequenceId:t,lifecycle:"closed",closesAt:e.recentPast.endedAt,updatedAt:Date.now()}:void 0}normalizeOffers(e){return Array.isArray(e)?e.filter(Boolean):[]}restoreFromStore(){const e=this.store.activeJourneys;if(!e||!e[this.experienceId])return null;const t=qe(e[this.experienceId]);return t||(this.store.removeJourney(this.experienceId),null)}persist(){this.store.setJourneyState(this.experienceId,this.stateAtom.get())}setSnapshot(e,t={}){if(this.destroyed)return;const i=this.stateAtom.get();this.stateAtom.set(e),!1!==t.persist&&this.persist(),this.emitPhaseChanged(i,e)}emitPhaseChanged(e,t){const i=e.journeyStage,s="routed"===i?M(e):void 0,r=t.journeyStage,n="routed"===r?M(t):void 0;i===r&&s===n||w().emit("journey:phase-changed",{experienceId:this.experienceId,journeyStage:r,sequencePhase:n})}runSerializedOperation(e){const t=this.operationQueue.then(async()=>{if(this.destroyed)throw c.notInitialized("Journey has been destroyed");return e()});return this.operationQueue=t.then(()=>{},()=>{}),t}transitionToGranted(e,t){if(this.destroyed)return;const i=this.stateAtom.get(),s=_(i),r=G(i);if(!s||!r)return void this.transitionToEnded({type:"closed",reason:"missing_distribution"});if(void 0!==t&&Date.now()>=t)return void this.transitionToEnded({type:"expired"});const n=N({token:e,distribution:s,sequenceId:r,experienceId:this.experienceId,expiresAt:t}),o=this.withGrantedConsumer(z(i),s),a=this.projectSequenceState({distribution:s,consumer:o,grant:n});this.commit(a),this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.checkoutStarted=!1,void 0!==t&&(this.grantExpiryTimer=setTimeout(()=>this.expireGrantFromTimer(),t-Date.now())),this.emitEvent("sequence_change","success","user","Grant issued",{grant:e}),w().emit("journey:granted",{experienceId:this.experienceId,sequenceId:r,grant:e})}transitionToEnded(e){if(this.destroyed)return;const t=this.stateAtom.get(),i=_(t),s=this.reinterpretLapsedGrantOutcome(t,e),r={...s,distributionType:s.distributionType??i?.type,distributionId:s.distributionId??i?.id,at:s.at??/* @__PURE__ */(new Date).toISOString()},n=this.withTerminalConsumer(z(t),r),o=this.projectSequenceState({distribution:i,consumer:n,outcome:r});this.commit(o),this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.emitEvent("sequence_change","expired"===r.type?"warning":"info","user","Sequence ended",{outcome:r}),w().emit("journey:ended",{experienceId:this.experienceId,sequenceId:G(this.stateAtom.get()),outcome:r})}reinterpretLapsedGrantOutcome(e,t){if("granted"!==M(e))return t;return"closed"===t.type&&("not_queued"===t.reason||"not_entered"===t.reason)?{type:"expired",at:t.at,distributionType:t.distributionType,distributionId:t.distributionId}:t}applyMonitorUpdate(e){this.destroyed||("granted"!==e.type?this.transitionToEnded(e.outcome):this.transitionToGranted(e.token,e.expiresAt))}applyMonitorUpdateFromCallback(e){try{this.applyMonitorUpdate(e)}catch(t){if(this.dropStaleCallbackCommit(t,"monitor"))return;throw t}}startMonitorForParticipation(e){const t=this.ensureDisplayAtom(e),i=e=>this.applyMonitorUpdateFromCallback(e),s=this.sequenceDrivers[e.type];if(s?.startMonitoring)return s.startMonitoring({participation:e,displayAtom:t,onUpdate:i}),void(this.activeMonitor={type:e.type,id:e.id,stop:()=>s.stopMonitoring?.()});throw new Error(`Unknown distribution type: ${e.type}`)}stopRuntimeMonitoring(e=!1){this.activeMonitor&&(this.activeMonitor.stop(),this.activeMonitor=null),e&&this.clearRuntimeDisplayState()}clearGrantExpiryTimer(){this.grantExpiryTimer&&(clearTimeout(this.grantExpiryTimer),this.grantExpiryTimer=void 0)}clearCapabilityRefreshTimer(){this.capabilityRefreshTimer&&(clearTimeout(this.capabilityRefreshTimer),this.capabilityRefreshTimer=void 0)}armCapabilityRefreshTimer(){if(this.clearCapabilityRefreshTimer(),this.destroyed||this.checkoutStarted)return;const e=this.stateAtom.get();if("routed"!==e.journeyStage)return;const t=q(this.experienceId,e.sequenceId);if(void 0===t)return;const i=Math.max(t-Date.now()-6e4,5e3);this.scheduleCapabilityRefresh(i)}scheduleCapabilityRefresh(e){if(this.clearCapabilityRefreshTimer(),this.destroyed||this.checkoutStarted)return;"routed"===this.stateAtom.get().journeyStage&&(this.capabilityRefreshTimer=setTimeout(()=>this.refreshCapabilityFromTimer(),e))}rearmTimersIfNeeded(){if(this.destroyed||this.checkoutStarted)return;const e=this.stateAtom.get();if("routed"===e.journeyStage&&this.armCapabilityRefreshTimer(),"granted"!==M(e)||this.grantExpiryTimer)return;const t=V(e);if(!t)return;const i=t-Date.now();i<=0?this.transitionToEnded({type:"expired"}):this.grantExpiryTimer=setTimeout(()=>this.expireGrantFromTimer(),i)}async refreshCapabilityFromTimer(){this.capabilityRefreshTimer=void 0;try{if("routed"!==this.stateAtom.get().journeyStage)return void this.clearCapabilityRefreshTimer();if(this.checkoutStarted)return;const e=Date.now();if(void 0!==this.lastCapabilityRefreshAtMs&&e-this.lastCapabilityRefreshAtMs<H)return void this.scheduleCapabilityRefresh(H);this.lastCapabilityRefreshAtMs=e,await this.retry(),this.consecutiveRefreshFailures=0}catch(e){if(this.dropStaleCallbackCommit(e,"capability refresh timer"))return;const t=Math.min(H*2**this.consecutiveRefreshFailures,6e5);this.consecutiveRefreshFailures+=1,this.emitEvent("error","warning","system","Capability grant refresh failed",{error:e instanceof Error?e.message:String(e),consecutiveFailures:this.consecutiveRefreshFailures}),this.scheduleCapabilityRefresh(t)}}expireGrantFromTimer(){try{this.transitionToEnded({type:"expired"})}catch(e){if(this.dropStaleCallbackCommit(e,"grant-expiry timer"))return;throw e}}dropStaleCallbackCommit(e,t){return!(!d(e)||e.code!==u.VALIDATION_ERROR||e.message!==$&&e.message!==Q)&&(this.logger.debug("Dropped stale journey callback",{experienceId:this.experienceId,source:t,message:e.message}),!0)}getRuntimeBinding(){const e=this.stateAtom.get(),t=M(e),i=k(e),s=G(e);return"participating"===t&&i&&s?{experienceId:this.experienceId,sequenceId:s,participationId:i.id,participationType:i.type}:null}startRuntimeMonitoring(){const e=this.getRuntimeBinding();if(!e)return;const t=this.activeMonitor;t&&t.id===e.participationId&&t.type===e.participationType&&this.displayAtom||(this.stopRuntimeMonitoring(!1),this.startMonitorForParticipation({id:e.participationId,type:e.participationType}))}getRuntimeDisplayAtom(){return this.displayAtom}applyRuntimeState(e){if(!e)return void this.clearRuntimeDisplayState();const t=this.getRuntimeBinding();if(!t)return;this.ensureDisplayAtom({id:t.participationId,type:t.participationType}).set(e)}reconcileSyncedSnapshot(e){if(this.destroyed)return;const t=this.getRuntimeBinding();if(!e)return this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),void this.setSnapshot(D(this.experienceId),{persist:!1});const i=qe(e);if(!i)return;const s=this.stateAtom.get();if(s.revision===i.revision&&s.updatedAt===i.updatedAt)return;const r=k(i),n=M(i),o="participating"===n&&r?{sequenceId:G(i),participationId:r.id,participationType:r.type}:null;o?t&&t.sequenceId===o.sequenceId&&t.participationId===o.participationId&&t.participationType===o.participationType||this.resetDisplayAtom(o.participationType):this.displayAtom=null,this.setSnapshot(i,{persist:!1}),"participating"===n&&r?this.ensureDisplayAtom(r):this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.rearmTimersIfNeeded()}ensureDisplayAtom(e){return this.resetDisplayAtom(e.type),this.displayAtom}resetDisplayAtom(t){this.displayAtom&&this.displayAtom.get().type===t?this.displayAtom.set({type:t}):this.displayAtom=e({type:t})}clearRuntimeDisplayState(){const e=this.getRuntimeBinding();e?this.resetDisplayAtom(e.participationType):this.displayAtom=null}clearRoutedRuntimeState(){this.stopRuntimeMonitoring(!0),this.clearGrantExpiryTimer(),this.clearCapabilityRefreshTimer(),this.checkoutStarted=!1}deriveTerminalOutcomeForClosedDistribution(e,t,i){if(i)return i;if(!t||"closed"!==t.lifecycle||!e)return;const s=this.projectSequenceState({distribution:t,consumer:e});return"ended"===s.phase&&"lost"===s.outcome.type?s.outcome:{type:"closed",distributionType:t.type,distributionId:t.id}}withGrantedConsumer(e,t){switch(e?.mechanism){case"auction":return o(e,t);case"draw":return m(e,t);case"queue":return f(e,t);case"timed_release":return R(e,t);case void 0:switch(t.type){case"auction":return o(e,t);case"draw":return m(e,t);case"queue":return f(e,t);case"timed_release":return R(e,t);default:throw c.validationError("Distribution does not produce grants")}default:if(!e)throw c.validationError("Grant requires a consumer state");return e}}withTerminalConsumer(e,t){switch(e?.mechanism){case"appointment":return r(e,t);case"auction":return a(e,t);case"draw":return y(e,t);case"queue":return b(e,t);case"timed_release":return j(e,t);default:return e}}stopPolling(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=void 0)}}function X(e,t){if(e&&e.waitlistId===t.waitlistId)return e}const Y=i.picklist(["queue","draw","auction","timed_release","appointment"]),Z=Y,ee=i.picklist(["queue","draw","auction","timed_release"]),te=i.picklist(["draw","auction"]),ie=i.strictObject({id:i.string(),ts:i.number(),kind:i.picklist(["reroute","sequence_change","distribution_change","requirement","error","info"]),severity:i.picklist(["info","success","warning","error"]),audience:i.picklist(["user","system","analytics"]),message:i.string(),detail:i.optional(i.record(i.string(),i.unknown()))}),se=i.strictObject({id:i.string(),name:i.string(),experienceId:i.string(),priority:i.number(),color:i.string()}),re=i.strictObject({type:Y,id:i.string(),sequenceId:i.optional(i.string()),lifecycle:i.string(),startsAt:i.optional(i.string()),opensAt:i.optional(i.string()),closesAt:i.optional(i.string()),settlesAt:i.optional(i.string()),waitlistId:i.optional(i.string()),details:i.optional(i.unknown()),updatedAt:i.optional(i.number())}),ne=i.strictObject({status:i.string(),id:i.optional(i.string()),distributionId:i.optional(i.string()),waitlistId:i.optional(i.string()),enteredAt:i.optional(i.string()),admissionGrant:i.optional(i.string()),grantedAt:i.optional(i.string()),deniedReason:i.optional(i.string()),position:i.optional(i.number()),highestBid:i.optional(i.string()),currentBid:i.optional(i.string()),slotId:i.optional(i.string()),locationId:i.optional(i.string()),bookedAt:i.optional(i.string()),checkedInAt:i.optional(i.string()),completedAt:i.optional(i.string()),cancelledAt:i.optional(i.string()),noShowAt:i.optional(i.string()),notifiedAt:i.optional(i.string())}),oe=i.strictObject({token:i.string(),distributionType:ee,distributionId:i.string(),sequenceId:i.string(),experienceId:i.string(),issuedAt:i.optional(i.string()),expiresAt:i.optional(i.string()),handoffUrl:i.optional(i.string())}),ae=i.strictObject({type:i.string(),reason:i.optional(i.string()),at:i.optional(i.string()),distributionType:i.optional(Y),distributionId:i.optional(i.string())}),ce=i.strictObject({phase:i.literal("unavailable"),reason:i.optional(i.string())}),de=i.strictObject({waitlistId:i.string(),status:i.picklist(["not_waitlisted","waitlisted","notified"]),entryId:i.optional(i.string())}),ue=i.strictObject({phase:i.literal("scheduled"),mechanism:Z,distribution:re,waitlist:i.optional(de)}),he=i.strictObject({phase:i.literal("enterable"),mechanism:Z,distribution:re,consumer:ne}),pe=i.strictObject({phase:i.literal("participating"),mechanism:Z,distribution:re,consumer:ne}),le=i.strictObject({phase:i.literal("settling"),mechanism:te,distribution:re,consumer:ne}),me=i.strictObject({phase:i.literal("granted"),mechanism:ee,distribution:re,consumer:ne,grant:oe}),ye=i.strictObject({phase:i.literal("ended"),mechanism:Z,distribution:re,consumer:ne,outcome:ae}),ge=i.union([ce,ue,he,pe,le,me,ye]),fe={schemaVersion:i.literal(2),revision:i.number(),updatedAt:i.number(),experienceId:i.string(),accessCode:i.optional(i.string()),experience:i.optional(i.unknown()),events:i.array(ie),lastSeenEventId:i.optional(i.string())},be=i.strictObject({...fe,journeyStage:i.literal("ready")}),Ie=i.strictObject({...fe,journeyStage:i.literal("routing")}),ve=i.strictObject({...fe,journeyStage:i.literal("gated"),gates:i.array(i.unknown()),selected:i.optional(i.strictObject({sequence:i.unknown(),status:i.literal("gated")}))}),Se=i.strictObject({...fe,journeyStage:i.literal("routed"),sequenceId:i.string(),offers:i.array(i.unknown()),sequence:ge,sequenceRef:i.optional(se)}),Ae=i.union([be,Ie,ve,Se]);function qe(e){const t=i.safeParse(Ae,e);if(!t.success)return null;const{schemaVersion:s,...r}=t.output;if("routed"===r.journeyStage)try{W(r.sequence)}catch{return null}return r}export{K as ExperienceJourney};
@@ -13,3 +13,26 @@ export declare function buildAdmissionProofHeaders(params: {
13
13
  method?: string;
14
14
  nonce?: string;
15
15
  }): Promise<AdmissionProofHeaders | null>;
16
+ /**
17
+ * Read the persisted DPoP-bound refresh token, or null if none is stored or
18
+ * IndexedDB is unavailable.
19
+ */
20
+ export declare function loadRefreshToken(): Promise<string | null>;
21
+ /**
22
+ * Persist the rotated refresh token. Returns false (without throwing) if the
23
+ * write could not be made durable, so the caller can decide whether to continue
24
+ * with an in-memory-only token.
25
+ */
26
+ export declare function persistRefreshToken(token: string): Promise<boolean>;
27
+ /**
28
+ * Remove any persisted refresh token (logout / forced re-auth). Best-effort:
29
+ * swallows errors so logout never fails on a storage hiccup.
30
+ */
31
+ export declare function clearRefreshToken(): Promise<void>;
32
+ /**
33
+ * One-time cleanup of the pre-memory-only build's durable bearer record: older SDKs
34
+ * persisted access/beacon tokens under this key in the refresh store. The current
35
+ * build never writes it, so delete any leftover from an upgraded client to keep the
36
+ * memory-only invariant true at rest. Best-effort.
37
+ */
38
+ export declare function clearLegacySessionTokens(): Promise<void>;
@@ -1 +1 @@
1
- import{generateId as n}from"../core/utils.js";const t="X-Admission-Key-Thumbprint",e="DPoP",r="DPoP-Nonce",o="fanfare-admission-proof",c="keys",i="current";let a=null,u=null;function l(n){const t=new URL(n);return t.search="",t.hash="",t.toString()}async function s(){const n=await p();return n?.thumbprint??null}async function y(o){const c=await p();if(!c)return null;const i=o.method?.toUpperCase()??"POST",a={typ:"dpop+jwt",alg:"ES256",jwk:c.publicJwk},u={htm:i,htu:l(o.url),iat:Math.floor(Date.now()/1e3),jti:n(),...o.nonce?{nonce:o.nonce}:{}},s=`${d(a)}.${d(u)}`,y=await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},c.privateKey,(new TextEncoder).encode(s));return{[t]:c.thumbprint,[e]:`${s}.${b(new Uint8Array(y))}`,...o.nonce?{[r]:o.nonce}:{}}}async function p(){if(u)return u;if(a)return await a;a=(async()=>{if("undefined"==typeof window||"undefined"==typeof document||!globalThis.crypto?.subtle)return null;const n=await async function(){if(!("indexedDB"in globalThis))return null;return await new Promise(n=>{try{const t=indexedDB.open(o,1);t.onupgradeneeded=()=>{const n=t.result;n.objectStoreNames.contains(c)||n.createObjectStore(c)},t.onsuccess=()=>n(t.result),t.onerror=()=>n(null),t.onblocked=()=>n(null)}catch{n(null)}})}();if(!n)return null;try{const t=await async function(n){try{const t=await f(n.transaction(c).objectStore(c).get(i));return t?.privateKey&&t.publicKey&&t.publicJwk&&t.thumbprint?t:null}catch{return null}}(n);if(t)return u=t,t;const e=await async function(){const n=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!1,["sign","verify"]),t=await crypto.subtle.exportKey("jwk",n.publicKey);return{privateKey:n.privateKey,publicKey:n.publicKey,publicJwk:t,thumbprint:await w(t)}}(),r=await async function(n,t){try{return await f(n.transaction(c,"readwrite").objectStore(c).put(t,i)),!0}catch{return!1}}(n,e);return r?(u=e,e):null}finally{n.close()}})();try{return await a}finally{a=null}}function f(n){return new Promise((t,e)=>{n.onsuccess=()=>t(n.result),n.onerror=()=>e(n.error??new Error("IndexedDB request failed"))})}async function w(n){const t=JSON.stringify({crv:n.crv,kty:n.kty,x:n.x,y:n.y}),e=await crypto.subtle.digest("SHA-256",(new TextEncoder).encode(t));return b(new Uint8Array(e))}function d(n){return b((new TextEncoder).encode(JSON.stringify(n)))}function b(n){let t="";for(const e of n)t+=String.fromCharCode(e);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}export{t as ADMISSION_KEY_THUMBPRINT_HEADER,e as DPOP_HEADER,r as DPOP_NONCE_HEADER,y as buildAdmissionProofHeaders,l as canonicalizeAdmissionProofUrl,s as getAdmissionKeyThumbprint};
1
+ import{generateId as t}from"../core/utils.js";const n="X-Admission-Key-Thumbprint",e="DPoP",r="DPoP-Nonce",o="keys",a="current",c="refresh",i="current";let u=null,s=null;function l(t){const n=new URL(t);return n.search="",n.hash="",n.toString()}async function y(){const t=await w();return t?.thumbprint??null}async function f(o){const a=await w();if(!a)return null;const c=o.method?.toUpperCase()??"POST",i={typ:"dpop+jwt",alg:"ES256",jwk:a.publicJwk},u={htm:c,htu:l(o.url),iat:Math.floor(Date.now()/1e3),jti:t(),...o.nonce?{nonce:o.nonce}:{}},s=`${K(i)}.${K(u)}`,y=await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},a.privateKey,(new TextEncoder).encode(s));return{[n]:a.thumbprint,[e]:`${s}.${k(new Uint8Array(y))}`,...o.nonce?{[r]:o.nonce}:{}}}async function w(){if(s)return s;if(u)return await u;u=(async()=>{if("undefined"==typeof window||"undefined"==typeof document||!globalThis.crypto?.subtle)return null;const t=await g();if(!t)return null;try{const n=await async function(t){try{const n=await S(t.transaction(o).objectStore(o).get(a));return n?.privateKey&&n.publicKey&&n.publicJwk&&n.thumbprint?n:null}catch{return null}}(t);if(n)return s=n,n;const e=await async function(){const t=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!1,["sign","verify"]),n=await crypto.subtle.exportKey("jwk",t.publicKey);return{privateKey:t.privateKey,publicKey:t.publicKey,publicJwk:n,thumbprint:await j(n)}}(),r=await async function(t,n){try{return await S(t.transaction(o,"readwrite").objectStore(o).put(n,a)),!0}catch{return!1}}(t,e);return r?(s=e,e):null}finally{t.close()}})();try{return await u}finally{u=null}}async function p(t){const n=await g();if(n)try{await S(n.transaction(c,"readwrite").objectStore(c).delete(t))}catch{}finally{n.close()}}async function d(){const t=await async function(t){const n=await g();if(!n)return null;try{return await S(n.transaction(c).objectStore(c).get(t))}catch{return null}finally{n.close()}}(i);return"string"==typeof t&&t.length>0?t:null}async function b(t){return async function(t,n){const e=await g();if(!e)return!1;try{return await S(e.transaction(c,"readwrite").objectStore(c).put(n,t)),!0}catch{return!1}finally{e.close()}}(i,t)}async function h(){await p(i)}async function m(){await p("session-tokens")}async function g(){return"indexedDB"in globalThis?await new Promise(t=>{try{const n=indexedDB.open("fanfare-admission-proof",2);n.onupgradeneeded=()=>{const t=n.result;t.objectStoreNames.contains(o)||t.createObjectStore(o),t.objectStoreNames.contains(c)||t.createObjectStore(c)},n.onsuccess=()=>t(n.result),n.onerror=()=>t(null),n.onblocked=()=>t(null)}catch{t(null)}}):null}function S(t){return new Promise((n,e)=>{t.onsuccess=()=>n(t.result),t.onerror=()=>e(t.error??new Error("IndexedDB request failed"))})}async function j(t){const n=JSON.stringify({crv:t.crv,kty:t.kty,x:t.x,y:t.y}),e=await crypto.subtle.digest("SHA-256",(new TextEncoder).encode(n));return k(new Uint8Array(e))}function K(t){return k((new TextEncoder).encode(JSON.stringify(t)))}function k(t){let n="";for(const e of t)n+=String.fromCharCode(e);return btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}export{n as ADMISSION_KEY_THUMBPRINT_HEADER,e as DPOP_HEADER,r as DPOP_NONCE_HEADER,f as buildAdmissionProofHeaders,l as canonicalizeAdmissionProofUrl,m as clearLegacySessionTokens,h as clearRefreshToken,y as getAdmissionKeyThumbprint,d as loadRefreshToken,b as persistRefreshToken};
@@ -15,6 +15,7 @@ export interface SDKState {
15
15
  distributionCapabilityIndex: Record<string, DistributionCapabilityBinding>;
16
16
  versionMap: Record<string, number>;
17
17
  setSession: (session: Session | null) => void;
18
+ updateSessionExpiry: (expiresAt: string) => void;
18
19
  setRefreshToken: (token: string | null) => void;
19
20
  setBeaconToken: (token: string | null) => void;
20
21
  addActiveExperience: (experienceId: string, session: ExperienceSession) => void;
@@ -1 +1 @@
1
- import{persist as e,createJSONStorage as i}from"zustand/middleware";import{createStore as n}from"zustand/vanilla";import{isBrowser as t,isStorageAvailable as s}from"../core/utils.js";import{getEventBus as o}from"./events.js";const r=["session","activeJourneys","versionMap"],a=["session"];function c(e){if(!e||"object"!=typeof e)return{};const i=e,n={};for(const t of a)t in i&&(n[t]=i[t]);return n}function p(e){if(!e||"object"!=typeof e)return{};const i=e,n={};for(const t of r)t in i&&(n[t]="versionMap"===t?c(i[t]):i[t]);return n}const l=n()(e((e,i)=>({session:null,refreshToken:null,beaconToken:null,activeExperiences:{},activeJourneys:{},capabilityTokens:{},distributionCapabilityIndex:{},versionMap:{},setSession:n=>{e(e=>({session:n,versionMap:{...e.versionMap,session:(e.versionMap.session||0)+1}})),n&&o().emit("auth:authenticated",{session:n,isNew:!i().session})},setRefreshToken:i=>{e(e=>({refreshToken:i,versionMap:{...e.versionMap,refreshToken:(e.versionMap.refreshToken||0)+1}}))},setBeaconToken:i=>{e({beaconToken:i})},addActiveExperience:(i,n)=>{e(e=>({activeExperiences:{...e.activeExperiences,[i]:n},versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}))},removeActiveExperience:i=>{e(e=>{const{[i]:n,...t}=e.activeExperiences;return{activeExperiences:t,versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}})},updateExperienceSession:(i,n)=>{e(e=>{const t=e.activeExperiences[i];return t?{activeExperiences:{...e.activeExperiences,[i]:{...t,...n}},versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}:e})},clearActiveExperiences:()=>{e(e=>({activeExperiences:{},versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}))},setJourneyState:(i,n)=>{const t={...n,schemaVersion:2};e(e=>({activeJourneys:{...e.activeJourneys,[i]:t}}))},removeJourney:i=>{e(e=>{const{[i]:n,...t}=e.activeJourneys;return{activeJourneys:t}})},clearJourneys:()=>{e(()=>({activeJourneys:{}}))},upsertCapabilityToken:(i,n)=>{e(e=>({capabilityTokens:{...e.capabilityTokens,[i]:n},versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1}}))},bindDistribution:(i,n)=>{e(e=>({distributionCapabilityIndex:{...e.distributionCapabilityIndex,[i]:n},versionMap:{...e.versionMap,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}))},clearCapabilityContext:(i,n)=>{e(e=>{const t=`${i}:${n}`,{[t]:s,...o}=e.capabilityTokens,r={};for(const[i,n]of Object.entries(e.distributionCapabilityIndex))n.sequenceKey!==t&&(r[i]=n);return{capabilityTokens:o,distributionCapabilityIndex:r,versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}})},clearExperienceCapabilityContext:i=>{e(e=>{const n={};for(const[s,o]of Object.entries(e.capabilityTokens))o.experienceId!==i&&(n[s]=o);const t={};for(const[s,o]of Object.entries(e.distributionCapabilityIndex))o.experienceId!==i&&(t[s]=o);return{capabilityTokens:n,distributionCapabilityIndex:t,versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}})},clearExpiredCapabilityTokens:i=>{e(e=>{const n=/* @__PURE__ */new Set,t={};for(const[o,r]of Object.entries(e.capabilityTokens))r.expiresAtMs&&r.expiresAtMs<=i?n.add(o):t[o]=r;if(0===n.size)return e;const s={};for(const[i,o]of Object.entries(e.distributionCapabilityIndex))n.has(o.sequenceKey)||(s[i]=o);return{capabilityTokens:t,distributionCapabilityIndex:s,versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}})},clearAll:()=>{e({session:null,refreshToken:null,beaconToken:null,activeExperiences:{},activeJourneys:{},capabilityTokens:{},distributionCapabilityIndex:{},versionMap:{}}),o().emit("state:cleared",{})}}),{name:"fanfare-sdk-state",storage:i(()=>(()=>{if(t()&&s("localStorage"))return window.localStorage;const e={};return{getItem:i=>Object.prototype.hasOwnProperty.call(e,i)?e[i]:null,setItem:(i,n)=>{e[i]=n},removeItem:i=>{delete e[i]}}})()),partialize:e=>p(e),version:3,merge:(e,i)=>({...i,...p(e)}),onRehydrateStorage:()=>e=>{if(e){const i=e.activeJourneys??{};o().emit("state:restored",{experiences:Object.keys(i)})}}}));function v(){l.setState({session:null,refreshToken:null,beaconToken:null,activeExperiences:{},activeJourneys:{},capabilityTokens:{},distributionCapabilityIndex:{},versionMap:{}}),b=null}let b=null;const u=()=>b||(b=new Proxy({},{get:(e,i)=>Reflect.has(e,i)?e[i]:l.getState()[i],has:(e,i)=>Reflect.has(e,i)||i in l.getState(),ownKeys:e=>{const i=new Set(Reflect.ownKeys(l.getState()));for(const n of Reflect.ownKeys(e))i.add(n);return Array.from(i)},getOwnPropertyDescriptor:(e,i)=>{const n=Object.getOwnPropertyDescriptor(e,i);if(n)return n;const t=Object.getOwnPropertyDescriptor(l.getState(),i);return t?{...t,configurable:!0}:void 0}}),b);export{u as getSDKStore,v as resetSDKStore,l as useSDKStore};
1
+ import{persist as e,createJSONStorage as i}from"zustand/middleware";import{createStore as n}from"zustand/vanilla";import{isBrowser as t,isStorageAvailable as s}from"../core/utils.js";import{getEventBus as o}from"./events.js";const r=["session","activeJourneys","versionMap"],a=["session"];function c(e){if(!e||"object"!=typeof e)return{};const i=e,n={};for(const t of a)t in i&&(n[t]=i[t]);return n}function p(e){if(!e||"object"!=typeof e)return{};const i=e,n={};for(const t of r)t in i&&(n[t]="versionMap"===t?c(i[t]):i[t]);return n}const l=n()(e((e,i)=>({session:null,refreshToken:null,beaconToken:null,activeExperiences:{},activeJourneys:{},capabilityTokens:{},distributionCapabilityIndex:{},versionMap:{},setSession:n=>{e(e=>({session:n,versionMap:{...e.versionMap,session:(e.versionMap.session||0)+1}})),n&&o().emit("auth:authenticated",{session:n,isNew:!i().session})},updateSessionExpiry:i=>{e(e=>e.session?{session:{...e.session,expiresAt:i},versionMap:{...e.versionMap,session:(e.versionMap.session||0)+1}}:{})},setRefreshToken:i=>{e(e=>({refreshToken:i,versionMap:{...e.versionMap,refreshToken:(e.versionMap.refreshToken||0)+1}}))},setBeaconToken:i=>{e({beaconToken:i})},addActiveExperience:(i,n)=>{e(e=>({activeExperiences:{...e.activeExperiences,[i]:n},versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}))},removeActiveExperience:i=>{e(e=>{const{[i]:n,...t}=e.activeExperiences;return{activeExperiences:t,versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}})},updateExperienceSession:(i,n)=>{e(e=>{const t=e.activeExperiences[i];return t?{activeExperiences:{...e.activeExperiences,[i]:{...t,...n}},versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}:e})},clearActiveExperiences:()=>{e(e=>({activeExperiences:{},versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}))},setJourneyState:(i,n)=>{const t={...n,schemaVersion:2};e(e=>({activeJourneys:{...e.activeJourneys,[i]:t}}))},removeJourney:i=>{e(e=>{const{[i]:n,...t}=e.activeJourneys;return{activeJourneys:t}})},clearJourneys:()=>{e(()=>({activeJourneys:{}}))},upsertCapabilityToken:(i,n)=>{e(e=>({capabilityTokens:{...e.capabilityTokens,[i]:n},versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1}}))},bindDistribution:(i,n)=>{e(e=>({distributionCapabilityIndex:{...e.distributionCapabilityIndex,[i]:n},versionMap:{...e.versionMap,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}))},clearCapabilityContext:(i,n)=>{e(e=>{const t=`${i}:${n}`,{[t]:s,...o}=e.capabilityTokens,r={};for(const[i,n]of Object.entries(e.distributionCapabilityIndex))n.sequenceKey!==t&&(r[i]=n);return{capabilityTokens:o,distributionCapabilityIndex:r,versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}})},clearExperienceCapabilityContext:i=>{e(e=>{const n={};for(const[s,o]of Object.entries(e.capabilityTokens))o.experienceId!==i&&(n[s]=o);const t={};for(const[s,o]of Object.entries(e.distributionCapabilityIndex))o.experienceId!==i&&(t[s]=o);return{capabilityTokens:n,distributionCapabilityIndex:t,versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}})},clearExpiredCapabilityTokens:i=>{e(e=>{const n=/* @__PURE__ */new Set,t={};for(const[o,r]of Object.entries(e.capabilityTokens))r.expiresAtMs&&r.expiresAtMs<=i?n.add(o):t[o]=r;if(0===n.size)return e;const s={};for(const[i,o]of Object.entries(e.distributionCapabilityIndex))n.has(o.sequenceKey)||(s[i]=o);return{capabilityTokens:t,distributionCapabilityIndex:s,versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}})},clearAll:()=>{e({session:null,refreshToken:null,beaconToken:null,activeExperiences:{},activeJourneys:{},capabilityTokens:{},distributionCapabilityIndex:{},versionMap:{}}),o().emit("state:cleared",{})}}),{name:"fanfare-sdk-state",storage:i(()=>(()=>{if(t()&&s("localStorage"))return window.localStorage;const e={};return{getItem:i=>Object.prototype.hasOwnProperty.call(e,i)?e[i]:null,setItem:(i,n)=>{e[i]=n},removeItem:i=>{delete e[i]}}})()),partialize:e=>p(e),version:3,merge:(e,i)=>({...i,...p(e)}),onRehydrateStorage:()=>e=>{if(e){const i=e.activeJourneys??{};o().emit("state:restored",{experiences:Object.keys(i)})}}}));function v(){l.setState({session:null,refreshToken:null,beaconToken:null,activeExperiences:{},activeJourneys:{},capabilityTokens:{},distributionCapabilityIndex:{},versionMap:{}}),b=null}let b=null;const u=()=>b||(b=new Proxy({},{get:(e,i)=>Reflect.has(e,i)?e[i]:l.getState()[i],has:(e,i)=>Reflect.has(e,i)||i in l.getState(),ownKeys:e=>{const i=new Set(Reflect.ownKeys(l.getState()));for(const n of Reflect.ownKeys(e))i.add(n);return Array.from(i)},getOwnPropertyDescriptor:(e,i)=>{const n=Object.getOwnPropertyDescriptor(e,i);if(n)return n;const t=Object.getOwnPropertyDescriptor(l.getState(),i);return t?{...t,configurable:!0}:void 0}}),b);export{u as getSDKStore,v as resetSDKStore,l as useSDKStore};
@@ -1 +1 @@
1
- import{isMoneyString as e,isMoneyGreaterThan as t,isMoneyGreaterThanOrEqualTo as n}from"../core/money.js";import{MockServer as s,ResponseBuilder as o}from"./mock-server.js";const i="http://mock.local/api",a="http://mock.local/beacon",r="424242";function u(){return{isEntered:!1,enteredAt:null,status:"not_waitlisted",isOpen:!1}}function d(e,t){window.localStorage.setItem(e,JSON.stringify(t))}const c={queue:{key:"queue",label:"Queue Journey",experienceId:"experience_queue_story_1",sequenceId:"sequence_queue_story_1",queueId:"queue_queue_story_1"},"waitlist-queue":{key:"waitlist-queue",label:"Waitlist to Queue Journey",experienceId:"experience_waitlist_queue_story_1",sequenceId:"sequence_waitlist_queue_story_1",queueId:"queue_waitlist_queue_story_1",waitlistId:"waitlist_waitlist_queue_story_1"},"waitlist-lifecycle":{key:"waitlist-lifecycle",label:"Waitlist Lifecycle Journey",experienceId:"experience_waitlist_lifecycle_story_1",sequenceId:"sequence_waitlist_lifecycle_story_1",queueId:"queue_waitlist_lifecycle_story_1",waitlistId:"waitlist_waitlist_lifecycle_story_1"},"auth-queue":{key:"auth-queue",label:"Auth-Gated Queue Journey",experienceId:"experience_auth_queue_story_1",sequenceId:"sequence_auth_queue_story_1",queueId:"queue_auth_queue_story_1"},"access-code-queue":{key:"access-code-queue",label:"Access-Code Queue Journey",experienceId:"experience_access_code_queue_story_1",sequenceId:"sequence_access_code_queue_story_1",queueId:"queue_access_code_queue_story_1",accessCode:"VIP123"},"upgrade-offer-queue":{key:"upgrade-offer-queue",label:"Upgrade Offer Journey",experienceId:"experience_upgrade_offer_story_1",sequenceId:"sequence_upgrade_offer_base_story_1",upgradeSequenceId:"sequence_upgrade_offer_vip_story_1",queueId:"queue_upgrade_offer_story_1",accessCode:"VIP123"},"upgrade-offer-multi-queue":{key:"upgrade-offer-multi-queue",label:"Upgrade Offer Journey (multi-tier)",experienceId:"experience_upgrade_offer_multi_story_1",sequenceId:"sequence_upgrade_offer_multi_base_story_1",upgradeSequenceId:"sequence_upgrade_offer_multi_vip_story_1",superUpgradeSequenceId:"sequence_upgrade_offer_multi_super_story_1",queueId:"queue_upgrade_offer_multi_story_1",accessCode:"VIP123"},draw:{key:"draw",label:"Draw Journey",experienceId:"experience_draw_story_1",sequenceId:"sequence_draw_story_1",drawId:"draw_draw_story_1"},"timed-release":{key:"timed-release",label:"Timed Release Journey",experienceId:"experience_timed_release_story_1",sequenceId:"sequence_timed_release_story_1",timedReleaseId:"timed_release_story_1"},"timed-release-completion":{key:"timed-release-completion",label:"Timed Release Completion Journey",experienceId:"experience_timed_release_completion_story_1",sequenceId:"sequence_timed_release_completion_story_1",timedReleaseId:"timed_release_completion_story_1"},appointment:{key:"appointment",label:"Appointment Journey",experienceId:"experience_appointment_story_1",sequenceId:"sequence_appointment_story_1",appointmentId:"appointment_story_1"},auction:{key:"auction",label:"Auction Journey",experienceId:"experience_auction_story_1",sequenceId:"sequence_auction_story_1",auctionId:"auction_auction_story_1"},"auth-draw":{key:"auth-draw",label:"Auth-Gated Draw Journey",experienceId:"experience_auth_draw_story_1",sequenceId:"sequence_auth_draw_story_1",drawId:"draw_auth_draw_story_1"},"admission-cycle":{key:"admission-cycle",label:"Admission Cycle Journey",experienceId:"experience_admission_cycle_story_1",sequenceId:"sequence_admission_cycle_story_1",queueId:"queue_admission_cycle_story_1"},"queue-denied":{key:"queue-denied",label:"Queue Denied (not selected)",experienceId:"experience_queue_denied_story_1",sequenceId:"sequence_queue_denied_story_1",queueId:"queue_queue_denied_story_1"},"draw-denied":{key:"draw-denied",label:"Draw Denied (not selected)",experienceId:"experience_draw_denied_story_1",sequenceId:"sequence_draw_denied_story_1",drawId:"draw_draw_denied_story_1"}};function l(e){return e.startsWith("http://")||e.startsWith("https://")?e:new URL(e,window.location.origin).toString()}function _(e){if(!e)return c.queue;if("string"==typeof e)return c[e]??c.queue;return{...c[e.key]??c.queue,...e}}async function p(e){try{const t=await e.clone().json();return"object"==typeof t&&null!==t?t:void 0}catch{return}}function I(e,t){return{baseUrl:e,defaultDelay:t.defaultDelay??30,logRequests:t.logRequests??!1,fallbackToNetwork:!1}}function m(e){return function(e){const t=e.headers.get("DPoP"),n=e.headers.get("X-Admission-Key-Thumbprint");if(!t||!n)return!1;const s=t.split(".");return 3===s.length&&!s.some(e=>0===e.length)&&/^[A-Za-z0-9_-]{16,}$/.test(n)}(e)?null:o.badRequest("Missing admission proof headers")}function y(e={}){const t=e.scenarios?.length?e.scenarios.map(e=>_(e)):[_(e.scenario)],n=t[0],d=l(e.apiBaseUrl??i),c=l(e.beaconBaseUrl??a),m=e.otpCode??r,y=new s(I(d,e)),A=new s(I(c,e)),E=/* @__PURE__ */new Date,x=E.toISOString(),h=new Date(E.getTime()+864e5).toISOString(),b=new Date(E.getTime()+(e.grantExpiresInMs??864e5)).toISOString(),O={sessionType:null};!function(e,t){const n={id:"session_mock_demo",consumerId:"consumer_mock_demo",guestId:"guest_mock_demo",type:"guest",email:null,phone:null,createdAt:t.nowIso,expiresAt:t.expiresAtIso};e.post("/auth/guest",()=>(t.authState.sessionType="guest",o.ok({session:n,accessToken:"guest_access_token_mock_demo",refreshToken:"guest_refresh_token_mock_demo",beaconToken:"guest_beacon_token_mock_demo"}))),e.post("/auth/refresh",()=>o.ok({accessToken:`access_token_mock_demo_${Date.now()}`,refreshToken:"guest_refresh_token_mock_demo",beaconToken:"authenticated"===t.authState.sessionType?"authenticated_beacon_token_mock_demo":"guest_beacon_token_mock_demo"})),e.post("/auth/otp/request",o.ok({ok:!0})),e.post("/auth/otp/verify",async e=>{const n=await p(e);return("string"==typeof n?.code?n.code:void 0)!==t.otpCode?o.error("Invalid verification code",401):(t.authState.sessionType="authenticated",o.ok({session:{id:"session_authenticated_mock_demo",consumerId:"consumer_authenticated_mock_demo",type:"authenticated",email:"string"==typeof n?.email?n.email:"sdk-harness@example.com",phone:"string"==typeof n?.phone?n.phone:null,createdAt:t.nowIso,expiresAt:t.expiresAtIso},accessToken:"authenticated_access_token_mock_demo",refreshToken:"authenticated_refresh_token_mock_demo",beaconToken:"authenticated_beacon_token_mock_demo"}))}),e.post("/auth/external/exchange",async e=>{const n=await p(e);return("string"==typeof n?.exchangeCode?n.exchangeCode:void 0)?(t.authState.sessionType="authenticated",o.ok({session:{id:"session_external_mock_demo",consumerId:"consumer_external_mock_demo",type:"authenticated",email:"external-auth@example.com",phone:null,createdAt:t.nowIso,expiresAt:t.expiresAtIso},accessToken:"external_access_token_mock_demo",refreshToken:"external_refresh_token_mock_demo",beaconToken:"authenticated_beacon_token_mock_demo"})):o.error("Invalid exchange code",401)})}(y,{nowIso:x,expiresAtIso:h,otpCode:m,authState:O}),function(e){const t=()=>o.ok({ok:!0});e.post("/events",t),e.post("/events/batch",t)}(A);const T=t.map(e=>{const t=e.waitlistId?`__fanfare_harness_waitlist__:${e.key}:${e.experienceId}`:void 0;return{scenario:e,waitlistStorageKey:t,waitlistState:e.waitlistId?function(e){const t={isEntered:!1,enteredAt:null,status:"not_waitlisted",isOpen:!1};try{const n=window.localStorage.getItem(e);if(!n)return t;const s=JSON.parse(n),o=!0===s.isEntered,i="string"==typeof s.status?s.status:void 0,a="notified"===i||"waitlisted"===i||"not_waitlisted"===i?i:o?"waitlisted":"not_waitlisted";return{isEntered:"not_waitlisted"!==a||o,enteredAt:"string"==typeof s.enteredAt?s.enteredAt:null,status:a,isOpen:!0===s.isOpen}}catch{return t}}(t??""):void 0}});var v;v=T,y.get("/consumers/me",()=>{const e=/* @__PURE__ */(new Date).toISOString(),t=v.flatMap(({scenario:t,waitlistState:n})=>{if(!t.waitlistId||!n?.isEntered||!t.queueId)return[];if("waitlist-lifecycle"===t.key&&n.isOpen)return[];const s=new Date(Date.now()+6e4).toISOString(),o="notified"===n.status?"notified":"waitlisted";return[{experienceId:t.experienceId,updatedAt:e,journeyStage:"routed",sequenceId:t.sequenceId,offers:[],sequence:{phase:"scheduled",mechanism:"queue",distribution:{type:"queue",id:t.queueId,sequenceId:t.sequenceId,lifecycle:"scheduled",startsAt:s,opensAt:s},waitlist:{waitlistId:t.waitlistId,status:o,entryId:`waitlist_entry_${t.waitlistId}`}}}]});return o.ok({active:{journeys:t}})});for(const{scenario:s,waitlistStorageKey:o,waitlistState:i}of T){if(g(y,s,{authState:O,expiresAtIso:b,waitlistState:i}),s.queueId){const e=[s.queueId,...s.upgradeSequenceId?[`${s.queueId}_vip`]:[],...s.superUpgradeSequenceId?[`${s.queueId}_super`]:[]];for(const t of e)q(y,{queueId:t,experienceId:s.experienceId,name:"Journey Queue",initialPosition:"admission-cycle"===s.key?2:5,initialEstimatedWaitSeconds:90,autoExpireAfterGrant:"admission-cycle"===s.key,forceDenyOnEnter:"queue-denied"===s.key})}s.waitlistId&&k(y,{waitlistId:s.waitlistId,sequenceId:s.sequenceId,state:i??u(),storageKey:o??""}),s.drawId&&w(y,{drawId:s.drawId,forceDenyOnEnter:"draw-denied"===s.key}),s.timedReleaseId&&S(y,{timedReleaseId:s.timedReleaseId,closeAt:new Date(Date.now()+9e5).toISOString(),grantOnEnter:"timed-release-completion"!==s.key}),s.appointmentId&&f(y,{appointmentId:s.appointmentId}),s.auctionId&&D(y,{auctionId:s.auctionId})}return y.start(),A.start(),{apiBaseUrl:d,beaconBaseUrl:c,scenario:n,scenarios:t,stop(){A.stop(),y.stop()},getRequests:()=>({api:y.getRequests(),beacon:A.getRequests()})}}function g(e,t,n){const s=(/* @__PURE__ */new Date).toISOString();e.get(`/experiences/${t.experienceId}`,o.ok({id:t.experienceId,name:t.label,createdAt:s,updatedAt:s,i18n:{en:{"start.title":t.label,"start.description":"Mock data served from the harness mock server."}}})),e.post(`/experiences/${t.experienceId}/enter`,o.ok({experienceId:t.experienceId,enteredAt:s}));const i=e=>({id:t.sequenceId,name:e,experienceId:t.experienceId,priority:1,color:"#000000"}),a=(e,n,s)=>({schemaVersion:1,experienceId:t.experienceId,outcome:"gated",selected:{sequence:e,status:"gated",gates:[n],reasons:s},offers:[],outcomeReasons:s}),r=e=>({schemaVersion:1,experienceId:t.experienceId,outcome:"routed",selected:{sequence:e,status:"admissible",gates:[],reasons:[],sequenceCapabilityGrant:`mock_sequence_capability_grant_${t.sequenceId}`,sequenceCapabilityGrantExpiresAt:n.expiresAtIso},offers:[],outcomeReasons:[]});if("auth-queue"===t.key||"auth-draw"===t.key)e.post(`/experiences/${t.experienceId}/route`,()=>"authenticated"!==n.authState.sessionType?o.ok(a(i("auth-draw"===t.key?"Auth-Gated Draw Sequence":"Auth-Gated Queue Sequence"),{type:"authentication",status:"required",reason:{code:"AUTH_REQUIRED"}},[{code:"AUTH_REQUIRED"}])):o.ok(r(i("auth-draw"===t.key?"Auth-Gated Draw Sequence":"Auth-Gated Queue Sequence"))));else if("access-code-queue"===t.key)e.post(`/experiences/${t.experienceId}/route`,async e=>{const n=await p(e),s="string"==typeof n?.accessCode?n.accessCode:void 0;return s?s!==t.accessCode?o.ok(a(i("Access-Code Queue Sequence"),{type:"access_code",status:"invalid",reason:{code:"ACCESS_CODE_INVALID"}},[{code:"ACCESS_CODE_INVALID"}])):o.ok(r(i("Access-Code Queue Sequence"))):o.ok(a(i("Access-Code Queue Sequence"),{type:"access_code",status:"missing",reason:{code:"ACCESS_CODE_REQUIRED"}},[{code:"ACCESS_CODE_REQUIRED"}]))});else if("upgrade-offer-queue"===t.key){const s={id:t.sequenceId,name:"General Admission",experienceId:t.experienceId,priority:1,color:"#000000"},i={id:t.upgradeSequenceId??`${t.sequenceId}_vip`,name:"VIP",experienceId:t.experienceId,priority:10,color:"#7c3aed"},a=e=>{const t="missing"===e?"ACCESS_CODE_REQUIRED":"ACCESS_CODE_INVALID";return{sequence:i,status:"gated",gates:[{type:"access_code",status:e,reason:{code:t}}],reasons:[{code:t}]}},r=e=>({schemaVersion:1,experienceId:t.experienceId,outcome:"routed",selected:{sequence:s,status:"admissible",gates:[],reasons:[],sequenceCapabilityGrant:`mock_sequence_capability_grant_${t.sequenceId}`,sequenceCapabilityGrantExpiresAt:n.expiresAtIso},offers:[a(e)],outcomeReasons:[]}),u=()=>({schemaVersion:1,experienceId:t.experienceId,outcome:"routed",selected:{sequence:i,status:"admissible",gates:[],reasons:[],sequenceCapabilityGrant:`mock_sequence_capability_grant_${i.id}`,sequenceCapabilityGrantExpiresAt:n.expiresAtIso},offers:[],outcomeReasons:[]});e.post(`/experiences/${t.experienceId}/route`,async e=>{const n=await p(e),s="string"==typeof n?.accessCode?n.accessCode:void 0;return s?s!==t.accessCode?o.ok(r("invalid")):o.ok(u()):o.ok(r("missing"))})}else if("upgrade-offer-multi-queue"===t.key){const s={id:t.sequenceId,name:"General Admission",experienceId:t.experienceId,priority:1,color:"#000000"},i={id:t.upgradeSequenceId??`${t.sequenceId}_vip`,name:"VIP",experienceId:t.experienceId,priority:10,color:"#7c3aed"},a={id:t.superUpgradeSequenceId??`${t.sequenceId}_super`,name:"Super VIP",experienceId:t.experienceId,priority:20,color:"#f59e0b"},r=(e,t)=>{const n="missing"===t?"ACCESS_CODE_REQUIRED":"ACCESS_CODE_INVALID";return{sequence:e,status:"gated",gates:[{type:"access_code",status:t,reason:{code:n}}],reasons:[{code:n}]}},u=(e,s)=>({schemaVersion:1,experienceId:t.experienceId,outcome:"routed",selected:{sequence:e,status:"admissible",gates:[],reasons:[],sequenceCapabilityGrant:`mock_sequence_capability_grant_${e.id}`,sequenceCapabilityGrantExpiresAt:n.expiresAtIso},offers:s,outcomeReasons:[]}),d=()=>({schemaVersion:1,experienceId:t.experienceId,outcome:"gated",selected:{sequence:s,status:"gated",gates:[{type:"access_code",status:"invalid",reason:{code:"ACCESS_CODE_INVALID"}}],reasons:[{code:"ACCESS_CODE_INVALID"}]},offers:[],outcomeReasons:[{code:"ACCESS_CODE_INVALID"}]});e.post(`/experiences/${t.experienceId}/route`,async e=>{const n=await p(e),c="string"==typeof n?.accessCode?n.accessCode:void 0;if("LOCKDOWN"===c)return o.ok(d());if("SUPER123"===c)return o.ok(u(a,[]));if(c===t.accessCode)return o.ok(u(i,[r(a,"invalid")]));const l=c?"invalid":"missing";return o.ok(u(s,[r(a,l),r(i,l)]))})}else e.post(`/experiences/${t.experienceId}/route`,o.ok(r(i(`${t.label} Sequence`))));if("waitlist-queue"!==t.key&&"waitlist-lifecycle"!==t.key)if("draw"!==t.key&&"auth-draw"!==t.key&&"draw-denied"!==t.key)if("timed-release"!==t.key&&"timed-release-completion"!==t.key)if("appointment"!==t.key)if("auction"!==t.key){if("upgrade-offer-queue"===t.key||"upgrade-offer-multi-queue"===t.key){const n=(e,t)=>()=>{const n=/* @__PURE__ */(new Date).toISOString();return o.ok({recentPast:void 0,active:{type:"queue",id:e,status:"active",details:{id:e,type:"queue",openAt:n,metadata:{label:t},estimatedWaitTime:300,queueLength:10}},upcoming:void 0})};return e.get(`/sequences/${t.sequenceId}/distributions`,n(t.queueId??"queue_upgrade_base","General Admission Queue")),e.get(`/sequences/${t.upgradeSequenceId??`${t.sequenceId}_vip`}/distributions`,n(`${t.queueId}_vip`,"VIP Queue")),void("upgrade-offer-multi-queue"===t.key&&e.get(`/sequences/${t.superUpgradeSequenceId??`${t.sequenceId}_super`}/distributions`,n(`${t.queueId}_super`,"Super VIP Queue")))}e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=/* @__PURE__ */(new Date).toISOString();return o.ok({recentPast:void 0,active:{type:"queue",id:t.queueId,status:"active",details:{id:t.queueId,type:"queue",openAt:e,metadata:{label:"Journey Queue"},estimatedWaitTime:300,queueLength:10}},upcoming:void 0})})}else e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=Date.now(),n=new Date(e).toISOString();return o.ok({recentPast:void 0,active:{type:"auction",id:t.auctionId,status:"active",details:{id:t.auctionId,type:"auction",openAt:n,endsAt:new Date(e+36e5).toISOString(),startsAt:n,currencyCode:"USD",reservePrice:"50.00",bidIncrement:"10.00"}},upcoming:void 0})});else e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=t.appointmentId??"appointment_mock",n=Date.now(),s=new Date(n-6e4).toISOString(),i=new Date(n+6048e5).toISOString();return o.ok({recentPast:void 0,active:{type:"appointment",id:e,status:"active",details:{id:e,type:"appointment",openAt:s,closeAt:i,bookingWindowOpenAt:s,bookingWindowCloseAt:i,slotDurationMinutes:30,timezone:"UTC",locationId:"loc_harness_1",totalSlots:3,availableSlots:3}},upcoming:void 0})});else e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=Date.now();return o.ok({recentPast:void 0,active:{type:"timed_release",id:t.timedReleaseId,status:"active",details:{id:t.timedReleaseId,type:"timed_release",openAt:new Date(e).toISOString(),closeAt:new Date(e+9e5).toISOString()}},upcoming:void 0})});else e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=Date.now(),n=new Date(e+(t.drawDelayMs??72e5)).toISOString();return o.ok({recentPast:void 0,active:{type:"draw",id:t.drawId,status:"active",details:{id:t.drawId,type:"draw",openAt:new Date(e).toISOString(),drawTime:n,entryDeadline:n}},upcoming:void 0})});else e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=/* @__PURE__ */(new Date).toISOString();return("waitlist-lifecycle"===t.key?!0===n.waitlistState?.isOpen:n.waitlistState?.isEntered)?o.ok({recentPast:void 0,active:{type:"queue",id:t.queueId,status:"active",details:{id:t.queueId,type:"queue",openAt:e,closeAt:new Date(Date.now()+18e5).toISOString(),metadata:{label:"Journey Queue (Window Open)"},estimatedWaitTime:300,queueLength:10}},upcoming:void 0}):o.ok({recentPast:void 0,active:void 0,upcoming:{type:"queue",id:t.queueId,startsAt:new Date(Date.now()+6e4).toISOString(),waitlistId:t.waitlistId}})})}function q(e,t){const{queueId:n,experienceId:s="experience_mock",name:i="Mock Queue",initialPosition:a=12,initialEstimatedWaitSeconds:r=300,autoExpireAfterGrant:u=!1,forceDenyOnEnter:d=!1}=t;let c=!1,l=!1,_=a,p=r,I=0;e.get(`/queues/${n}`,o.ok({id:n,experienceId:s,name:i,status:"open",capacity:500,currentSize:247,estimatedWaitTime:p,openAt:new Date(Date.now()-3e5).toISOString(),closeAt:new Date(Date.now()+36e5).toISOString()})),e.post(`/queues/${n}/enter`,e=>{const t=m(e);return t||(d?o.ok({status:"DENIED"}):(c=!0,l=!1,I=0,_=a,p=r,o.ok({position:_,estimatedWaitTimeInSeconds:p,status:"QUEUED"})))}),e.post(`/queues/${n}/leave`,()=>(c=!1,l=!1,I=0,o.ok({}))),e.get(`/queues/${n}/status`,()=>c?l?(I+=1,u&&I>1?(c=!1,l=!1,o.ok({status:"EXPIRED"})):o.ok({status:"GRANTED",admissionGrant:`admission_grant_${n}`,expiresAt:new Date(Date.now()+6e5).toISOString()})):(_=Math.max(1,_-1),p=Math.max(0,p-30),_<=1?(l=!0,I=1,o.ok({status:"GRANTED",admissionGrant:`admission_grant_${n}`,expiresAt:new Date(Date.now()+6e5).toISOString()})):o.ok({status:"QUEUED",position:_,estimatedWaitTimeInSeconds:p})):o.ok({status:"NOT_QUEUED"}))}function w(e,t){const{drawId:n,drawAtOffsetMs:s=72e5,forceDenyOnEnter:i=!1}=t;let a="NOT_ENTERED",r=0;e.get(`/draws/${n}`,()=>o.ok({id:n,openAt:new Date(Date.now()-6e4).toISOString(),closeAt:null,timeZone:"UTC",drawAt:new Date(Date.now()+s).toISOString(),capacity:null,continueSelectingUntilCompleted:null})),e.get(`/draws/${n}/status`,()=>"NOT_ENTERED"===a?o.ok({status:"NOT_ENTERED"}):(r+=1,r>=2?o.ok({status:"WON",wonAt:/* @__PURE__ */(new Date).toISOString(),admissionGrant:`admission_grant_${n}`}):o.ok({status:"ENTERED"}))),e.post(`/draws/${n}/enter`,e=>{const t=m(e);return t||(i?o.ok({status:"DENIED"}):(a="ENTERED",r=0,o.ok({status:"ENTERED"})))}),e.post(`/draws/${n}/leave`,()=>(a="NOT_ENTERED",r=0,o.ok({})))}function k(e,t){const{waitlistId:n,sequenceId:s,state:i,storageKey:a}=t;e.get(`/waitlists/${n}/status`,()=>o.ok({waitlistId:n,isEntered:i.isEntered,enteredAt:i.enteredAt??void 0})),e.post(`/waitlists/${n}/enter`,()=>(i.isEntered=!0,i.enteredAt=/* @__PURE__ */(new Date).toISOString(),i.status="waitlisted",d(a,i),o.ok({id:`waitlist_entry_${n}`,waitlistId:n,sequenceId:s,consumerId:"consumer_mock_demo",enteredAt:i.enteredAt})));const r=()=>(i.isEntered=!1,i.enteredAt=null,i.status="not_waitlisted",i.isOpen=!1,d(a,i),o.ok({}));e.delete(`/waitlists/${n}/leave`,r),e.post(`/waitlists/${n}/leave`,r)}function S(e,t){const{timedReleaseId:n,closeAt:s,grantOnEnter:i=!0}=t;let a="NOT_ENTERED",r=null,u=null,d=0;const c=()=>`admission_grant_${n}_${d+=1}`;e.get(`/timed-releases/${n}`,o.ok({id:n,openAt:new Date(Date.now()-6e4).toISOString(),closeAt:s,timeZone:"America/Los_Angeles",supportsGuest:!0})),e.get(`/timed-releases/${n}/status`,()=>"ENTERED"===a?o.ok({status:"ENTERED",enteredAt:r??/* @__PURE__ */(new Date).toISOString(),...i?{admissionGrant:c()}:{},expiresAt:u??new Date(Date.now()+6e4).toISOString()}):"LEFT"===a?o.ok({status:"LEFT",leftAt:/* @__PURE__ */(new Date).toISOString()}):o.ok({status:"NOT_ENTERED"})),e.post(`/timed-releases/${n}/enter`,async e=>{const t=await p(e),n="string"==typeof t?.variantId?t.variantId:void 0;return a="ENTERED",r=/* @__PURE__ */(new Date).toISOString(),u=new Date(Date.now()+6e4).toISOString(),o.ok({status:"ENTERED",enteredAt:r,selectedVariantId:n,...i?{admissionGrant:c()}:{},expiresAt:u})}),e.post(`/timed-releases/${n}/leave`,()=>"ENTERED"===a?o.error("Participant holds a live admission; it lapses when its window ends",409):(a="LEFT",o.ok({})))}function f(e,t){const{appointmentId:n}=t,s="loc_harness_1";let i=null;const a=(e,t,n)=>{const s=/* @__PURE__ */new Date;return new Date(Date.UTC(s.getUTCFullYear(),s.getUTCMonth(),s.getUTCDate()+e,t,n))},r=(e,t)=>({id:e,startTime:t.toISOString(),endTime:new Date(t.getTime()+18e5).toISOString(),locationId:s,locationName:"Suite 100",timezone:"UTC",capacity:4,available:3}),u=[r("slot_0900",a(1,9,0)),r("slot_1030",a(1,10,30)),r("slot_1100",a(2,11,0))],d=(e,t)=>{const s=(e=>u.find(t=>t.id===e)??u[0])(e);return{bookingId:`booking_${n}`,appointmentId:n,consumerId:"consumer_mock_demo",slotId:s.id,locationId:s.locationId,locationName:s.locationName,startTime:s.startTime,endTime:s.endTime,confirmationCode:t,status:"booked",createdAt:/* @__PURE__ */(new Date).toISOString()}};e.get(`/appointments/${n}`,o.ok({id:n,type:"appointment",openAt:new Date(Date.now()-6e4).toISOString(),closeAt:new Date(Date.now()+6048e5).toISOString(),slotDurationMinutes:30,timeZone:"UTC",locationId:s,totalSlots:u.length,availableSlots:u.length})),e.get(`/appointments/${n}/slots`,()=>o.ok({slots:u})),e.get(`/appointments/${n}/me`,()=>o.ok({booking:i})),e.post(`/appointments/${n}/book`,async e=>{const t=await p(e),n="string"==typeof t?.slotId?t.slotId:u[0].id;return i=d(n,"FF-1001"),o.ok(i)}),e.post(`/appointments/${n}/reschedule`,async e=>{const t=await p(e),n=i?.slotId??u[0].id,s="string"==typeof t?.newSlotId?t.newSlotId:u[1].id;return i=d(s,"FF-2001"),o.ok({bookingId:i.bookingId,previousSlotId:n,newSlotId:i.slotId,confirmationCode:i.confirmationCode,status:i.status})}),e.post(`/appointments/${n}/cancel`,()=>{const e=i?.bookingId??`booking_${n}`;return i=null,o.ok({message:"ok",bookingId:e})})}function D(s,i){const{auctionId:a}=i,r=/* @__PURE__ */new Date,u=r.toISOString(),d=new Date(r.getTime()+6e5).toISOString();let c="100.00",l=null,_=12,I=!1,m=[{bidId:"bid_1",consumerId:"consumer_mock_demo",amount:"80.00",timestamp:u},{bidId:"bid_2",consumerId:"consumer_mock_demo",amount:"90.00",timestamp:u}];s.get(`/auctions/${a}`,o.ok({id:a,openAt:u,closeAt:d,settleAt:d,currencyCode:"USD",reservePrice:null,minBidIncrement:"10.00",autoExtendSeconds:null,timeZone:"UTC"})),s.get(`/auctions/${a}/highest-bid`,()=>o.ok({amount:c})),s.get(`/auctions/${a}/status`,()=>{const e=null===l?"NOT_BID":n(l,c)?"WINNING":I?"OUTBID":"NOT_BID";return o.ok({status:e,lastBidAmount:l??void 0,bidCount:_,auctionEndTime:d})}),s.post(`/auctions/${a}/enter`,()=>(I=!0,o.ok({}))),s.post(`/auctions/${a}/leave`,()=>(I=!1,l=null,o.ok({}))),s.post(`/auctions/${a}/bid`,async s=>{const i=await p(s),a=i?.amount,r="string"==typeof a?a:"";return e(r)?(l=r,_+=1,t(r,c)&&(c=r),m=[...m,{bidId:`bid_${m.length+1}`,consumerId:"consumer_mock_demo",amount:r,timestamp:/* @__PURE__ */(new Date).toISOString()}],o.ok({status:n(r,c)?"WINNING":"OUTBID",lastBidAmount:r,bidCount:_,winningBidAmount:c,winningBidTime:/* @__PURE__ */(new Date).toISOString(),auctionEndTime:d,paymentToken:null})):o.badRequest("Invalid bid amount")}),s.get(`/auctions/${a}/bids/history`,()=>o.ok(m.map(e=>({...e,isHighest:e.amount===c}))))}export{i as DEFAULT_HARNESS_MOCK_API_BASE_URL,a as DEFAULT_HARNESS_MOCK_BEACON_BASE_URL,r as DEFAULT_TEST_OTP_CODE,c as HARNESS_SCENARIOS,y as installHarnessMockServer,_ as normalizeHarnessScenario};
1
+ import{isMoneyString as e,isMoneyGreaterThan as t,isMoneyGreaterThanOrEqualTo as n}from"../core/money.js";import{MockServer as s,ResponseBuilder as o}from"./mock-server.js";const i="http://mock.local/api",a="http://mock.local/beacon",r="424242";function u(){return{isEntered:!1,enteredAt:null,status:"not_waitlisted",isOpen:!1}}function d(e,t){window.localStorage.setItem(e,JSON.stringify(t))}const c={queue:{key:"queue",label:"Queue Journey",experienceId:"experience_queue_story_1",sequenceId:"sequence_queue_story_1",queueId:"queue_queue_story_1"},"waitlist-queue":{key:"waitlist-queue",label:"Waitlist to Queue Journey",experienceId:"experience_waitlist_queue_story_1",sequenceId:"sequence_waitlist_queue_story_1",queueId:"queue_waitlist_queue_story_1",waitlistId:"waitlist_waitlist_queue_story_1"},"waitlist-lifecycle":{key:"waitlist-lifecycle",label:"Waitlist Lifecycle Journey",experienceId:"experience_waitlist_lifecycle_story_1",sequenceId:"sequence_waitlist_lifecycle_story_1",queueId:"queue_waitlist_lifecycle_story_1",waitlistId:"waitlist_waitlist_lifecycle_story_1"},"auth-queue":{key:"auth-queue",label:"Auth-Gated Queue Journey",experienceId:"experience_auth_queue_story_1",sequenceId:"sequence_auth_queue_story_1",queueId:"queue_auth_queue_story_1"},"access-code-queue":{key:"access-code-queue",label:"Access-Code Queue Journey",experienceId:"experience_access_code_queue_story_1",sequenceId:"sequence_access_code_queue_story_1",queueId:"queue_access_code_queue_story_1",accessCode:"VIP123"},"upgrade-offer-queue":{key:"upgrade-offer-queue",label:"Upgrade Offer Journey",experienceId:"experience_upgrade_offer_story_1",sequenceId:"sequence_upgrade_offer_base_story_1",upgradeSequenceId:"sequence_upgrade_offer_vip_story_1",queueId:"queue_upgrade_offer_story_1",accessCode:"VIP123"},"upgrade-offer-multi-queue":{key:"upgrade-offer-multi-queue",label:"Upgrade Offer Journey (multi-tier)",experienceId:"experience_upgrade_offer_multi_story_1",sequenceId:"sequence_upgrade_offer_multi_base_story_1",upgradeSequenceId:"sequence_upgrade_offer_multi_vip_story_1",superUpgradeSequenceId:"sequence_upgrade_offer_multi_super_story_1",queueId:"queue_upgrade_offer_multi_story_1",accessCode:"VIP123"},draw:{key:"draw",label:"Draw Journey",experienceId:"experience_draw_story_1",sequenceId:"sequence_draw_story_1",drawId:"draw_draw_story_1"},"timed-release":{key:"timed-release",label:"Timed Release Journey",experienceId:"experience_timed_release_story_1",sequenceId:"sequence_timed_release_story_1",timedReleaseId:"timed_release_story_1"},"timed-release-completion":{key:"timed-release-completion",label:"Timed Release Completion Journey",experienceId:"experience_timed_release_completion_story_1",sequenceId:"sequence_timed_release_completion_story_1",timedReleaseId:"timed_release_completion_story_1"},appointment:{key:"appointment",label:"Appointment Journey",experienceId:"experience_appointment_story_1",sequenceId:"sequence_appointment_story_1",appointmentId:"appointment_story_1"},auction:{key:"auction",label:"Auction Journey",experienceId:"experience_auction_story_1",sequenceId:"sequence_auction_story_1",auctionId:"auction_auction_story_1"},"auth-draw":{key:"auth-draw",label:"Auth-Gated Draw Journey",experienceId:"experience_auth_draw_story_1",sequenceId:"sequence_auth_draw_story_1",drawId:"draw_auth_draw_story_1"},"admission-cycle":{key:"admission-cycle",label:"Admission Cycle Journey",experienceId:"experience_admission_cycle_story_1",sequenceId:"sequence_admission_cycle_story_1",queueId:"queue_admission_cycle_story_1"},"queue-denied":{key:"queue-denied",label:"Queue Denied (not selected)",experienceId:"experience_queue_denied_story_1",sequenceId:"sequence_queue_denied_story_1",queueId:"queue_queue_denied_story_1"},"draw-denied":{key:"draw-denied",label:"Draw Denied (not selected)",experienceId:"experience_draw_denied_story_1",sequenceId:"sequence_draw_denied_story_1",drawId:"draw_draw_denied_story_1"}};function l(e){return e.startsWith("http://")||e.startsWith("https://")?e:new URL(e,window.location.origin).toString()}function _(e){if(!e)return c.queue;if("string"==typeof e)return c[e]??c.queue;return{...c[e.key]??c.queue,...e}}async function p(e){try{const t=await e.clone().json();return"object"==typeof t&&null!==t?t:void 0}catch{return}}function I(e,t){return{baseUrl:e,defaultDelay:t.defaultDelay??30,logRequests:t.logRequests??!1,fallbackToNetwork:!1}}function m(e){return function(e){const t=e.headers.get("DPoP"),n=e.headers.get("X-Admission-Key-Thumbprint");if(!t||!n)return!1;const s=t.split(".");return 3===s.length&&!s.some(e=>0===e.length)&&/^[A-Za-z0-9_-]{16,}$/.test(n)}(e)?null:o.badRequest("Missing admission proof headers")}function y(e={}){const t=e.scenarios?.length?e.scenarios.map(e=>_(e)):[_(e.scenario)],n=t[0],d=l(e.apiBaseUrl??i),c=l(e.beaconBaseUrl??a),m=e.otpCode??r,y=new s(I(d,e)),A=new s(I(c,e)),E=/* @__PURE__ */new Date,x=E.toISOString(),h=new Date(E.getTime()+864e5).toISOString(),b=new Date(E.getTime()+(e.grantExpiresInMs??864e5)).toISOString(),O={sessionType:null};!function(e,t){const n={id:"session_mock_demo",consumerId:"consumer_mock_demo",type:"guest",email:null,phone:null,createdAt:t.nowIso,expiresAt:t.expiresAtIso};e.post("/auth/guest",()=>(t.authState.sessionType="guest",o.ok({session:n,accessToken:"guest_access_token_mock_demo",refreshToken:"guest_refresh_token_mock_demo",beaconToken:"guest_beacon_token_mock_demo"}))),e.post("/auth/refresh",()=>o.ok({accessToken:`access_token_mock_demo_${Date.now()}`,refreshToken:"guest_refresh_token_mock_demo",beaconToken:"authenticated"===t.authState.sessionType?"authenticated_beacon_token_mock_demo":"guest_beacon_token_mock_demo"})),e.post("/auth/otp/request",o.ok({ok:!0})),e.post("/auth/otp/verify",async e=>{const n=await p(e);return("string"==typeof n?.code?n.code:void 0)!==t.otpCode?o.error("Invalid verification code",401):(t.authState.sessionType="authenticated",o.ok({session:{id:"session_authenticated_mock_demo",consumerId:"consumer_authenticated_mock_demo",type:"authenticated",email:"string"==typeof n?.email?n.email:"sdk-harness@example.com",phone:"string"==typeof n?.phone?n.phone:null,createdAt:t.nowIso,expiresAt:t.expiresAtIso},accessToken:"authenticated_access_token_mock_demo",refreshToken:"authenticated_refresh_token_mock_demo",beaconToken:"authenticated_beacon_token_mock_demo"}))}),e.post("/auth/external/exchange",async e=>{const n=await p(e);return("string"==typeof n?.exchangeCode?n.exchangeCode:void 0)?(t.authState.sessionType="authenticated",o.ok({session:{id:"session_external_mock_demo",consumerId:"consumer_external_mock_demo",type:"authenticated",email:"external-auth@example.com",phone:null,createdAt:t.nowIso,expiresAt:t.expiresAtIso},accessToken:"external_access_token_mock_demo",refreshToken:"external_refresh_token_mock_demo",beaconToken:"authenticated_beacon_token_mock_demo"})):o.error("Invalid exchange code",401)})}(y,{nowIso:x,expiresAtIso:h,otpCode:m,authState:O}),function(e){const t=()=>o.ok({ok:!0});e.post("/events",t),e.post("/events/batch",t)}(A);const T=t.map(e=>{const t=e.waitlistId?`__fanfare_harness_waitlist__:${e.key}:${e.experienceId}`:void 0;return{scenario:e,waitlistStorageKey:t,waitlistState:e.waitlistId?function(e){const t={isEntered:!1,enteredAt:null,status:"not_waitlisted",isOpen:!1};try{const n=window.localStorage.getItem(e);if(!n)return t;const s=JSON.parse(n),o=!0===s.isEntered,i="string"==typeof s.status?s.status:void 0,a="notified"===i||"waitlisted"===i||"not_waitlisted"===i?i:o?"waitlisted":"not_waitlisted";return{isEntered:"not_waitlisted"!==a||o,enteredAt:"string"==typeof s.enteredAt?s.enteredAt:null,status:a,isOpen:!0===s.isOpen}}catch{return t}}(t??""):void 0}});var v;v=T,y.get("/consumers/me",()=>{const e=/* @__PURE__ */(new Date).toISOString(),t=v.flatMap(({scenario:t,waitlistState:n})=>{if(!t.waitlistId||!n?.isEntered||!t.queueId)return[];if("waitlist-lifecycle"===t.key&&n.isOpen)return[];const s=new Date(Date.now()+6e4).toISOString(),o="notified"===n.status?"notified":"waitlisted";return[{experienceId:t.experienceId,updatedAt:e,journeyStage:"routed",sequenceId:t.sequenceId,offers:[],sequence:{phase:"scheduled",mechanism:"queue",distribution:{type:"queue",id:t.queueId,sequenceId:t.sequenceId,lifecycle:"scheduled",startsAt:s,opensAt:s},waitlist:{waitlistId:t.waitlistId,status:o,entryId:`waitlist_entry_${t.waitlistId}`}}}]});return o.ok({active:{journeys:t}})});for(const{scenario:s,waitlistStorageKey:o,waitlistState:i}of T){if(g(y,s,{authState:O,expiresAtIso:b,waitlistState:i}),s.queueId){const e=[s.queueId,...s.upgradeSequenceId?[`${s.queueId}_vip`]:[],...s.superUpgradeSequenceId?[`${s.queueId}_super`]:[]];for(const t of e)q(y,{queueId:t,experienceId:s.experienceId,name:"Journey Queue",initialPosition:"admission-cycle"===s.key?2:5,initialEstimatedWaitSeconds:90,autoExpireAfterGrant:"admission-cycle"===s.key,forceDenyOnEnter:"queue-denied"===s.key})}s.waitlistId&&k(y,{waitlistId:s.waitlistId,sequenceId:s.sequenceId,state:i??u(),storageKey:o??""}),s.drawId&&w(y,{drawId:s.drawId,forceDenyOnEnter:"draw-denied"===s.key}),s.timedReleaseId&&S(y,{timedReleaseId:s.timedReleaseId,closeAt:new Date(Date.now()+9e5).toISOString(),grantOnEnter:"timed-release-completion"!==s.key}),s.appointmentId&&f(y,{appointmentId:s.appointmentId}),s.auctionId&&D(y,{auctionId:s.auctionId})}return y.start(),A.start(),{apiBaseUrl:d,beaconBaseUrl:c,scenario:n,scenarios:t,stop(){A.stop(),y.stop()},getRequests:()=>({api:y.getRequests(),beacon:A.getRequests()})}}function g(e,t,n){const s=(/* @__PURE__ */new Date).toISOString();e.get(`/experiences/${t.experienceId}`,o.ok({id:t.experienceId,name:t.label,createdAt:s,updatedAt:s,i18n:{en:{"start.title":t.label,"start.description":"Mock data served from the harness mock server."}}})),e.post(`/experiences/${t.experienceId}/enter`,o.ok({experienceId:t.experienceId,enteredAt:s}));const i=e=>({id:t.sequenceId,name:e,experienceId:t.experienceId,priority:1,color:"#000000"}),a=(e,n,s)=>({schemaVersion:1,experienceId:t.experienceId,outcome:"gated",selected:{sequence:e,status:"gated",gates:[n],reasons:s},offers:[],outcomeReasons:s}),r=e=>({schemaVersion:1,experienceId:t.experienceId,outcome:"routed",selected:{sequence:e,status:"admissible",gates:[],reasons:[],sequenceCapabilityGrant:`mock_sequence_capability_grant_${t.sequenceId}`,sequenceCapabilityGrantExpiresAt:n.expiresAtIso},offers:[],outcomeReasons:[]});if("auth-queue"===t.key||"auth-draw"===t.key)e.post(`/experiences/${t.experienceId}/route`,()=>"authenticated"!==n.authState.sessionType?o.ok(a(i("auth-draw"===t.key?"Auth-Gated Draw Sequence":"Auth-Gated Queue Sequence"),{type:"authentication",status:"required",reason:{code:"AUTH_REQUIRED"}},[{code:"AUTH_REQUIRED"}])):o.ok(r(i("auth-draw"===t.key?"Auth-Gated Draw Sequence":"Auth-Gated Queue Sequence"))));else if("access-code-queue"===t.key)e.post(`/experiences/${t.experienceId}/route`,async e=>{const n=await p(e),s="string"==typeof n?.accessCode?n.accessCode:void 0;return s?s!==t.accessCode?o.ok(a(i("Access-Code Queue Sequence"),{type:"access_code",status:"invalid",reason:{code:"ACCESS_CODE_INVALID"}},[{code:"ACCESS_CODE_INVALID"}])):o.ok(r(i("Access-Code Queue Sequence"))):o.ok(a(i("Access-Code Queue Sequence"),{type:"access_code",status:"missing",reason:{code:"ACCESS_CODE_REQUIRED"}},[{code:"ACCESS_CODE_REQUIRED"}]))});else if("upgrade-offer-queue"===t.key){const s={id:t.sequenceId,name:"General Admission",experienceId:t.experienceId,priority:1,color:"#000000"},i={id:t.upgradeSequenceId??`${t.sequenceId}_vip`,name:"VIP",experienceId:t.experienceId,priority:10,color:"#7c3aed"},a=e=>{const t="missing"===e?"ACCESS_CODE_REQUIRED":"ACCESS_CODE_INVALID";return{sequence:i,status:"gated",gates:[{type:"access_code",status:e,reason:{code:t}}],reasons:[{code:t}]}},r=e=>({schemaVersion:1,experienceId:t.experienceId,outcome:"routed",selected:{sequence:s,status:"admissible",gates:[],reasons:[],sequenceCapabilityGrant:`mock_sequence_capability_grant_${t.sequenceId}`,sequenceCapabilityGrantExpiresAt:n.expiresAtIso},offers:[a(e)],outcomeReasons:[]}),u=()=>({schemaVersion:1,experienceId:t.experienceId,outcome:"routed",selected:{sequence:i,status:"admissible",gates:[],reasons:[],sequenceCapabilityGrant:`mock_sequence_capability_grant_${i.id}`,sequenceCapabilityGrantExpiresAt:n.expiresAtIso},offers:[],outcomeReasons:[]});e.post(`/experiences/${t.experienceId}/route`,async e=>{const n=await p(e),s="string"==typeof n?.accessCode?n.accessCode:void 0;return s?s!==t.accessCode?o.ok(r("invalid")):o.ok(u()):o.ok(r("missing"))})}else if("upgrade-offer-multi-queue"===t.key){const s={id:t.sequenceId,name:"General Admission",experienceId:t.experienceId,priority:1,color:"#000000"},i={id:t.upgradeSequenceId??`${t.sequenceId}_vip`,name:"VIP",experienceId:t.experienceId,priority:10,color:"#7c3aed"},a={id:t.superUpgradeSequenceId??`${t.sequenceId}_super`,name:"Super VIP",experienceId:t.experienceId,priority:20,color:"#f59e0b"},r=(e,t)=>{const n="missing"===t?"ACCESS_CODE_REQUIRED":"ACCESS_CODE_INVALID";return{sequence:e,status:"gated",gates:[{type:"access_code",status:t,reason:{code:n}}],reasons:[{code:n}]}},u=(e,s)=>({schemaVersion:1,experienceId:t.experienceId,outcome:"routed",selected:{sequence:e,status:"admissible",gates:[],reasons:[],sequenceCapabilityGrant:`mock_sequence_capability_grant_${e.id}`,sequenceCapabilityGrantExpiresAt:n.expiresAtIso},offers:s,outcomeReasons:[]}),d=()=>({schemaVersion:1,experienceId:t.experienceId,outcome:"gated",selected:{sequence:s,status:"gated",gates:[{type:"access_code",status:"invalid",reason:{code:"ACCESS_CODE_INVALID"}}],reasons:[{code:"ACCESS_CODE_INVALID"}]},offers:[],outcomeReasons:[{code:"ACCESS_CODE_INVALID"}]});e.post(`/experiences/${t.experienceId}/route`,async e=>{const n=await p(e),c="string"==typeof n?.accessCode?n.accessCode:void 0;if("LOCKDOWN"===c)return o.ok(d());if("SUPER123"===c)return o.ok(u(a,[]));if(c===t.accessCode)return o.ok(u(i,[r(a,"invalid")]));const l=c?"invalid":"missing";return o.ok(u(s,[r(a,l),r(i,l)]))})}else e.post(`/experiences/${t.experienceId}/route`,o.ok(r(i(`${t.label} Sequence`))));if("waitlist-queue"!==t.key&&"waitlist-lifecycle"!==t.key)if("draw"!==t.key&&"auth-draw"!==t.key&&"draw-denied"!==t.key)if("timed-release"!==t.key&&"timed-release-completion"!==t.key)if("appointment"!==t.key)if("auction"!==t.key){if("upgrade-offer-queue"===t.key||"upgrade-offer-multi-queue"===t.key){const n=(e,t)=>()=>{const n=/* @__PURE__ */(new Date).toISOString();return o.ok({recentPast:void 0,active:{type:"queue",id:e,status:"active",details:{id:e,type:"queue",openAt:n,metadata:{label:t},estimatedWaitTime:300,queueLength:10}},upcoming:void 0})};return e.get(`/sequences/${t.sequenceId}/distributions`,n(t.queueId??"queue_upgrade_base","General Admission Queue")),e.get(`/sequences/${t.upgradeSequenceId??`${t.sequenceId}_vip`}/distributions`,n(`${t.queueId}_vip`,"VIP Queue")),void("upgrade-offer-multi-queue"===t.key&&e.get(`/sequences/${t.superUpgradeSequenceId??`${t.sequenceId}_super`}/distributions`,n(`${t.queueId}_super`,"Super VIP Queue")))}e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=/* @__PURE__ */(new Date).toISOString();return o.ok({recentPast:void 0,active:{type:"queue",id:t.queueId,status:"active",details:{id:t.queueId,type:"queue",openAt:e,metadata:{label:"Journey Queue"},estimatedWaitTime:300,queueLength:10}},upcoming:void 0})})}else e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=Date.now(),n=new Date(e).toISOString();return o.ok({recentPast:void 0,active:{type:"auction",id:t.auctionId,status:"active",details:{id:t.auctionId,type:"auction",openAt:n,endsAt:new Date(e+36e5).toISOString(),startsAt:n,currencyCode:"USD",reservePrice:"50.00",bidIncrement:"10.00"}},upcoming:void 0})});else e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=t.appointmentId??"appointment_mock",n=Date.now(),s=new Date(n-6e4).toISOString(),i=new Date(n+6048e5).toISOString();return o.ok({recentPast:void 0,active:{type:"appointment",id:e,status:"active",details:{id:e,type:"appointment",openAt:s,closeAt:i,bookingWindowOpenAt:s,bookingWindowCloseAt:i,slotDurationMinutes:30,timezone:"UTC",locationId:"loc_harness_1",totalSlots:3,availableSlots:3}},upcoming:void 0})});else e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=Date.now();return o.ok({recentPast:void 0,active:{type:"timed_release",id:t.timedReleaseId,status:"active",details:{id:t.timedReleaseId,type:"timed_release",openAt:new Date(e).toISOString(),closeAt:new Date(e+9e5).toISOString()}},upcoming:void 0})});else e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=Date.now(),n=new Date(e+(t.drawDelayMs??72e5)).toISOString();return o.ok({recentPast:void 0,active:{type:"draw",id:t.drawId,status:"active",details:{id:t.drawId,type:"draw",openAt:new Date(e).toISOString(),drawTime:n,entryDeadline:n}},upcoming:void 0})});else e.get(`/sequences/${t.sequenceId}/distributions`,()=>{const e=/* @__PURE__ */(new Date).toISOString();return("waitlist-lifecycle"===t.key?!0===n.waitlistState?.isOpen:n.waitlistState?.isEntered)?o.ok({recentPast:void 0,active:{type:"queue",id:t.queueId,status:"active",details:{id:t.queueId,type:"queue",openAt:e,closeAt:new Date(Date.now()+18e5).toISOString(),metadata:{label:"Journey Queue (Window Open)"},estimatedWaitTime:300,queueLength:10}},upcoming:void 0}):o.ok({recentPast:void 0,active:void 0,upcoming:{type:"queue",id:t.queueId,startsAt:new Date(Date.now()+6e4).toISOString(),waitlistId:t.waitlistId}})})}function q(e,t){const{queueId:n,experienceId:s="experience_mock",name:i="Mock Queue",initialPosition:a=12,initialEstimatedWaitSeconds:r=300,autoExpireAfterGrant:u=!1,forceDenyOnEnter:d=!1}=t;let c=!1,l=!1,_=a,p=r,I=0;e.get(`/queues/${n}`,o.ok({id:n,experienceId:s,name:i,status:"open",capacity:500,currentSize:247,estimatedWaitTime:p,openAt:new Date(Date.now()-3e5).toISOString(),closeAt:new Date(Date.now()+36e5).toISOString()})),e.post(`/queues/${n}/enter`,e=>{const t=m(e);return t||(d?o.ok({status:"DENIED"}):(c=!0,l=!1,I=0,_=a,p=r,o.ok({position:_,estimatedWaitTimeInSeconds:p,status:"QUEUED"})))}),e.post(`/queues/${n}/leave`,()=>(c=!1,l=!1,I=0,o.ok({}))),e.get(`/queues/${n}/status`,()=>c?l?(I+=1,u&&I>1?(c=!1,l=!1,o.ok({status:"EXPIRED"})):o.ok({status:"GRANTED",admissionGrant:`admission_grant_${n}`,expiresAt:new Date(Date.now()+6e5).toISOString()})):(_=Math.max(1,_-1),p=Math.max(0,p-30),_<=1?(l=!0,I=1,o.ok({status:"GRANTED",admissionGrant:`admission_grant_${n}`,expiresAt:new Date(Date.now()+6e5).toISOString()})):o.ok({status:"QUEUED",position:_,estimatedWaitTimeInSeconds:p})):o.ok({status:"NOT_QUEUED"}))}function w(e,t){const{drawId:n,drawAtOffsetMs:s=72e5,forceDenyOnEnter:i=!1}=t;let a="NOT_ENTERED",r=0;e.get(`/draws/${n}`,()=>o.ok({id:n,openAt:new Date(Date.now()-6e4).toISOString(),closeAt:null,timeZone:"UTC",drawAt:new Date(Date.now()+s).toISOString(),capacity:null,continueSelectingUntilCompleted:null})),e.get(`/draws/${n}/status`,()=>"NOT_ENTERED"===a?o.ok({status:"NOT_ENTERED"}):(r+=1,r>=2?o.ok({status:"WON",wonAt:/* @__PURE__ */(new Date).toISOString(),admissionGrant:`admission_grant_${n}`}):o.ok({status:"ENTERED"}))),e.post(`/draws/${n}/enter`,e=>{const t=m(e);return t||(i?o.ok({status:"DENIED"}):(a="ENTERED",r=0,o.ok({status:"ENTERED"})))}),e.post(`/draws/${n}/leave`,()=>(a="NOT_ENTERED",r=0,o.ok({})))}function k(e,t){const{waitlistId:n,sequenceId:s,state:i,storageKey:a}=t;e.get(`/waitlists/${n}/status`,()=>o.ok({waitlistId:n,isEntered:i.isEntered,enteredAt:i.enteredAt??void 0})),e.post(`/waitlists/${n}/enter`,()=>(i.isEntered=!0,i.enteredAt=/* @__PURE__ */(new Date).toISOString(),i.status="waitlisted",d(a,i),o.ok({id:`waitlist_entry_${n}`,waitlistId:n,sequenceId:s,consumerId:"consumer_mock_demo",enteredAt:i.enteredAt})));const r=()=>(i.isEntered=!1,i.enteredAt=null,i.status="not_waitlisted",i.isOpen=!1,d(a,i),o.ok({}));e.delete(`/waitlists/${n}/leave`,r),e.post(`/waitlists/${n}/leave`,r)}function S(e,t){const{timedReleaseId:n,closeAt:s,grantOnEnter:i=!0}=t;let a="NOT_ENTERED",r=null,u=null,d=0;const c=()=>`admission_grant_${n}_${d+=1}`;e.get(`/timed-releases/${n}`,o.ok({id:n,openAt:new Date(Date.now()-6e4).toISOString(),closeAt:s,timeZone:"America/Los_Angeles",supportsGuest:!0})),e.get(`/timed-releases/${n}/status`,()=>"ENTERED"===a?o.ok({status:"ENTERED",enteredAt:r??/* @__PURE__ */(new Date).toISOString(),...i?{admissionGrant:c()}:{},expiresAt:u??new Date(Date.now()+6e4).toISOString()}):"LEFT"===a?o.ok({status:"LEFT",leftAt:/* @__PURE__ */(new Date).toISOString()}):o.ok({status:"NOT_ENTERED"})),e.post(`/timed-releases/${n}/enter`,async e=>{const t=await p(e),n="string"==typeof t?.variantId?t.variantId:void 0;return a="ENTERED",r=/* @__PURE__ */(new Date).toISOString(),u=new Date(Date.now()+6e4).toISOString(),o.ok({status:"ENTERED",enteredAt:r,selectedVariantId:n,...i?{admissionGrant:c()}:{},expiresAt:u})}),e.post(`/timed-releases/${n}/leave`,()=>"ENTERED"===a?o.error("Participant holds a live admission; it lapses when its window ends",409):(a="LEFT",o.ok({})))}function f(e,t){const{appointmentId:n}=t,s="loc_harness_1";let i=null;const a=(e,t,n)=>{const s=/* @__PURE__ */new Date;return new Date(Date.UTC(s.getUTCFullYear(),s.getUTCMonth(),s.getUTCDate()+e,t,n))},r=(e,t)=>({id:e,startTime:t.toISOString(),endTime:new Date(t.getTime()+18e5).toISOString(),locationId:s,locationName:"Suite 100",timezone:"UTC",capacity:4,available:3}),u=[r("slot_0900",a(1,9,0)),r("slot_1030",a(1,10,30)),r("slot_1100",a(2,11,0))],d=(e,t)=>{const s=(e=>u.find(t=>t.id===e)??u[0])(e);return{bookingId:`booking_${n}`,appointmentId:n,consumerId:"consumer_mock_demo",slotId:s.id,locationId:s.locationId,locationName:s.locationName,startTime:s.startTime,endTime:s.endTime,confirmationCode:t,status:"booked",createdAt:/* @__PURE__ */(new Date).toISOString()}};e.get(`/appointments/${n}`,o.ok({id:n,type:"appointment",openAt:new Date(Date.now()-6e4).toISOString(),closeAt:new Date(Date.now()+6048e5).toISOString(),slotDurationMinutes:30,timeZone:"UTC",locationId:s,totalSlots:u.length,availableSlots:u.length})),e.get(`/appointments/${n}/slots`,()=>o.ok({slots:u})),e.get(`/appointments/${n}/me`,()=>o.ok({booking:i})),e.post(`/appointments/${n}/book`,async e=>{const t=await p(e),n="string"==typeof t?.slotId?t.slotId:u[0].id;return i=d(n,"FF-1001"),o.ok(i)}),e.post(`/appointments/${n}/reschedule`,async e=>{const t=await p(e),n=i?.slotId??u[0].id,s="string"==typeof t?.newSlotId?t.newSlotId:u[1].id;return i=d(s,"FF-2001"),o.ok({bookingId:i.bookingId,previousSlotId:n,newSlotId:i.slotId,confirmationCode:i.confirmationCode,status:i.status})}),e.post(`/appointments/${n}/cancel`,()=>{const e=i?.bookingId??`booking_${n}`;return i=null,o.ok({message:"ok",bookingId:e})})}function D(s,i){const{auctionId:a}=i,r=/* @__PURE__ */new Date,u=r.toISOString(),d=new Date(r.getTime()+6e5).toISOString();let c="100.00",l=null,_=12,I=!1,m=[{bidId:"bid_1",consumerId:"consumer_mock_demo",amount:"80.00",timestamp:u},{bidId:"bid_2",consumerId:"consumer_mock_demo",amount:"90.00",timestamp:u}];s.get(`/auctions/${a}`,o.ok({id:a,openAt:u,closeAt:d,settleAt:d,currencyCode:"USD",reservePrice:null,minBidIncrement:"10.00",autoExtendSeconds:null,timeZone:"UTC"})),s.get(`/auctions/${a}/highest-bid`,()=>o.ok({amount:c})),s.get(`/auctions/${a}/status`,()=>{const e=null===l?"NOT_BID":n(l,c)?"WINNING":I?"OUTBID":"NOT_BID";return o.ok({status:e,lastBidAmount:l??void 0,bidCount:_,auctionEndTime:d})}),s.post(`/auctions/${a}/enter`,()=>(I=!0,o.ok({}))),s.post(`/auctions/${a}/leave`,()=>(I=!1,l=null,o.ok({}))),s.post(`/auctions/${a}/bid`,async s=>{const i=await p(s),a=i?.amount,r="string"==typeof a?a:"";return e(r)?(l=r,_+=1,t(r,c)&&(c=r),m=[...m,{bidId:`bid_${m.length+1}`,consumerId:"consumer_mock_demo",amount:r,timestamp:/* @__PURE__ */(new Date).toISOString()}],o.ok({status:n(r,c)?"WINNING":"OUTBID",lastBidAmount:r,bidCount:_,winningBidAmount:c,winningBidTime:/* @__PURE__ */(new Date).toISOString(),auctionEndTime:d,paymentToken:null})):o.badRequest("Invalid bid amount")}),s.get(`/auctions/${a}/bids/history`,()=>o.ok(m.map(e=>({...e,isHighest:e.amount===c}))))}export{i as DEFAULT_HARNESS_MOCK_API_BASE_URL,a as DEFAULT_HARNESS_MOCK_BEACON_BASE_URL,r as DEFAULT_TEST_OTP_CODE,c as HARNESS_SCENARIOS,y as installHarnessMockServer,_ as normalizeHarnessScenario};
@@ -1 +1 @@
1
- import{atom as e,computed as t}from"nanostores";import{projectAppointmentSequence as n,createAppointmentDriver as i}from"../appointments/appointment.driver.js";import{projectAuctionSequence as a,createAuctionDriver as r}from"../auctions/auction.driver.js";import{projectDrawSequence as s,createDrawDriver as o}from"../draws/draw.driver.js";import{buildJourneyView as c}from"../experiences/journey-view.js";import{projectQueueSequence as u,createQueueDriver as p}from"../queues/queue.driver.js";import{projectTimedReleaseSequence as m,createTimedReleaseDriver as d}from"../timed-releases/timed-release.driver.js";function l(e){if("routed"!==e.journeyStage||"participating"!==e.sequence.phase)return{type:"queue"};switch(e.sequence.mechanism){case"queue":return{type:"queue"};case"draw":return{type:"draw"};case"auction":return{type:"auction"};case"timed_release":return{type:"timed_release"};case"appointment":return{type:"appointment"}}}function v(e="exp_123",t){return{revision:1,updatedAt:Date.now(),journeyStage:"ready",experienceId:e,experience:void 0,events:[],lastSeenEventId:void 0,...t}}function g(e){return{...v(e?.experienceId),journeyStage:"gated",gates:e?.gates??[],selected:e?.selected,...e?.overrides}}function y(e){const t=e.sequence&&"mechanism"in e.sequence?e.sequence:function(e){if(!e.distribution)return{phase:"unavailable"};switch(e.distribution.type){case"appointment":return n(e);case"auction":return a(e);case"draw":return s(e);case"queue":return u(e);case"timed_release":return m(e)}}({distribution:e.distribution,consumer:e.consumer??e.overrides?.consumer,grant:e.grant??e.overrides?.grant,outcome:e.outcome??e.overrides?.outcome}),{consumer:i,grant:r,outcome:o,actions:c,sequence:p,...d}=e.overrides??{};return{...v(e.experienceId),journeyStage:"routed",sequenceId:e.sequenceId??"seq_1",offers:e.offers??[],sequence:t,...d}}function h(t){const n=e(t??v());let i=0;return{atom:n,get:()=>n.get(),listen:e=>{i+=1;const t=n.listen(e);return()=>{i-=1,t()}},subscribe:e=>{i+=1;const t=n.subscribe(e);return()=>{i-=1,t()}},setValue:e=>n.set(e),getListenerCount:()=>i}}function b(n){const a=n?.initialSnapshot??v(),s=h(a),u=e(l(s.get())),m=n?.actions??{},g={start:async()=>{},reroute:async()=>{},submitAccessCode:async()=>({outcome:"unchanged"}),retry:async()=>{},enter:async()=>{},leave:async()=>{},joinWaitlist:async()=>{},exitWaitlist:async()=>{},bid:async()=>{},complete:async()=>{},book:async()=>{},cancel:async()=>{},reschedule:async()=>{},claim:()=>{const e=s.get();if("routed"===e.journeyStage&&"granted"===e.sequence.phase)return e.sequence.grant;throw new Error("No grant is available to claim")},...n?.handlers,...m},y=function(t){const n=p({queues:{enter:async()=>({status:"QUEUED",position:0,estimatedWaitTimeInSeconds:0}),leave:async()=>{},startMonitoring:()=>{},stopMonitoring:()=>{}},runSerializedOperation:async e=>e(),getCurrentSequence:()=>{},commit:()=>{},applyMonitorUpdate:()=>{},ensureDisplayAtom:()=>{},stopRuntimeMonitoring:()=>{},emitEvent:()=>{}}),a=d({timedReleases:{enter:async()=>({status:"NOT_ENTERED"}),leave:async()=>{},complete:()=>{},startMonitoring:()=>{},stopMonitoring:()=>{}},runSerializedOperation:async e=>e(),getCurrentSequence:()=>{},commit:()=>{},applyMonitorUpdate:()=>{},ensureDisplayAtom:()=>{},stopRuntimeMonitoring:()=>{},emitEvent:()=>{}}),s=o({draws:{enter:async()=>({status:"ENTERED"}),leave:async()=>{},startMonitoring:()=>{},stopMonitoring:()=>{}},runSerializedOperation:async e=>e(),getCurrentSequence:()=>{},commit:()=>{},applyMonitorUpdate:()=>{},ensureDisplayAtom:()=>{},stopRuntimeMonitoring:()=>{},emitEvent:()=>{}}),c=r({auctions:{enter:async()=>{},leave:async()=>{},status:async()=>({auctionId:"auction_1",status:"watching",highestBid:"0.00",timeRemaining:0}),bid:async(e,t)=>({status:"accepted",amount:t,highestBid:t,bidCount:1}),startMonitoring:()=>{},stopMonitoring:()=>{}},runSerializedOperation:async e=>e(),getCurrentSequence:()=>{},commit:()=>{},applyMonitorUpdate:()=>{},ensureDisplayAtom:()=>e({type:"auction"}),stopRuntimeMonitoring:()=>{},emitEvent:()=>{}}),u=i({appointments:{book:async(e,t,n)=>({bookingId:"booking_1",appointmentId:e,slotId:t,locationId:n,startTime:new Date(Date.now()+6e4).toISOString(),endTime:new Date(Date.now()+12e4).toISOString(),consumerStatus:"booked"}),cancel:async()=>{},reschedule:async(e,t,n)=>({bookingId:"booking_1",appointmentId:e,slotId:t,locationId:n,startTime:new Date(Date.now()+6e4).toISOString(),endTime:new Date(Date.now()+12e4).toISOString(),consumerStatus:"booked"}),bumpMonitorGeneration:()=>{},startMonitoring:()=>{},stopMonitoring:()=>{}},runSerializedOperation:async e=>e(),getCurrentSequence:()=>{},getDisplayState:()=>({type:"appointment"}),commit:()=>{},applyMonitorUpdate:()=>{},ensureDisplayAtom:()=>e({type:"appointment"}),stopRuntimeMonitoring:()=>{},emitEvent:()=>{}});return{appointment:{mechanism:"appointment",project:u.project.bind(u),buildView:(e,n)=>{const i=u.buildView(e,n);return"enterable"===i.phase&&"appointment"===i.mechanism?{...i,book:(e,n)=>t.book(e,n)}:"participating"===i.phase&&"appointment"===i.mechanism?{...i,cancel:e=>t.cancel(e),reschedule:(e,n)=>t.reschedule(e,n)}:i}},auction:{mechanism:"auction",project:c.project.bind(c),buildView:(e,n)=>{const i=c.buildView(e,n);return"enterable"===i.phase&&"auction"===i.mechanism?{...i,bid:e=>t.bid(e)}:"participating"===i.phase&&"auction"===i.mechanism?{...i,bid:e=>t.bid(e),leave:()=>t.leave()}:i},enter:()=>t.enter(),leave:()=>t.leave()},queue:{mechanism:"queue",project:n.project.bind(n),buildView:(e,i)=>{const a=n.buildView(e,i);return"enterable"===a.phase&&"queue"===a.mechanism?{...a,enter:()=>t.enter()}:"participating"===a.phase&&"queue"===a.mechanism?{...a,leave:()=>t.leave()}:a},enter:()=>t.enter(),leave:()=>t.leave()},timed_release:{mechanism:"timed_release",project:a.project.bind(a),buildView:(e,n)=>{const i=a.buildView(e,n);return"enterable"===i.phase&&"timed_release"===i.mechanism?{...i,enter:()=>t.enter()}:"participating"===i.phase&&"timed_release"===i.mechanism?{...i,leave:()=>t.leave(),complete:()=>t.complete()}:i},enter:()=>t.enter(),leave:()=>t.leave()},draw:{mechanism:"draw",project:s.project.bind(s),buildView:(e,n)=>{const i=s.buildView(e,n);return"enterable"===i.phase&&"draw"===i.mechanism?{...i,enter:()=>t.enter()}:"participating"===i.phase&&"draw"===i.mechanism?{...i,leave:()=>t.leave()}:i},enter:()=>t.enter(),leave:()=>t.leave()}}}(g),b=t(s.atom,e=>c(e,g,u,y)),w=t(s.atom,e=>e.events),S=t(s.atom,e=>{if(!e.events.length)return null;const t=e.lastSeenEventId??"",n=e.events.filter(e=>e.id>t);return n.length?n[n.length-1]:null}),q=e=>{if(m.ackEvent)return void m.ackEvent(e);const t=s.get();t.events.some(t=>t.id===e)&&s.setValue({...t,lastSeenEventId:e})},E=()=>{if(m.ackAllEvents)return void m.ackAllEvents();const e=s.get(),t=e.events[e.events.length-1];t&&s.setValue({...e,lastSeenEventId:t.id})},f=()=>{if(m.destroy)return void m.destroy();const e=s.get().experienceId||a.experienceId;s.setValue(v(e)),u.set(l(v(e)))};return{handle:{view$:b,snapshot$:s.atom,events$:w,latestEvent$:S,ackEvent:q,ackAllEvents:E,destroy:f},snapshot$:s,view$:b,events$:w,latestEvent$:S,handlers:g,setSnapshot:e=>{s.setValue(e)},updateSnapshot:e=>{const t=e(s.get());s.setValue(t)},setDisplayState:e=>u.set(e),ackEvent:q,ackAllEvents:E,destroy:f}}export{b as createMockJourneyHandle,h as createMockJourneyStore,g as makeGatedSnapshot,v as makeReadySnapshot,y as makeRoutedSnapshot};
1
+ import{atom as e,computed as t}from"nanostores";import{projectAppointmentSequence as n,createAppointmentDriver as i}from"../appointments/appointment.driver.js";import{projectAuctionSequence as a,createAuctionDriver as r}from"../auctions/auction.driver.js";import{projectDrawSequence as s,createDrawDriver as o}from"../draws/draw.driver.js";import{buildJourneyView as c}from"../experiences/journey-view.js";import{projectQueueSequence as u,createQueueDriver as p}from"../queues/queue.driver.js";import{projectTimedReleaseSequence as m,createTimedReleaseDriver as l}from"../timed-releases/timed-release.driver.js";function d(e){if("routed"!==e.journeyStage||"participating"!==e.sequence.phase)return{type:"queue"};switch(e.sequence.mechanism){case"queue":return{type:"queue"};case"draw":return{type:"draw"};case"auction":return{type:"auction"};case"timed_release":return{type:"timed_release"};case"appointment":return{type:"appointment"}}}function v(e="exp_123",t){return{revision:1,updatedAt:Date.now(),journeyStage:"ready",experienceId:e,experience:void 0,events:[],lastSeenEventId:void 0,...t}}function g(e){return{...v(e?.experienceId),journeyStage:"gated",gates:e?.gates??[],selected:e?.selected,...e?.overrides}}function y(e){const t=e.sequence&&"mechanism"in e.sequence?e.sequence:function(e){if(!e.distribution)return{phase:"unavailable"};switch(e.distribution.type){case"appointment":return n(e);case"auction":return a(e);case"draw":return s(e);case"queue":return u(e);case"timed_release":return m(e)}}({distribution:e.distribution,consumer:e.consumer??e.overrides?.consumer,grant:e.grant??e.overrides?.grant,outcome:e.outcome??e.overrides?.outcome}),{consumer:i,grant:r,outcome:o,actions:c,sequence:p,...l}=e.overrides??{};return{...v(e.experienceId),journeyStage:"routed",sequenceId:e.sequenceId??"seq_1",offers:e.offers??[],sequence:t,...l}}function h(t){const n=e(t??v());let i=0;return{atom:n,get:()=>n.get(),listen:e=>{i+=1;const t=n.listen(e);return()=>{i-=1,t()}},subscribe:e=>{i+=1;const t=n.subscribe(e);return()=>{i-=1,t()}},setValue:e=>n.set(e),getListenerCount:()=>i}}function b(n){const a=n?.initialSnapshot??v(),s=h(a),u=e(d(s.get())),m=n?.actions??{},g={start:async()=>{},reroute:async()=>{},submitAccessCode:async()=>({outcome:"unchanged"}),retry:async()=>{},enter:async()=>{},leave:async()=>{},joinWaitlist:async()=>{},exitWaitlist:async()=>{},bid:async()=>{},complete:async()=>{},book:async()=>{},cancel:async()=>{},reschedule:async()=>{},claim:()=>{const e=s.get();if("routed"===e.journeyStage&&"granted"===e.sequence.phase)return e.sequence.grant;throw new Error("No grant is available to claim")},...n?.handlers,...m},y=function(t){const n=p({queues:{enter:async()=>({status:"QUEUED",position:0,estimatedWaitTimeInSeconds:0}),leave:async()=>{},startMonitoring:()=>{},stopMonitoring:()=>{}},runSerializedOperation:async e=>e(),getCurrentSequence:()=>{},commit:()=>{},applyMonitorUpdate:()=>{},ensureDisplayAtom:()=>{},stopRuntimeMonitoring:()=>{},emitEvent:()=>{}}),a=l({timedReleases:{enter:async()=>({status:"NOT_ENTERED"}),leave:async()=>{},complete:()=>{},startMonitoring:()=>{},stopMonitoring:()=>{}},runSerializedOperation:async e=>e(),getCurrentSequence:()=>{},commit:()=>{},applyMonitorUpdate:()=>{},ensureDisplayAtom:()=>{},stopRuntimeMonitoring:()=>{},emitEvent:()=>{}}),s=o({draws:{enter:async()=>({status:"ENTERED"}),leave:async()=>{},startMonitoring:()=>{},stopMonitoring:()=>{}},runSerializedOperation:async e=>e(),getCurrentSequence:()=>{},commit:()=>{},applyMonitorUpdate:()=>{},ensureDisplayAtom:()=>{},stopRuntimeMonitoring:()=>{},emitEvent:()=>{}}),c=r({auctions:{enter:async()=>{},leave:async()=>{},status:async()=>({auctionId:"auction_1",status:"watching",highestBid:"0.00",timeRemaining:0}),bid:async(e,t)=>({status:"accepted",amount:t,highestBid:t,bidCount:1}),startMonitoring:()=>{},stopMonitoring:()=>{}},runSerializedOperation:async e=>e(),getCurrentSequence:()=>{},getDisplayState:()=>null,commit:()=>{},applyMonitorUpdate:()=>{},ensureDisplayAtom:()=>e({type:"auction"}),stopRuntimeMonitoring:()=>{},emitEvent:()=>{}}),u=i({appointments:{book:async(e,t,n)=>({bookingId:"booking_1",appointmentId:e,slotId:t,locationId:n,startTime:new Date(Date.now()+6e4).toISOString(),endTime:new Date(Date.now()+12e4).toISOString(),consumerStatus:"booked"}),cancel:async()=>{},reschedule:async(e,t,n)=>({bookingId:"booking_1",appointmentId:e,slotId:t,locationId:n,startTime:new Date(Date.now()+6e4).toISOString(),endTime:new Date(Date.now()+12e4).toISOString(),consumerStatus:"booked"}),bumpMonitorGeneration:()=>{},startMonitoring:()=>{},stopMonitoring:()=>{}},runSerializedOperation:async e=>e(),getCurrentSequence:()=>{},getDisplayState:()=>({type:"appointment"}),commit:()=>{},applyMonitorUpdate:()=>{},ensureDisplayAtom:()=>e({type:"appointment"}),stopRuntimeMonitoring:()=>{},emitEvent:()=>{}});return{appointment:{mechanism:"appointment",project:u.project.bind(u),buildView:(e,n)=>{const i=u.buildView(e,n);return"enterable"===i.phase&&"appointment"===i.mechanism?{...i,book:(e,n)=>t.book(e,n)}:"participating"===i.phase&&"appointment"===i.mechanism?{...i,cancel:e=>t.cancel(e),reschedule:(e,n)=>t.reschedule(e,n)}:i}},auction:{mechanism:"auction",project:c.project.bind(c),buildView:(e,n)=>{const i=c.buildView(e,n);return"enterable"===i.phase&&"auction"===i.mechanism?{...i,bid:e=>t.bid(e)}:"participating"===i.phase&&"auction"===i.mechanism?{...i,bid:e=>t.bid(e),leave:()=>t.leave()}:i},enter:()=>t.enter(),leave:()=>t.leave()},queue:{mechanism:"queue",project:n.project.bind(n),buildView:(e,i)=>{const a=n.buildView(e,i);return"enterable"===a.phase&&"queue"===a.mechanism?{...a,enter:()=>t.enter()}:"participating"===a.phase&&"queue"===a.mechanism?{...a,leave:()=>t.leave()}:a},enter:()=>t.enter(),leave:()=>t.leave()},timed_release:{mechanism:"timed_release",project:a.project.bind(a),buildView:(e,n)=>{const i=a.buildView(e,n);return"enterable"===i.phase&&"timed_release"===i.mechanism?{...i,enter:()=>t.enter()}:"participating"===i.phase&&"timed_release"===i.mechanism?{...i,leave:()=>t.leave(),complete:()=>t.complete()}:i},enter:()=>t.enter(),leave:()=>t.leave()},draw:{mechanism:"draw",project:s.project.bind(s),buildView:(e,n)=>{const i=s.buildView(e,n);return"enterable"===i.phase&&"draw"===i.mechanism?{...i,enter:()=>t.enter()}:"participating"===i.phase&&"draw"===i.mechanism?{...i,leave:()=>t.leave()}:i},enter:()=>t.enter(),leave:()=>t.leave()}}}(g),b=t(s.atom,e=>c(e,g,u,y)),w=t(s.atom,e=>e.events),S=t(s.atom,e=>{if(!e.events.length)return null;const t=e.lastSeenEventId??"",n=e.events.filter(e=>e.id>t);return n.length?n[n.length-1]:null}),q=e=>{if(m.ackEvent)return void m.ackEvent(e);const t=s.get();t.events.some(t=>t.id===e)&&s.setValue({...t,lastSeenEventId:e})},E=()=>{if(m.ackAllEvents)return void m.ackAllEvents();const e=s.get(),t=e.events[e.events.length-1];t&&s.setValue({...e,lastSeenEventId:t.id})},f=()=>{if(m.destroy)return void m.destroy();const e=s.get().experienceId||a.experienceId;s.setValue(v(e)),u.set(d(v(e)))};return{handle:{view$:b,snapshot$:s.atom,events$:w,latestEvent$:S,ackEvent:q,ackAllEvents:E,destroy:f},snapshot$:s,view$:b,events$:w,latestEvent$:S,handlers:g,setSnapshot:e=>{s.setValue(e)},updateSnapshot:e=>{const t=e(s.get());s.setValue(t)},setDisplayState:e=>u.set(e),ackEvent:q,ackAllEvents:E,destroy:f}}export{b as createMockJourneyHandle,h as createMockJourneyStore,g as makeGatedSnapshot,v as makeReadySnapshot,y as makeRoutedSnapshot};
@@ -1 +1 @@
1
- import{createMockJourneyHandle as e,makeReadySnapshot as t}from"./mock-journey.js";const n={type:"guest",consumerId:"consumer_guest_123",guestId:"guest_123",email:void 0,expiresAt:new Date(Date.now()+36e5).toISOString()},s={type:"authenticated",consumerId:"consumer_auth_123",email:"test@example.com",expiresAt:new Date(Date.now()+36e5).toISOString()},i={id:"queue_test_123",experienceId:"experience_test_123",name:"Test Queue",status:"open",currentSize:0},a={position:1,estimatedWaitTimeInSeconds:60,status:"QUEUED"},o={status:"NOT_QUEUED"},c={id:"auction_test_123",openAt:/* @__PURE__ */(new Date).toISOString(),closeAt:new Date(Date.now()+6e4).toISOString(),settleAt:new Date(Date.now()+6e4).toISOString(),currencyCode:"USD",reservePrice:null,minBidIncrement:"1.00",autoExtendSeconds:null,timeZone:"UTC"},r={auctionId:"auction_test_123",status:"watching",highestBid:"0.00",timeRemaining:6e4},u={id:"draw_test_123",openAt:/* @__PURE__ */(new Date).toISOString(),closeAt:null,timeZone:"UTC",drawAt:new Date(Date.now()+6e4).toISOString(),capacity:null,continueSelectingUntilCompleted:null},d={status:"NOT_ENTERED"},g={id:"waitlist_entry_123",waitlistId:"waitlist_test_123",sequenceId:"sequence_test_123",consumerId:"consumer_test_123",enteredAt:/* @__PURE__ */(new Date).toISOString()},l={waitlistId:"waitlist_test_123",isEntered:!1},y={id:"timed_release_test_123",timeZone:"UTC",supportsGuest:!1},m={status:"NOT_ENTERED"},S={id:"experience_test_123",name:"Test Experience",createdAt:/* @__PURE__ */(new Date).toISOString(),updatedAt:/* @__PURE__ */(new Date).toISOString()},p={experienceId:"experience_test_123",enteredAt:/* @__PURE__ */(new Date).toISOString()};function _(){const i=e({initialSnapshot:t(S.id)}).handle;return{auth:{check:()=>({isAuthenticated:!1}),guest:async()=>n,requestOtp:async()=>{},verifyOtp:async()=>s,exchangeExternal:async()=>s,login:async()=>{},logout:async()=>{},getSession:()=>null,refresh:async()=>{}},challenges:{initiate:async()=>({}),verify:async()=>({status:"failed",attemptsRemaining:0})},journeys:{get:()=>i,list:()=>[],resumeAll:async()=>[]},beacon:{track:async()=>{},trackBatch:async()=>{},flush:async()=>{}},appointments:{get:async()=>({id:"appt_mock",type:"appointment"}),getSlots:async()=>[],getMe:async()=>null,book:async(e,t,n)=>({bookingId:"booking_mock",appointmentId:e,slotId:t,locationId:n,startTime:/* @__PURE__ */(new Date).toISOString(),endTime:/* @__PURE__ */(new Date).toISOString(),confirmationCode:"MOCK",consumerStatus:"booked"}),cancel:async()=>{},reschedule:async(e,t,n)=>({bookingId:"booking_mock",appointmentId:e,slotId:t,locationId:n,startTime:/* @__PURE__ */(new Date).toISOString(),endTime:/* @__PURE__ */(new Date).toISOString(),confirmationCode:"MOCK",consumerStatus:"booked"}),startMonitoring:()=>{},stopMonitoring:()=>{},isMonitoring:()=>!1,bumpMonitorGeneration:()=>{},destroy:()=>{}},on:()=>()=>{},use:()=>{},destroy:async()=>{}}}function I(e={}){return{..._(),...e}}function w(n={}){return{...{..._(),queues:{get:async()=>i,enter:async()=>a,leave:async()=>{},status:async()=>o,startMonitoring:()=>{},stopMonitoring:()=>{},isMonitoring:()=>!1},auctions:{get:async()=>c,bid:async()=>({status:"accepted",amount:"10.00",highestBid:"10.00",bidCount:1}),enter:async()=>{},leave:async()=>{},status:async()=>r,getBidHistory:async()=>[],enableAutoRebid:()=>{},disableAutoRebid:()=>{},getAutoRebidConfig:()=>{},startMonitoring:()=>{},stopMonitoring:()=>{},isMonitoring:()=>!1,destroy:()=>{}},draws:{get:async()=>u,enter:async()=>({status:"ENTERED"}),leave:async()=>{},status:async()=>d,startMonitoring:()=>{},stopMonitoring:()=>{},isMonitoring:()=>!1},waitlists:{enter:async()=>g,leave:async()=>{},getStatus:async()=>l,destroy:()=>{}},timedReleases:{get:async()=>y,enter:async()=>({status:"ENTERED",enteredAt:/* @__PURE__ */(new Date).toISOString(),admissionGrant:"mock_tr_grant",expiresAt:new Date(Date.now()+6e4).toISOString()}),leave:async()=>{},complete:()=>"mock_tr_grant",status:async()=>m,startMonitoring:()=>{},stopMonitoring:()=>{},isMonitoring:()=>!1},experiences:{get:async()=>S,enter:async()=>p,leave:async()=>{},findSequence:async()=>({outcome:"routed",selected:{sequence:{id:"sequence_test_123",name:"Test Sequence",experienceId:"experience_test_123",priority:1,color:"#000000"},status:"admissible",gates:[]},offers:[]}),validateSequenceAccess:async()=>({sequenceId:"sequence_test_123",hasAccess:!0,accessReasons:[],deniedReasons:[]}),selectSequence:async()=>{},getCurrentDistributions:async()=>({}),enterDistribution:async()=>{},getActiveSession:()=>null,isInExperience:()=>!1,getSelectedSequence:()=>null,getMe:async()=>({active:{journeys:[]}}),destroy:()=>{},createJourney:n=>e({initialSnapshot:t(n)}).handle,resumeJourneysFromMe:async()=>[]}},...n}}export{I as createMockFanfareSDK,w as createMockInternalFanfareSDK,c as mockAuction,r as mockAuctionStatus,u as mockDraw,d as mockDrawStatus,S as mockExperience,p as mockExperienceSession,i as mockQueue,a as mockQueueEnterResult,o as mockQueueStatus,y as mockTimedRelease,m as mockTimedReleaseStatus,g as mockWaitlistEntry,l as mockWaitlistStatus};
1
+ import{createMockJourneyHandle as e,makeReadySnapshot as t}from"./mock-journey.js";const n={type:"guest",consumerId:"consumer_guest_123",email:void 0,expiresAt:new Date(Date.now()+36e5).toISOString()},s={type:"authenticated",consumerId:"consumer_auth_123",email:"test@example.com",expiresAt:new Date(Date.now()+36e5).toISOString()},i={id:"queue_test_123",experienceId:"experience_test_123",name:"Test Queue",status:"open",currentSize:0},a={position:1,estimatedWaitTimeInSeconds:60,status:"QUEUED"},o={status:"NOT_QUEUED"},c={id:"auction_test_123",openAt:/* @__PURE__ */(new Date).toISOString(),closeAt:new Date(Date.now()+6e4).toISOString(),settleAt:new Date(Date.now()+6e4).toISOString(),currencyCode:"USD",reservePrice:null,minBidIncrement:"1.00",autoExtendSeconds:null,timeZone:"UTC"},r={auctionId:"auction_test_123",status:"watching",highestBid:"0.00",timeRemaining:6e4},u={id:"draw_test_123",openAt:/* @__PURE__ */(new Date).toISOString(),closeAt:null,timeZone:"UTC",drawAt:new Date(Date.now()+6e4).toISOString(),capacity:null,continueSelectingUntilCompleted:null},d={status:"NOT_ENTERED"},l={id:"waitlist_entry_123",waitlistId:"waitlist_test_123",sequenceId:"sequence_test_123",consumerId:"consumer_test_123",enteredAt:/* @__PURE__ */(new Date).toISOString()},y={waitlistId:"waitlist_test_123",isEntered:!1},g={id:"timed_release_test_123",timeZone:"UTC",supportsGuest:!1},m={status:"NOT_ENTERED"},S={id:"experience_test_123",name:"Test Experience",createdAt:/* @__PURE__ */(new Date).toISOString(),updatedAt:/* @__PURE__ */(new Date).toISOString()},p={experienceId:"experience_test_123",enteredAt:/* @__PURE__ */(new Date).toISOString()};function _(){const i=e({initialSnapshot:t(S.id)}).handle;return{auth:{check:()=>({isAuthenticated:!1}),guest:async()=>n,requestOtp:async()=>{},verifyOtp:async()=>s,exchangeExternal:async()=>s,login:async()=>{},logout:async()=>{},getSession:()=>null,refresh:async()=>{}},challenges:{initiate:async()=>({}),verify:async()=>({status:"failed",attemptsRemaining:0})},journeys:{get:()=>i,list:()=>[],resumeAll:async()=>[]},beacon:{track:async()=>{},trackBatch:async()=>{},flush:async()=>{}},appointments:{get:async()=>({id:"appt_mock",type:"appointment"}),getSlots:async()=>[],getMe:async()=>null,book:async(e,t,n)=>({bookingId:"booking_mock",appointmentId:e,slotId:t,locationId:n,startTime:/* @__PURE__ */(new Date).toISOString(),endTime:/* @__PURE__ */(new Date).toISOString(),confirmationCode:"MOCK",consumerStatus:"booked"}),cancel:async()=>{},reschedule:async(e,t,n)=>({bookingId:"booking_mock",appointmentId:e,slotId:t,locationId:n,startTime:/* @__PURE__ */(new Date).toISOString(),endTime:/* @__PURE__ */(new Date).toISOString(),confirmationCode:"MOCK",consumerStatus:"booked"}),startMonitoring:()=>{},stopMonitoring:()=>{},isMonitoring:()=>!1,bumpMonitorGeneration:()=>{},destroy:()=>{}},on:()=>()=>{},use:()=>{},destroy:async()=>{}}}function I(e={}){return{..._(),...e}}function w(n={}){return{...{..._(),queues:{get:async()=>i,enter:async()=>a,leave:async()=>{},status:async()=>o,startMonitoring:()=>{},stopMonitoring:()=>{},isMonitoring:()=>!1},auctions:{get:async()=>c,bid:async()=>({status:"accepted",amount:"10.00",highestBid:"10.00",bidCount:1}),enter:async()=>{},leave:async()=>{},status:async()=>r,getBidHistory:async()=>[],enableAutoRebid:()=>{},disableAutoRebid:()=>{},getAutoRebidConfig:()=>{},startMonitoring:()=>{},stopMonitoring:()=>{},isMonitoring:()=>!1,destroy:()=>{}},draws:{get:async()=>u,enter:async()=>({status:"ENTERED"}),leave:async()=>{},status:async()=>d,startMonitoring:()=>{},stopMonitoring:()=>{},isMonitoring:()=>!1},waitlists:{enter:async()=>l,leave:async()=>{},getStatus:async()=>y,destroy:()=>{}},timedReleases:{get:async()=>g,enter:async()=>({status:"ENTERED",enteredAt:/* @__PURE__ */(new Date).toISOString(),admissionGrant:"mock_tr_grant",expiresAt:new Date(Date.now()+6e4).toISOString()}),leave:async()=>{},complete:()=>"mock_tr_grant",status:async()=>m,startMonitoring:()=>{},stopMonitoring:()=>{},isMonitoring:()=>!1},experiences:{get:async()=>S,enter:async()=>p,leave:async()=>{},findSequence:async()=>({outcome:"routed",selected:{sequence:{id:"sequence_test_123",name:"Test Sequence",experienceId:"experience_test_123",priority:1,color:"#000000"},status:"admissible",gates:[]},offers:[]}),validateSequenceAccess:async()=>({sequenceId:"sequence_test_123",hasAccess:!0,accessReasons:[],deniedReasons:[]}),selectSequence:async()=>{},getCurrentDistributions:async()=>({}),enterDistribution:async()=>{},getActiveSession:()=>null,isInExperience:()=>!1,getSelectedSequence:()=>null,getMe:async()=>({active:{journeys:[]}}),destroy:()=>{},createJourney:n=>e({initialSnapshot:t(n)}).handle,resumeJourneysFromMe:async()=>[]}},...n}}export{I as createMockFanfareSDK,w as createMockInternalFanfareSDK,c as mockAuction,r as mockAuctionStatus,u as mockDraw,d as mockDrawStatus,S as mockExperience,p as mockExperienceSession,i as mockQueue,a as mockQueueEnterResult,o as mockQueueStatus,g as mockTimedRelease,m as mockTimedReleaseStatus,l as mockWaitlistEntry,y as mockWaitlistStatus};
@@ -46,6 +46,14 @@ export interface FanfareConfig {
46
46
  * @default "include"
47
47
  */
48
48
  credentials?: RequestCredentials;
49
+ /**
50
+ * Automatically restore and resume persisted journeys on init (re-registering
51
+ * their capability grants) when a session is present, so a reload mid-experience
52
+ * can act immediately. Disable to manage resume manually via `journeys.resumeAll()`.
53
+ *
54
+ * @default true
55
+ */
56
+ autoResume?: boolean;
49
57
  /**
50
58
  * Enable debug mode
51
59
  * @default false
package/dist/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * SDK version
3
3
  * This is automatically updated during the build process
4
4
  */
5
- export declare const version = "0.7.2";
5
+ export declare const version = "0.9.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- const o="0.7.2";export{o as version};
1
+ const o="0.9.0";export{o as version};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanfare-io/fanfare-sdk-core",
3
- "version": "0.7.2",
3
+ "version": "0.9.0",
4
4
  "description": "Official Fanfare Browser SDK for queue, draw, auction, and appointment experiences",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -122,7 +122,7 @@
122
122
  "uuid": "^14.0.0",
123
123
  "wretch": "^2.11.0",
124
124
  "zustand": "^5.0.6",
125
- "@fanfare-io/fanfare-sdk-contracts": "0.7.2"
125
+ "@fanfare-io/fanfare-sdk-contracts": "0.9.0"
126
126
  },
127
127
  "peerDependencies": {
128
128
  "valibot": "^1.1.0",
@@ -165,7 +165,7 @@
165
165
  "vite-plugin-dts": "^4.5.4",
166
166
  "vite-tsconfig-paths": "^5.1.4",
167
167
  "vitest": "^3.2.4",
168
- "@fanfare-io/shared-models": "0.7.2"
168
+ "@fanfare-io/shared-models": "0.9.0"
169
169
  },
170
170
  "sideEffects": false,
171
171
  "keywords": [
@@ -182,7 +182,7 @@
182
182
  {
183
183
  "name": "Core + Queues",
184
184
  "path": "dist/index.js",
185
- "limit": "42 KB"
185
+ "limit": "43 KB"
186
186
  }
187
187
  ],
188
188
  "scripts": {