@fanfare-io/fanfare-sdk-core 0.4.0 → 0.6.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.
@@ -40,6 +40,10 @@ export declare class AuthenticationModule implements AuthModule {
40
40
  * Logout current session
41
41
  */
42
42
  logout(): Promise<void>;
43
+ /**
44
+ * Clear all local auth state: store, access-token header, and beacon-token header.
45
+ */
46
+ private clearAuthState;
43
47
  /**
44
48
  * Get current session
45
49
  */
@@ -57,7 +61,11 @@ export declare class AuthenticationModule implements AuthModule {
57
61
  */
58
62
  private handleUnauthorized;
59
63
  /**
60
- * Schedule token refresh 5 minutes before expiry
64
+ * 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.
61
69
  */
62
70
  private scheduleTokenRefresh;
63
71
  /**
@@ -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;this.cancelTokenRefresh(),await this.http.post("/auth/logout",{},{skipUnauthorizedHandler:!0}).catch(()=>{}),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}),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}}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(){try{this.logger.info("Refreshing access token");const{accessToken:e,refreshToken:t,beaconToken:s}=await this.http.post("/auth/refresh",{});this.store.setRefreshToken(t??null),this.store.setBeaconToken(s),this.http.updateConfig({...this.http.getConfig(),headers:{...this.http.getConfig().headers,Authorization:`Bearer ${e}`}}),this.beaconHttp.updateConfig({...this.beaconHttp.getConfig(),headers:{...this.beaconHttp.getConfig().headers,"X-Beacon-Token":s}});const r=this.store.session;r&&r.expiresAt&&this.scheduleTokenRefresh(r.expiresAt),this.store.session&&this.events.emit("auth:refreshed",{session:this.store.session})}catch(e){const t=e instanceof Error?e:new Error(String(e));throw this.logger.error("Failed to refresh token",t),await this.logout(),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){this.cancelTokenRefresh();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{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,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 c}from"../state/capability-token-registry.js";import{getEventBus as u}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 f}from"../timed-releases/timed-release.module.js";import{version as p}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 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":p,"X-Fanfare-Client-Type":"browser"},timeout:3e4,retryConfig:{maxRetries:3,delayTimer:1e3,retryOnNetworkError:!0}},j),D=h({baseUrl:S.beaconUrl,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":p,"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),F=new e(x),O=new m(x),K=new f(x),X=new a(x,C,R,z,O,K),P=new n(D,j.beacon||{}),T=u(),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(),F.destroy(),O.destroy(),K.destroy(),X.destroy(),P.destroy(),U?.close(),T.off(),M===k&&(M=null)};B={auth:H,challenges:_,journeys:Y,queues:{get:e=>C.get(e),enter:(e,t,r)=>C.enter(e,t,c({distributionId:e},r)),leave:(e,t)=>C.leave(e,c({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,c({distributionId:e},r)),leave:(e,t)=>R.leave(e,c({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,c({distributionId:e},r)),enter:(e,t,r)=>z.enter(e,t,c({distributionId:e},r)),leave:(e,t)=>z.leave(e,c({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)=>O.enter(e,c({distributionId:e},t)),leave:(e,t)=>O.leave(e,c({distributionId:e},t)),getStatus:e=>O.getStatus(e),destroy:()=>O.destroy()},timedReleases:{get:e=>K.get(e),enter:(e,t,r)=>K.enter(e,t,c({distributionId:e},r)),leave:(e,t)=>K.leave(e,c({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=>F.get(e),getSlots:(e,t)=>F.getSlots(e,t),getMe:e=>F.getMe(e),book:(e,t,r)=>F.book(e,t,r),cancel:(e,t,r,n)=>F.cancel(e,t,r,n),reschedule:(e,t,r)=>F.reschedule(e,t,r),startMonitoring:(e,t,r)=>F.startMonitoring(e,t,r),stopMonitoring:e=>F.stopMonitoring(e),isMonitoring:e=>F.isMonitoring(e),bumpMonitorGeneration:e=>F.bumpMonitorGeneration(e),destroy:()=>F.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};if(J.session)try{await E.refresh()}catch(ne){A.warn("Failed to refresh persisted session during init",ne),J.clearAll()}return M=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 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};
@@ -41,6 +41,7 @@ export declare const ErrorCodes: {
41
41
  readonly DISTRIBUTION_NOT_OPEN: "DISTRIBUTION_NOT_OPEN";
42
42
  readonly DISTRIBUTION_CLOSED: "DISTRIBUTION_CLOSED";
43
43
  readonly DISTRIBUTION_FULL: "DISTRIBUTION_FULL";
44
+ readonly DISTRIBUTION_ORDER_LIMIT: "DISTRIBUTION_ORDER_LIMIT";
44
45
  readonly FINGERPRINT_REQUIRED: "FINGERPRINT_REQUIRED";
45
46
  readonly FINGERPRINT_DEVICE_MISMATCH: "FINGERPRINT_DEVICE_MISMATCH";
46
47
  readonly FINGERPRINT_INVALID: "FINGERPRINT_INVALID";
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.4.0";
5
+ export declare const version = "0.6.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- const o="0.4.0";export{o as version};
1
+ const o="0.6.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.4.0",
3
+ "version": "0.6.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.4.0"
125
+ "@fanfare-io/fanfare-sdk-contracts": "0.6.0"
126
126
  },
127
127
  "peerDependencies": {
128
128
  "valibot": "^1.1.0",
@@ -160,12 +160,12 @@
160
160
  "terser": "^5.46.1",
161
161
  "typescript": "^5.9.2",
162
162
  "valibot": "^1.1.0",
163
- "vite": "^7.3.1",
163
+ "vite": "^7.3.5",
164
164
  "vite-bundle-visualizer": "^1.0.0",
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.4.0"
168
+ "@fanfare-io/shared-models": "0.6.0"
169
169
  },
170
170
  "sideEffects": false,
171
171
  "keywords": [