@fanfare-io/fanfare-sdk-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +44 -0
- package/dist/adapters/google-analytics.d.ts +12 -0
- package/dist/adapters/google-analytics.js +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/types.d.ts +9 -0
- package/dist/appointments/appointment.module.d.ts +32 -0
- package/dist/appointments/appointment.module.js +1 -0
- package/dist/appointments/index.d.ts +2 -0
- package/dist/appointments/public.d.ts +1 -0
- package/dist/appointments/public.js +1 -0
- package/dist/appointments/types.d.ts +47 -0
- package/dist/auctions/auction.module.d.ts +58 -0
- package/dist/auctions/auction.module.js +1 -0
- package/dist/auctions/index.d.ts +5 -0
- package/dist/auctions/public.d.ts +5 -0
- package/dist/auctions/public.js +1 -0
- package/dist/auctions/types.d.ts +97 -0
- package/dist/auth/auth.module.d.ts +71 -0
- package/dist/auth/auth.module.js +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/types.d.ts +112 -0
- package/dist/beacon/batching.d.ts +52 -0
- package/dist/beacon/batching.js +1 -0
- package/dist/beacon/beacon.module.d.ts +58 -0
- package/dist/beacon/beacon.module.js +1 -0
- package/dist/beacon/enrichment.d.ts +16 -0
- package/dist/beacon/enrichment.js +1 -0
- package/dist/beacon/index.d.ts +12 -0
- package/dist/beacon/public.d.ts +9 -0
- package/dist/beacon/public.js +1 -0
- package/dist/beacon/types.d.ts +103 -0
- package/dist/beacon/validation.d.ts +24 -0
- package/dist/beacon/validation.js +1 -0
- package/dist/challenges/challenge.module.d.ts +9 -0
- package/dist/challenges/challenge.module.js +1 -0
- package/dist/challenges/index.d.ts +3 -0
- package/dist/challenges/public.d.ts +9 -0
- package/dist/challenges/public.js +1 -0
- package/dist/challenges/types.d.ts +33 -0
- package/dist/config/index.d.ts +44 -0
- package/dist/config/index.js +1 -0
- package/dist/core/client.d.ts +15 -0
- package/dist/core/client.js +2 -0
- package/dist/core/errors.d.ts +175 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/http.d.ts +87 -0
- package/dist/core/http.js +1 -0
- package/dist/core/logger.d.ts +46 -0
- package/dist/core/logger.js +1 -0
- package/dist/core/money.d.ts +10 -0
- package/dist/core/money.js +1 -0
- package/dist/core/parse-response.d.ts +62 -0
- package/dist/core/parse-response.js +1 -0
- package/dist/core/utils.d.ts +67 -0
- package/dist/core/utils.js +1 -0
- package/dist/draws/draw.module.d.ts +40 -0
- package/dist/draws/draw.module.js +1 -0
- package/dist/draws/index.d.ts +5 -0
- package/dist/draws/public.d.ts +6 -0
- package/dist/draws/public.js +1 -0
- package/dist/draws/types.d.ts +90 -0
- package/dist/draws/types.js +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +1 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.js +1 -0
- package/dist/experiences/distribution-monitor.runtime.d.ts +12 -0
- package/dist/experiences/distribution-monitor.runtime.js +1 -0
- package/dist/experiences/distribution-monitor.types.d.ts +62 -0
- package/dist/experiences/experience.module.d.ts +88 -0
- package/dist/experiences/experience.module.js +1 -0
- package/dist/experiences/index.d.ts +3 -0
- package/dist/experiences/index.js +1 -0
- package/dist/experiences/journey-contract.fixtures.d.ts +9 -0
- package/dist/experiences/journey-view.d.ts +22 -0
- package/dist/experiences/journey-view.js +1 -0
- package/dist/experiences/journey.d.ts +89 -0
- package/dist/experiences/journey.js +1 -0
- package/dist/experiences/journey.machine.d.ts +79 -0
- package/dist/experiences/journey.machine.js +1 -0
- package/dist/experiences/journey.types.d.ts +395 -0
- package/dist/experiences/public.d.ts +13 -0
- package/dist/experiences/public.js +1 -0
- package/dist/experiences/types.d.ts +161 -0
- package/dist/handoff/handoff.module.d.ts +184 -0
- package/dist/handoff/handoff.module.js +1 -0
- package/dist/handoff/index.d.ts +5 -0
- package/dist/handoff/index.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1 -0
- package/dist/internals.d.ts +11 -0
- package/dist/internals.js +1 -0
- package/dist/queues/index.d.ts +5 -0
- package/dist/queues/index.js +1 -0
- package/dist/queues/qualitative-bucket.d.ts +12 -0
- package/dist/queues/qualitative-bucket.js +1 -0
- package/dist/queues/queue.module.d.ts +42 -0
- package/dist/queues/queue.module.js +1 -0
- package/dist/queues/types.d.ts +112 -0
- package/dist/queues/types.js +1 -0
- package/dist/security/admission-proof.d.ts +15 -0
- package/dist/security/admission-proof.js +1 -0
- package/dist/state/capability-token-registry.d.ts +44 -0
- package/dist/state/capability-token-registry.js +1 -0
- package/dist/state/events.d.ts +342 -0
- package/dist/state/events.js +1 -0
- package/dist/state/store.d.ts +48 -0
- package/dist/state/store.js +1 -0
- package/dist/sync/broadcast-transport.d.ts +19 -0
- package/dist/sync/broadcast-transport.js +1 -0
- package/dist/sync/cross-tab-coordinator.d.ts +44 -0
- package/dist/sync/cross-tab-coordinator.js +1 -0
- package/dist/sync/index.d.ts +8 -0
- package/dist/sync/localstorage-transport.d.ts +32 -0
- package/dist/sync/localstorage-transport.js +1 -0
- package/dist/sync/protocol-transport.d.ts +51 -0
- package/dist/sync/protocol-transport.js +1 -0
- package/dist/sync/protocol.d.ts +254 -0
- package/dist/sync/protocol.js +1 -0
- package/dist/sync/recovery.d.ts +68 -0
- package/dist/sync/transport-factory.d.ts +16 -0
- package/dist/sync/transport-factory.js +1 -0
- package/dist/sync/transport-interface.d.ts +33 -0
- package/dist/sync/transport.d.ts +49 -0
- package/dist/sync/transport.js +1 -0
- package/dist/test-utils/harness-scenarios.d.ts +71 -0
- package/dist/test-utils/harness-scenarios.js +1 -0
- package/dist/test-utils/index.d.ts +12 -0
- package/dist/test-utils/index.js +1 -0
- package/dist/test-utils/mock-journey.d.ts +63 -0
- package/dist/test-utils/mock-journey.js +1 -0
- package/dist/test-utils/mock-sdk-vitest.d.ts +80 -0
- package/dist/test-utils/mock-sdk-vitest.js +1 -0
- package/dist/test-utils/mock-sdk.d.ts +22 -0
- package/dist/test-utils/mock-sdk.js +1 -0
- package/dist/test-utils/mock-server.d.ts +105 -0
- package/dist/test-utils/mock-server.js +1 -0
- package/dist/test-utils/sdk-factory.d.ts +3 -0
- package/dist/test-utils/sdk-factory.js +1 -0
- package/dist/test-utils/verification-scenarios.d.ts +43 -0
- package/dist/test-utils/verification-scenarios.js +1 -0
- package/dist/test-utils/verification-state.d.ts +1 -0
- package/dist/test-utils/verification-state.js +1 -0
- package/dist/theme.d.ts +8 -0
- package/dist/theme.js +1 -0
- package/dist/timed-releases/index.d.ts +6 -0
- package/dist/timed-releases/public.d.ts +6 -0
- package/dist/timed-releases/public.js +1 -0
- package/dist/timed-releases/timed-release.module.d.ts +31 -0
- package/dist/timed-releases/timed-release.module.js +1 -0
- package/dist/timed-releases/types.d.ts +70 -0
- package/dist/timed-releases/types.js +1 -0
- package/dist/types/distribution-type.d.ts +45 -0
- package/dist/types/index.d.ts +178 -0
- package/dist/utils/fingerprint.module.d.ts +27 -0
- package/dist/utils/fingerprint.module.js +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +1 -0
- package/dist/waitlists/index.d.ts +2 -0
- package/dist/waitlists/public.d.ts +6 -0
- package/dist/waitlists/public.js +1 -0
- package/dist/waitlists/types.d.ts +50 -0
- package/dist/waitlists/types.js +1 -0
- package/dist/waitlists/waitlist.module.d.ts +17 -0
- package/dist/waitlists/waitlist.module.js +1 -0
- package/package.json +205 -0
|
@@ -0,0 +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,t)=>K.complete(e,c({distributionId:e},t)),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};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { ConsumerApiError, ErrorCode as ContractErrorCode } from '@fanfare-io/fanfare-sdk-contracts/errors';
|
|
2
|
+
export type { ConsumerApiError, ValidationIssue } from '@fanfare-io/fanfare-sdk-contracts/errors';
|
|
3
|
+
/**
|
|
4
|
+
* Codes for failures that never reach the wire — transport, SDK lifecycle, and
|
|
5
|
+
* client-side protocol validation. These are intentionally disjoint from the
|
|
6
|
+
* contract codes.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SdkClientErrorCodes: {
|
|
9
|
+
readonly NETWORK_ERROR: "NETWORK_ERROR";
|
|
10
|
+
readonly TIMEOUT: "TIMEOUT";
|
|
11
|
+
readonly ABORTED: "ABORTED";
|
|
12
|
+
readonly NOT_INITIALIZED: "NOT_INITIALIZED";
|
|
13
|
+
readonly ALREADY_INITIALIZED: "ALREADY_INITIALIZED";
|
|
14
|
+
readonly INVALID_CONFIG: "INVALID_CONFIG";
|
|
15
|
+
readonly ADMISSION_PROOF_UNAVAILABLE: "ADMISSION_PROOF_UNAVAILABLE";
|
|
16
|
+
readonly PROTOCOL_ERROR: "PROTOCOL_ERROR";
|
|
17
|
+
};
|
|
18
|
+
export type SdkClientErrorCode = (typeof SdkClientErrorCodes)[keyof typeof SdkClientErrorCodes];
|
|
19
|
+
/**
|
|
20
|
+
* The complete set of codes the SDK can surface: every canonical contract code
|
|
21
|
+
* plus the client-only codes above. Consumers read members off this object
|
|
22
|
+
* (e.g. `ErrorCodes.RATE_LIMITED`).
|
|
23
|
+
*/
|
|
24
|
+
export declare const ErrorCodes: {
|
|
25
|
+
readonly NETWORK_ERROR: "NETWORK_ERROR";
|
|
26
|
+
readonly TIMEOUT: "TIMEOUT";
|
|
27
|
+
readonly ABORTED: "ABORTED";
|
|
28
|
+
readonly NOT_INITIALIZED: "NOT_INITIALIZED";
|
|
29
|
+
readonly ALREADY_INITIALIZED: "ALREADY_INITIALIZED";
|
|
30
|
+
readonly INVALID_CONFIG: "INVALID_CONFIG";
|
|
31
|
+
readonly ADMISSION_PROOF_UNAVAILABLE: "ADMISSION_PROOF_UNAVAILABLE";
|
|
32
|
+
readonly PROTOCOL_ERROR: "PROTOCOL_ERROR";
|
|
33
|
+
readonly BOT_CHALLENGE_INVALID: "BOT_CHALLENGE_INVALID";
|
|
34
|
+
readonly BOT_CHALLENGE_EXPIRED: "BOT_CHALLENGE_EXPIRED";
|
|
35
|
+
readonly BOT_CHALLENGE_SUBJECT_MISMATCH: "BOT_CHALLENGE_SUBJECT_MISMATCH";
|
|
36
|
+
readonly ADMISSION_KEY_THUMBPRINT_REQUIRED: "ADMISSION_KEY_THUMBPRINT_REQUIRED";
|
|
37
|
+
readonly ADMISSION_PROOF_REQUIRED: "ADMISSION_PROOF_REQUIRED";
|
|
38
|
+
readonly ADMISSION_GRANT_INVALID: "ADMISSION_GRANT_INVALID";
|
|
39
|
+
readonly ADMISSION_ORIGINAL_ENTRY_REQUIRED: "ADMISSION_ORIGINAL_ENTRY_REQUIRED";
|
|
40
|
+
readonly ADMISSION_KEY_MISMATCH: "ADMISSION_KEY_MISMATCH";
|
|
41
|
+
readonly DISTRIBUTION_NOT_OPEN: "DISTRIBUTION_NOT_OPEN";
|
|
42
|
+
readonly DISTRIBUTION_CLOSED: "DISTRIBUTION_CLOSED";
|
|
43
|
+
readonly DISTRIBUTION_FULL: "DISTRIBUTION_FULL";
|
|
44
|
+
readonly FINGERPRINT_REQUIRED: "FINGERPRINT_REQUIRED";
|
|
45
|
+
readonly FINGERPRINT_DEVICE_MISMATCH: "FINGERPRINT_DEVICE_MISMATCH";
|
|
46
|
+
readonly FINGERPRINT_INVALID: "FINGERPRINT_INVALID";
|
|
47
|
+
readonly SLOT_EXPIRED: "SLOT_EXPIRED";
|
|
48
|
+
readonly SLOT_OUT_OF_WINDOW: "SLOT_OUT_OF_WINDOW";
|
|
49
|
+
readonly SLOT_NOT_AVAILABLE: "SLOT_NOT_AVAILABLE";
|
|
50
|
+
readonly ALREADY_BOOKED: "ALREADY_BOOKED";
|
|
51
|
+
readonly ACTIVE_BOOKING_EXISTS: "ACTIVE_BOOKING_EXISTS";
|
|
52
|
+
readonly NO_EXISTING_BOOKING: "NO_EXISTING_BOOKING";
|
|
53
|
+
readonly INVALID_SLOT: "INVALID_SLOT";
|
|
54
|
+
readonly NEW_SLOT_FULL: "NEW_SLOT_FULL";
|
|
55
|
+
readonly CANNOT_CANCEL_ONGOING_OR_PAST: "CANNOT_CANCEL_ONGOING_OR_PAST";
|
|
56
|
+
readonly ENTRY_TOKEN_REQUIRED: "ENTRY_TOKEN_REQUIRED";
|
|
57
|
+
readonly ENTRY_TOKEN_INVALID: "ENTRY_TOKEN_INVALID";
|
|
58
|
+
readonly ENTRY_TOKEN_EXPIRED: "ENTRY_TOKEN_EXPIRED";
|
|
59
|
+
readonly ENTRY_TOKEN_MISMATCH: "ENTRY_TOKEN_MISMATCH";
|
|
60
|
+
readonly INVALID_CREDENTIALS: "INVALID_CREDENTIALS";
|
|
61
|
+
readonly INVALID_SESSION: "INVALID_SESSION";
|
|
62
|
+
readonly INVALID_REFRESH_TOKEN: "INVALID_REFRESH_TOKEN";
|
|
63
|
+
readonly OTP_INVALID: "OTP_INVALID";
|
|
64
|
+
readonly OTP_EXPIRED: "OTP_EXPIRED";
|
|
65
|
+
readonly VALIDATION_ERROR: "VALIDATION_ERROR";
|
|
66
|
+
readonly UNAUTHORIZED: "UNAUTHORIZED";
|
|
67
|
+
readonly FORBIDDEN: "FORBIDDEN";
|
|
68
|
+
readonly NOT_FOUND: "NOT_FOUND";
|
|
69
|
+
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
70
|
+
readonly RATE_LIMIT_UNAVAILABLE: "RATE_LIMIT_UNAVAILABLE";
|
|
71
|
+
readonly PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE";
|
|
72
|
+
readonly INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
73
|
+
readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Codes the SDK recognizes. Open-ended via `(string & {})` so an unknown
|
|
77
|
+
* forward-compatible code from a newer server still type-checks at call sites.
|
|
78
|
+
*/
|
|
79
|
+
export type KnownErrorCode = ContractErrorCode | SdkClientErrorCode;
|
|
80
|
+
export type ErrorCode = KnownErrorCode | (string & {});
|
|
81
|
+
/**
|
|
82
|
+
* Base error class for the Fanfare SDK.
|
|
83
|
+
*/
|
|
84
|
+
export declare class FanfareError extends Error {
|
|
85
|
+
/** Machine-readable code. Known codes are listed in `ErrorCodes`. */
|
|
86
|
+
readonly code: KnownErrorCode | (string & {});
|
|
87
|
+
/** HTTP status code, when the error originated from a response. */
|
|
88
|
+
readonly status?: number;
|
|
89
|
+
/** Structured error details (server `details`, or the raw body). */
|
|
90
|
+
readonly details?: unknown;
|
|
91
|
+
/** Per-request tracking id echoed by the server. */
|
|
92
|
+
readonly requestId?: string;
|
|
93
|
+
/** Correlation id grouping retries of a single logical operation. */
|
|
94
|
+
readonly correlationId?: string;
|
|
95
|
+
/** Whether the operation may be retried. */
|
|
96
|
+
readonly retryable: boolean;
|
|
97
|
+
/** Server-provided retry-after, in seconds. */
|
|
98
|
+
readonly retryAfter?: number;
|
|
99
|
+
/** Validation issues, when the server returned a structured list (untyped wire data). */
|
|
100
|
+
readonly issues?: readonly unknown[];
|
|
101
|
+
/** Flow hint from the server (e.g. `"reroute"`). */
|
|
102
|
+
readonly action?: string;
|
|
103
|
+
/** Support metadata for surfacing reference/confirmation codes. */
|
|
104
|
+
readonly support?: ConsumerApiError["support"];
|
|
105
|
+
constructor(message: string, code: KnownErrorCode | (string & {}), status?: number, details?: unknown, requestId?: string, correlationId?: string, retryable?: boolean, retryAfter?: number, extra?: {
|
|
106
|
+
issues?: readonly unknown[];
|
|
107
|
+
action?: string;
|
|
108
|
+
support?: ConsumerApiError["support"];
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* Normalize an HTTP error response into a `FanfareError`. Preserves
|
|
112
|
+
* structured `details`, `issues`, `action`, and `support` from the canonical
|
|
113
|
+
* error envelope. Never surfaces a raw 5xx (or otherwise unsafe) `message` as
|
|
114
|
+
* display copy.
|
|
115
|
+
*/
|
|
116
|
+
static fromResponse(response: Response, body?: unknown, correlationId?: string): FanfareError;
|
|
117
|
+
/** Check whether this error has a specific code. */
|
|
118
|
+
is(code: string): boolean;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Classify an HTTP error into a normalized code + retryability.
|
|
122
|
+
*
|
|
123
|
+
* Precedence: a top-level `code` string in the body wins (the server already
|
|
124
|
+
* spoke the canonical contract); otherwise we map by status. `retryable` is
|
|
125
|
+
* always derived from the status (429 + 5xx) so a server code can't accidentally
|
|
126
|
+
* mark a non-idempotent failure as retryable.
|
|
127
|
+
*/
|
|
128
|
+
export declare function classifyHttpError(status: number, body?: unknown): {
|
|
129
|
+
code: ErrorCode;
|
|
130
|
+
retryable: boolean;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Raised when a server response fails client-side schema validation. Carries
|
|
134
|
+
* the endpoint + raw valibot issues for diagnosis. By default the shared
|
|
135
|
+
* response-parse helper does NOT throw this — it surfaces it via the
|
|
136
|
+
* `journey:error` event bus and returns a `Result`. Callers that want
|
|
137
|
+
* exception-flow can opt in via `parseResponse(..., { throwOnFailure: true })`.
|
|
138
|
+
*/
|
|
139
|
+
export declare class ProtocolError extends FanfareError {
|
|
140
|
+
/** Endpoint that produced the malformed response (for triage). */
|
|
141
|
+
readonly endpoint: string;
|
|
142
|
+
/** Raw valibot issues — shape depends on the schema. */
|
|
143
|
+
readonly issues: readonly unknown[];
|
|
144
|
+
constructor(message: string, endpoint: string, issues: readonly unknown[], requestId?: string);
|
|
145
|
+
}
|
|
146
|
+
/** Type guard for `FanfareError`. */
|
|
147
|
+
export declare function isFanfareError(error: unknown): error is FanfareError;
|
|
148
|
+
/** Type guard for `ProtocolError`. */
|
|
149
|
+
export declare function isProtocolError(error: unknown): error is ProtocolError;
|
|
150
|
+
/**
|
|
151
|
+
* Check whether an error is a capability grant error: a 403 carrying an
|
|
152
|
+
* `ENTRY_TOKEN_*` code, read from the top-level `code` (consumer-app emits
|
|
153
|
+
* `{ code, action }` at the top level).
|
|
154
|
+
*/
|
|
155
|
+
export declare function isCapabilityTokenError(error: unknown): error is FanfareError;
|
|
156
|
+
export declare function isCapabilityGrantError(error: unknown): error is FanfareError;
|
|
157
|
+
/**
|
|
158
|
+
* Check whether a capability grant error carries a reroute action, read from the
|
|
159
|
+
* top-level `action` (consumer-app emits it top-level).
|
|
160
|
+
*/
|
|
161
|
+
export declare function isCapabilityTokenRerouteError(error: unknown): error is FanfareError;
|
|
162
|
+
export declare function isCapabilityGrantRerouteError(error: unknown): error is FanfareError;
|
|
163
|
+
/**
|
|
164
|
+
* Helpers for constructing common client-side errors.
|
|
165
|
+
*/
|
|
166
|
+
export declare const createError: {
|
|
167
|
+
networkError: (message: string, details?: unknown) => FanfareError;
|
|
168
|
+
timeout: (message?: string) => FanfareError;
|
|
169
|
+
unauthorized: (message?: string) => FanfareError;
|
|
170
|
+
invalidSession: (message?: string) => FanfareError;
|
|
171
|
+
notInitialized: (message?: string) => FanfareError;
|
|
172
|
+
invalidConfig: (message: string, details?: unknown) => FanfareError;
|
|
173
|
+
validationError: (message: string, details?: unknown) => FanfareError;
|
|
174
|
+
internalError: (message: string, details?: unknown) => FanfareError;
|
|
175
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ErrorCodeMap as I}from"@fanfare-io/fanfare-sdk-contracts/errors";const e={NETWORK_ERROR:"NETWORK_ERROR",TIMEOUT:"TIMEOUT",ABORTED:"ABORTED",NOT_INITIALIZED:"NOT_INITIALIZED",ALREADY_INITIALIZED:"ALREADY_INITIALIZED",INVALID_CONFIG:"INVALID_CONFIG",ADMISSION_PROOF_UNAVAILABLE:"ADMISSION_PROOF_UNAVAILABLE",PROTOCOL_ERROR:"PROTOCOL_ERROR"},t={...I,...e};function r(I,e){if(I&&"object"==typeof I&&e in I){const t=I[e];if("string"==typeof t)return t}}const E=/* @__PURE__ */new Set([I.VALIDATION_ERROR,I.UNAUTHORIZED,I.FORBIDDEN,I.NOT_FOUND,I.RATE_LIMITED,I.RATE_LIMIT_UNAVAILABLE,I.PAYLOAD_TOO_LARGE,I.INVALID_CREDENTIALS,I.INVALID_SESSION,I.INVALID_REFRESH_TOKEN,I.OTP_INVALID,I.OTP_EXPIRED,I.ENTRY_TOKEN_REQUIRED,I.ENTRY_TOKEN_INVALID,I.ENTRY_TOKEN_EXPIRED,I.ENTRY_TOKEN_MISMATCH,I.SLOT_EXPIRED,I.SLOT_OUT_OF_WINDOW,I.ALREADY_BOOKED,I.ACTIVE_BOOKING_EXISTS,I.NO_EXISTING_BOOKING,I.INVALID_SLOT,I.CANNOT_CANCEL_ONGOING_OR_PAST,I.FINGERPRINT_REQUIRED,I.FINGERPRINT_DEVICE_MISMATCH,I.FINGERPRINT_INVALID,I.SLOT_NOT_AVAILABLE,I.NEW_SLOT_FULL,I.DISTRIBUTION_NOT_OPEN,I.DISTRIBUTION_CLOSED,I.DISTRIBUTION_FULL,I.ADMISSION_KEY_THUMBPRINT_REQUIRED,I.ADMISSION_PROOF_REQUIRED,I.ADMISSION_GRANT_INVALID,I.ADMISSION_ORIGINAL_ENTRY_REQUIRED,I.ADMISSION_KEY_MISMATCH,I.BOT_CHALLENGE_INVALID,I.BOT_CHALLENGE_EXPIRED,I.BOT_CHALLENGE_SUBJECT_MISMATCH]);class O extends Error{constructor(I,e,t,r,E,N,R=!1,o,_){super(I),this.name="FanfareError",this.code=e,this.status=t,this.details=r,this.requestId=E,this.correlationId=N,this.retryable=R,this.retryAfter=o,this.issues=_?.issues,this.action=_?.action,this.support=_?.support,Error.captureStackTrace&&Error.captureStackTrace(this,O)}static fromResponse(I,e,t){const R=I.headers.get("X-Request-Id")||void 0,o=I.headers.get("X-Correlation-Id")||t,_=I.headers.get("Retry-After"),T=_?Number(_):void 0,{code:s,retryable:n}=N(I.status,e),A=r(e,"message"),i=(A&&I.status<500&&E.has(s)?A:null)||I.statusText||"An error occurred",a=(e&&"object"==typeof e&&"details"in e?e.details:e)??e,D=e&&"object"==typeof e&&Array.isArray(e.issues)?e.issues:void 0,L=r(e,"action"),c=e&&"object"==typeof e&&"support"in e?e.support:void 0;return new O(i,s,I.status,a,R,o,n,T,{issues:D,action:L,support:c})}is(I){return this.code===I}}function N(I,e){const E=429===I||I>=500,O=r(e,"code");return O?{code:O,retryable:E}:429===I?{code:t.RATE_LIMITED,retryable:E}:401===I?{code:t.UNAUTHORIZED,retryable:E}:403===I?{code:t.FORBIDDEN,retryable:E}:413===I?{code:t.PAYLOAD_TOO_LARGE,retryable:E}:422===I||400===I?{code:t.VALIDATION_ERROR,retryable:E}:404===I?{code:t.NOT_FOUND,retryable:E}:503===I?{code:t.SERVICE_UNAVAILABLE,retryable:E}:I>=500?{code:t.INTERNAL_ERROR,retryable:E}:{code:`HTTP_${I}`,retryable:E}}class R extends O{constructor(I,e,r,E){super(I,t.PROTOCOL_ERROR,void 0,r,E),this.name="ProtocolError",this.endpoint=e,this.issues=r}}function o(I){return I instanceof O}function _(I){return I instanceof R}const T=/* @__PURE__ */new Set([t.ENTRY_TOKEN_REQUIRED,t.ENTRY_TOKEN_INVALID,t.ENTRY_TOKEN_EXPIRED,t.ENTRY_TOKEN_MISMATCH]);function s(I){return I instanceof O&&(403===I.status&&T.has(I.code))}function n(I){return s(I)}function A(I){return!!s(I)&&"reroute"===I.action}function i(I){return A(I)}const a={networkError:(I,e)=>new O(I,t.NETWORK_ERROR,void 0,e),timeout:(I="Request timed out")=>new O(I,t.TIMEOUT),unauthorized:(I="Unauthorized")=>new O(I,t.UNAUTHORIZED,401),invalidSession:(I="Session is no longer valid")=>new O(I,t.INVALID_SESSION,401),notInitialized:(I="SDK not initialized")=>new O(I,t.NOT_INITIALIZED),invalidConfig:(I,e)=>new O(I,t.INVALID_CONFIG,void 0,e),validationError:(I,e)=>new O(I,t.VALIDATION_ERROR,void 0,e),internalError:(I,e)=>new O(I,t.INTERNAL_ERROR,void 0,e)};export{t as ErrorCodes,O as FanfareError,R as ProtocolError,e as SdkClientErrorCodes,N as classifyHttpError,a as createError,n as isCapabilityGrantError,i as isCapabilityGrantRerouteError,s as isCapabilityTokenError,A as isCapabilityTokenRerouteError,o as isFanfareError,_ as isProtocolError};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { FanfareConfig } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Public HTTP client interface for dependency inversion and testability
|
|
4
|
+
*/
|
|
5
|
+
export interface HttpClient {
|
|
6
|
+
get<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
7
|
+
post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
8
|
+
put<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
9
|
+
delete<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
10
|
+
patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
11
|
+
updateConfig(config: Partial<HttpClientConfig>): void;
|
|
12
|
+
getConfig(): HttpClientConfig;
|
|
13
|
+
}
|
|
14
|
+
export interface HttpClientConfig {
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
/**
|
|
18
|
+
* Cookie behavior for fetches made by this client. Defaults to
|
|
19
|
+
* `"include"` so HttpOnly refresh-token cookies flow on cross-origin
|
|
20
|
+
* consumer API requests. Set explicitly to `"omit"` for a pure
|
|
21
|
+
* token-based deployment that should never send cookies.
|
|
22
|
+
*/
|
|
23
|
+
credentials?: RequestCredentials;
|
|
24
|
+
timeout?: number;
|
|
25
|
+
retryConfig?: {
|
|
26
|
+
maxRetries?: number;
|
|
27
|
+
delayTimer?: number;
|
|
28
|
+
maxDelay?: number;
|
|
29
|
+
retryOnNetworkError?: boolean;
|
|
30
|
+
};
|
|
31
|
+
onUnauthorized?: () => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export interface RequestOptions {
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
skipUnauthorizedHandler?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare class DefaultHttpClient implements HttpClient {
|
|
39
|
+
private config;
|
|
40
|
+
private logger;
|
|
41
|
+
private clientSessionId;
|
|
42
|
+
private fingerprint;
|
|
43
|
+
private fingerprintingEnabled;
|
|
44
|
+
constructor(config: HttpClientConfig, sdkConfig?: FanfareConfig);
|
|
45
|
+
/**
|
|
46
|
+
* Initialize browser fingerprint asynchronously
|
|
47
|
+
* Gracefully handles failures - continues without fingerprint if generation fails
|
|
48
|
+
*/
|
|
49
|
+
private initializeFingerprint;
|
|
50
|
+
private getClient;
|
|
51
|
+
/**
|
|
52
|
+
* Internal request method that handles all HTTP verbs
|
|
53
|
+
*/
|
|
54
|
+
private request;
|
|
55
|
+
/**
|
|
56
|
+
* Make a GET request
|
|
57
|
+
*/
|
|
58
|
+
get<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
59
|
+
/**
|
|
60
|
+
* Make a POST request
|
|
61
|
+
*/
|
|
62
|
+
post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
63
|
+
/**
|
|
64
|
+
* Make a PUT request
|
|
65
|
+
*/
|
|
66
|
+
put<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Make a DELETE request
|
|
69
|
+
*/
|
|
70
|
+
delete<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
71
|
+
/**
|
|
72
|
+
* Make a PATCH request
|
|
73
|
+
*/
|
|
74
|
+
patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
75
|
+
/**
|
|
76
|
+
* Update base configuration
|
|
77
|
+
*/
|
|
78
|
+
updateConfig(config: Partial<HttpClientConfig>): void;
|
|
79
|
+
/**
|
|
80
|
+
* Get current configuration
|
|
81
|
+
*/
|
|
82
|
+
getConfig(): HttpClientConfig;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create HTTP client instance
|
|
86
|
+
*/
|
|
87
|
+
export declare function createHttpClient(config: HttpClientConfig, sdkConfig?: FanfareConfig): HttpClient;
|
|
@@ -0,0 +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 n}from"../utils/fingerprint.module.js";import{FanfareError as o,ErrorCodes as s}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 n();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,n={"Content-Type":"application/json","X-Client-Session-Id":this.clientSessionId,...this.config.headers};return this.fingerprint&&(n["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(n).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()),e}).catcherFallback(async e=>{if(e&&"object"==typeof e&&"response"in e&&e.response instanceof Response){const t=await e.response.text().catch(()=>null)||("function"==typeof e.text?await e.text().catch(()=>null):null);let r=null;if(t)try{r=JSON.parse(t)}catch{r={message:t}}else"function"==typeof e.json&&(r=await e.json().catch(()=>null));const i=("correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0)||void 0;throw o.fromResponse(e.response,r,i)}if(e&&"object"==typeof e&&"name"in e&&"AbortError"===e.name)throw new o("Request timed out",s.TIMEOUT,void 0,void 0,"correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0);throw new o(e&&"object"==typeof e&&"message"in e&&"string"==typeof e.message?e.message:"Network error occurred",s.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,n,a){const c=l(),g=l(),p=this.config.timeout||3e4,h=await r(),d=function(e){return/^\/(queues|draws)\/[^/]+\/enter$/.test(e)}(t),u=new URL(t,this.config.baseUrl).toString(),f=new AbortController,m=setTimeout(()=>f.abort(),p);try{const r={"X-Correlation-Id":c,"X-Request-Id":g,...a?.headers};if(d&&!h&&!r["X-Admission-Key-Thumbprint"])throw new o("Admission proof requires persistent browser crypto support",s.ADMISSION_PROOF_UNAVAILABLE,void 0,{path:t});if(d){const n=await i({url:u,method:e.toUpperCase()});if(!n)throw new o("Admission proof requires persistent browser crypto support",s.ADMISSION_PROOF_UNAVAILABLE,void 0,{path:t});Object.assign(r,n)}h&&!r["X-Admission-Key-Thumbprint"]&&(r["X-Admission-Key-Thumbprint"]=h);const l=this.getClient(a).url(t).headers(r).options({signal:a?.signal||f.signal});let p;switch(e){case"get":p=await l.get().json();break;case"post":p=await l.post(n).json();break;case"put":p=await l.put(n).json();break;case"delete":p=await l.delete().json();break;case"patch":p=await l.patch(n).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};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger module for SDK internal use
|
|
3
|
+
*
|
|
4
|
+
* Based on design decisions:
|
|
5
|
+
* - No request/response logging
|
|
6
|
+
* - Minimal, structured logging
|
|
7
|
+
* - Respects debug mode
|
|
8
|
+
* - Masks PII data
|
|
9
|
+
*/
|
|
10
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
11
|
+
export interface LoggerConfig {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
level: LogLevel;
|
|
14
|
+
prefix?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface Logger {
|
|
17
|
+
debug(message: string, data?: unknown): void;
|
|
18
|
+
info(message: string, data?: unknown): void;
|
|
19
|
+
warn(message: string, data?: unknown): void;
|
|
20
|
+
error(message: string, data?: unknown): void;
|
|
21
|
+
setLevel(level: LogLevel): void;
|
|
22
|
+
setEnabled(enabled: boolean): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create or get logger instance
|
|
26
|
+
*/
|
|
27
|
+
export declare function createLogger(config?: LoggerConfig): Logger;
|
|
28
|
+
/**
|
|
29
|
+
* Get current logger instance
|
|
30
|
+
*/
|
|
31
|
+
export declare function getLogger(): Logger;
|
|
32
|
+
/**
|
|
33
|
+
* Allow host applications to inject their own logger implementation
|
|
34
|
+
* that conforms to the SDK's minimal Logger contract.
|
|
35
|
+
*/
|
|
36
|
+
export declare function setLogger(custom: Logger): void;
|
|
37
|
+
/**
|
|
38
|
+
* Adapter to wrap a Pino-like logger into the SDK's Logger interface
|
|
39
|
+
* so host apps can bridge their server-style loggers.
|
|
40
|
+
*/
|
|
41
|
+
export declare function fromPino(pino: {
|
|
42
|
+
debug?: (objOrMsg?: unknown, msg?: string) => void;
|
|
43
|
+
info: (objOrMsg?: unknown, msg?: string) => void;
|
|
44
|
+
warn: (objOrMsg?: unknown, msg?: string) => void;
|
|
45
|
+
error: (objOrMsg?: unknown, msg?: string) => void;
|
|
46
|
+
}): Logger;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createError as e}from"./errors.js";import{maskPII as o}from"./utils.js";class t{constructor(e){this.levels={debug:0,info:1,warn:2,error:3},this.config=e}shouldLog(e){return!!this.config.enabled&&this.levels[e]>=this.levels[this.config.level]}format(e,t,i){const r=[`${this.config.prefix?`[${this.config.prefix}] `:""}${/* @__PURE__ */(new Date).toISOString()} [${e.toUpperCase()}] ${t}`];if(void 0!==i){const e=o(i);r.push(e)}}debug(e,o){this.shouldLog("debug")&&this.format("debug",e,o)}info(e,o){this.shouldLog("info")&&this.format("info",e,o)}warn(e,o){this.shouldLog("warn")&&this.format("warn",e,o)}error(e,o){this.shouldLog("error")&&this.format("error",e,o)}setLevel(e){this.config.level=e}setEnabled(e){this.config.enabled=e}}let i=null;function r(e){return i&&!e||(i=new t(e||{enabled:!1,level:"warn",prefix:"Fanfare"})),i}function s(){if(!i)throw e.notInitialized("Logger not initialized. Call createLogger first.");return i}export{r as createLogger,s as getLogger};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const MONEY_INTEGER_DIGITS = 12;
|
|
2
|
+
export declare const MONEY_FRACTIONAL_DIGITS = 8;
|
|
3
|
+
export declare const MONEY_REGEX: RegExp;
|
|
4
|
+
export declare function isMoneyString(value: unknown): value is string;
|
|
5
|
+
export declare function normalizeMoney(value: unknown, fallback?: string): string;
|
|
6
|
+
export declare function addMoney(operandA: string, operandB: string): string;
|
|
7
|
+
export declare function subtractMoney(operandA: string, operandB: string): string;
|
|
8
|
+
export declare function isMoneyGreaterThan(operandA: string, operandB: string): boolean;
|
|
9
|
+
export declare function isMoneyGreaterThanOrEqualTo(operandA: string, operandB: string): boolean;
|
|
10
|
+
export declare function isMoneyLessThanOrEqualTo(operandA: string, operandB: string): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t from"big.js";const n=/^\d{1,12}(\.\d{1,8})?$/;function r(t){return"string"==typeof t&&n.test(t)}function u(t,n="0"){return t&&r(t)?t:n}function e(t){return t.split(".")[1]?.length??0}function i(t,n){return Math.max(e(t),e(n))}function o(n,r){return t(n).plus(r).toFixed(i(n,r))}function f(n,r){return t(n).minus(r).toFixed(i(n,r))}function c(n,r){return t(n).gt(r)}function s(n,r){return t(n).gte(r)}function g(n,r){return t(n).lte(r)}export{n as MONEY_REGEX,o as addMoney,c as isMoneyGreaterThan,s as isMoneyGreaterThanOrEqualTo,g as isMoneyLessThanOrEqualTo,r as isMoneyString,u as normalizeMoney,f as subtractMoney};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ProtocolError } from './errors';
|
|
2
|
+
/**
|
|
3
|
+
* Shared SDK helper for runtime validation of HTTP responses.
|
|
4
|
+
*
|
|
5
|
+
* Today the SDK does ZERO runtime validation of server responses (F037 in the
|
|
6
|
+
* May Bug Bash audit). Every payload is a TS-typed cast — when the server
|
|
7
|
+
* changes a field, the SDK silently coerces and downstream code crashes (or
|
|
8
|
+
* worse, succeeds with wrong data). This helper centralises validation so
|
|
9
|
+
* that per-endpoint sub-tasks (ENG-641 routing, ENG-646 me/auction) emit
|
|
10
|
+
* consistent observability.
|
|
11
|
+
*
|
|
12
|
+
* # Default behaviour: non-throwing
|
|
13
|
+
*
|
|
14
|
+
* Throwing on parse failure would break every existing call site that
|
|
15
|
+
* doesn't catch — most are `await client.get(...)` followed by `.fieldName`
|
|
16
|
+
* access. The helper instead returns a discriminated `ParseResult`:
|
|
17
|
+
*
|
|
18
|
+
* - `{ ok: true, value }` on success
|
|
19
|
+
* - `{ ok: false, error: ProtocolError }` on failure
|
|
20
|
+
*
|
|
21
|
+
* On failure it ALSO emits `journey:error` with `kind: "protocolError"` and
|
|
22
|
+
* logs at error level. Callers can choose to:
|
|
23
|
+
* - propagate the error: `if (!result.ok) throw result.error`
|
|
24
|
+
* - degrade gracefully: `if (!result.ok) return fallbackShape`
|
|
25
|
+
* - retry / resync: `if (!result.ok) await this.refetchMe()`
|
|
26
|
+
*
|
|
27
|
+
* Callers that prefer exception-flow can opt in via
|
|
28
|
+
* `parseResponse(..., { throwOnFailure: true })` — useful for code paths
|
|
29
|
+
* that already have a `try/catch` and want a single happy-path branch.
|
|
30
|
+
*
|
|
31
|
+
* # Strict vs lenient
|
|
32
|
+
*
|
|
33
|
+
* Uses `v.safeParse` against whatever schema the caller passes. If the
|
|
34
|
+
* caller wants leniency (extra-fields-allowed), they pass a `v.looseObject`
|
|
35
|
+
* schema. Strict-vs-lenient is a per-endpoint policy decision, not a global
|
|
36
|
+
* helper concern.
|
|
37
|
+
*/
|
|
38
|
+
import * as v from "valibot";
|
|
39
|
+
export interface ParseResponseContext {
|
|
40
|
+
/** Endpoint identifier — used in error messages and `journey:error` events for triage. */
|
|
41
|
+
endpoint: string;
|
|
42
|
+
/** Optional request ID for correlation with server-side logs. */
|
|
43
|
+
requestId?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface ParseResponseOptions {
|
|
46
|
+
/** When true, throw `ProtocolError` instead of returning a failure result. Default false. */
|
|
47
|
+
throwOnFailure?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export type ParseResult<T> = {
|
|
50
|
+
ok: true;
|
|
51
|
+
value: T;
|
|
52
|
+
} | {
|
|
53
|
+
ok: false;
|
|
54
|
+
error: ProtocolError;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Validate `data` against `schema`. On success returns the typed value. On
|
|
58
|
+
* failure emits a `journey:error` event of `kind: "protocolError"` and
|
|
59
|
+
* either returns a failure result (default) or throws the `ProtocolError`
|
|
60
|
+
* (when `options.throwOnFailure` is true).
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseResponse<TInput, TOutput>(schema: v.GenericSchema<TInput, TOutput>, data: unknown, context: ParseResponseContext, options?: ParseResponseOptions): ParseResult<TOutput>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as e from"valibot";import{getEventBus as r}from"../state/events.js";import{ProtocolError as s}from"./errors.js";import{getLogger as o}from"./logger.js";function t(t,n,i,u={}){const a=e.safeParse(t,n);if(a.success)return{ok:!0,value:a.output};const d=function(e){const r=e[0];if(!r)return"no issues reported";const s=r.path?.map(e=>String(e.key)).join(".")??"",o=s?` at "${s}"`:"",t=e.length>1?` (and ${e.length-1} more)`:"";return`${r.message}${o}${t}`}(a.issues),p=`Response from ${i.endpoint} failed schema validation: ${d}`,m=new s(p,i.endpoint,a.issues,i.requestId);if(o().error("Protocol error: response failed schema validation",{endpoint:i.endpoint,requestId:i.requestId,issues:a.issues}),r().emit("journey:error",{kind:"protocolError",endpoint:i.endpoint,message:p,requestId:i.requestId,issues:a.issues}),u.throwOnFailure)throw m;return{ok:!1,error:m}}export{t as parseResponse};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for SDK
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate a unique ID using UUID v7 (time-based)
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateId(): string;
|
|
8
|
+
/**
|
|
9
|
+
* Create a deferred promise
|
|
10
|
+
*/
|
|
11
|
+
export declare function createDeferred<T>(): {
|
|
12
|
+
promise: Promise<T>;
|
|
13
|
+
resolve: (value: T) => void;
|
|
14
|
+
reject: (reason?: unknown) => void;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Sleep for specified milliseconds
|
|
18
|
+
*/
|
|
19
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Retry a function with exponential backoff
|
|
22
|
+
*/
|
|
23
|
+
export declare function retry<T>(fn: () => Promise<T>, options?: {
|
|
24
|
+
maxAttempts?: number;
|
|
25
|
+
initialDelay?: number;
|
|
26
|
+
maxDelay?: number;
|
|
27
|
+
factor?: number;
|
|
28
|
+
onRetry?: (attempt: number, error: Error) => void;
|
|
29
|
+
}): Promise<T>;
|
|
30
|
+
/**
|
|
31
|
+
* Deep clone an object (simple implementation for SDK data)
|
|
32
|
+
*/
|
|
33
|
+
export declare function deepClone<T>(obj: T, visited?: WeakSet<object>): T;
|
|
34
|
+
/**
|
|
35
|
+
* Check if running in browser environment
|
|
36
|
+
*/
|
|
37
|
+
export declare function isBrowser(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Check if storage is available
|
|
40
|
+
*/
|
|
41
|
+
export declare function isStorageAvailable(type: "localStorage" | "sessionStorage"): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Parse JSON safely
|
|
44
|
+
*/
|
|
45
|
+
export declare function safeJsonParse<T>(json: string, fallback?: T): T | null;
|
|
46
|
+
/**
|
|
47
|
+
* Mask email for privacy
|
|
48
|
+
*/
|
|
49
|
+
export declare function maskEmail(email: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Mask sensitive PII data in objects
|
|
52
|
+
*/
|
|
53
|
+
export declare function maskPII(data: unknown): unknown;
|
|
54
|
+
/**
|
|
55
|
+
* Format remaining time in human-readable format
|
|
56
|
+
*/
|
|
57
|
+
export declare function formatRemainingTime(seconds: number): string;
|
|
58
|
+
/**
|
|
59
|
+
* Debounce function calls
|
|
60
|
+
*/
|
|
61
|
+
export declare function debounce<T extends (...args: unknown[]) => unknown>(fn: T, delay: number): T & {
|
|
62
|
+
cancel: () => void;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Throttle function calls
|
|
66
|
+
*/
|
|
67
|
+
export declare function throttle<T extends (...args: unknown[]) => unknown>(fn: T, limit: number): T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{v7 as t}from"uuid";function n(){return t()}function e(t,n=/* @__PURE__ */new WeakSet){if(null===t||"object"!=typeof t)return t;if(n.has(t))return t;if(t instanceof Date)return new Date(t.getTime());if(t instanceof Array)return n.add(t),t.map(t=>e(t,n));if(t instanceof Object){n.add(t);const r={};for(const i in t)Object.prototype.hasOwnProperty.call(t,i)&&(r[i]=e(t[i],n));return r}return t}function r(){return"undefined"!=typeof window&&void 0!==window.document}function i(t){if(!r())return!1;try{const n=window[t],e="__storage_test__";return n.setItem(e,e),n.removeItem(e),!0}catch{return!1}}function o(t){const[n,e]=t.split("@");if(!n||!e)return t;const r=Math.min(2,n.length);return`${n.substring(0,r)+"***"}@${e}`}function u(t){if(null==t)return t;if("string"==typeof t)return t.includes("@")&&t.includes(".")?o(t):t;if(Array.isArray(t))return t.map(t=>u(t));if("object"==typeof t){const n={};for(const[e,r]of Object.entries(t))n[e]="email"===e&&"string"==typeof r?o(r):"consumerId"===e&&"string"==typeof r||"userId"===e&&"string"==typeof r?r.substring(0,8)+"***":u(r);return n}return t}export{e as deepClone,n as generateId,r as isBrowser,i as isStorageAvailable,o as maskEmail,u as maskPII};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { HttpClient, RequestOptions } from '../core/http';
|
|
2
|
+
import { MonitorUpdate } from '../experiences/distribution-monitor.types';
|
|
3
|
+
import { Draw, DrawConsumerState, DrawModule } from './types';
|
|
4
|
+
export declare class DrawManagementModule implements DrawModule {
|
|
5
|
+
private readonly http;
|
|
6
|
+
private readonly logger;
|
|
7
|
+
private readonly events;
|
|
8
|
+
private readonly trackedParticipations;
|
|
9
|
+
private readonly inFlightRequests;
|
|
10
|
+
private readonly scheduledChecks;
|
|
11
|
+
private readonly monitorRuntime;
|
|
12
|
+
private readonly monitorGenerations;
|
|
13
|
+
private readonly operationGenerations;
|
|
14
|
+
private readonly initialCheckBufferMs;
|
|
15
|
+
private readonly retryCheckDelayMs;
|
|
16
|
+
private destroyed;
|
|
17
|
+
private lifecycleGeneration;
|
|
18
|
+
private get store();
|
|
19
|
+
constructor(http: HttpClient);
|
|
20
|
+
get(drawId: string): Promise<Draw>;
|
|
21
|
+
enter(drawId: string, metadata?: Record<string, unknown>, options?: RequestOptions): Promise<DrawConsumerState>;
|
|
22
|
+
leave(drawId: string, options?: RequestOptions): Promise<void>;
|
|
23
|
+
status(drawId: string): Promise<DrawConsumerState>;
|
|
24
|
+
private doStatus;
|
|
25
|
+
startMonitoring(id: string, context?: Record<string, unknown>, onUpdate?: (update: MonitorUpdate) => void): void;
|
|
26
|
+
stopMonitoring(id: string): void;
|
|
27
|
+
isMonitoring(id: string): boolean;
|
|
28
|
+
private buildTransition;
|
|
29
|
+
private applyTransition;
|
|
30
|
+
private scheduleInitialStatusCheck;
|
|
31
|
+
private scheduleRetryStatusCheck;
|
|
32
|
+
private scheduleStatusCheck;
|
|
33
|
+
private cancelScheduledCheck;
|
|
34
|
+
private buildLostResult;
|
|
35
|
+
private bumpMonitorGeneration;
|
|
36
|
+
private getOperationGeneration;
|
|
37
|
+
private bumpOperationGeneration;
|
|
38
|
+
private canApplyAsyncResult;
|
|
39
|
+
destroy(): void;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createError as t,isFanfareError as e}from"../core/errors.js";import{getLogger as i}from"../core/logger.js";import{DistributionMonitorRuntime as r}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as s}from"../state/events.js";import{getSDKStore as a}from"../state/store.js";class n{constructor(t){this.logger=i(),this.events=s(),this.trackedParticipations=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.scheduledChecks=/* @__PURE__ */new Map,this.monitorRuntime=new r,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.initialCheckBufferMs=5e3,this.retryCheckDelayMs=5e3,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return a()}async get(t){try{return await this.http.get(`/draws/${t}`)}catch(e){throw this.logger.error("Failed to get draw",{drawId:t,error:e}),e}}async enter(e,i,r){try{const s=this.lifecycleGeneration;if(!this.store.session)throw t.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const a=this.bumpOperationGeneration(e);this.logger.info("Entering draw",{drawId:e});const n=await this.http.post(`/draws/${e}/enter`,{metadata:i},r),o=this.buildTransition(e,n,{source:"enter",enteredAt:/* @__PURE__ */(new Date).toISOString(),metadata:i});return this.canApplyAsyncResult(e,s,void 0,a)?(this.applyTransition(e,o),n):n}catch(s){throw this.logger.error("Failed to enter draw",{drawId:e,error:s}),this.events.emit("draw:error",{drawId:e,error:s}),s}}async leave(t,e){try{const i=this.lifecycleGeneration,r=this.bumpOperationGeneration(t);if(this.logger.info("Leaving draw",{drawId:t}),await this.http.post(`/draws/${t}/leave`,void 0,e),!this.canApplyAsyncResult(t,i,void 0,r))return;this.trackedParticipations.delete(t),this.stopMonitoring(t),this.events.emit("draw:left",{drawId:t})}catch(i){throw this.logger.error("Failed to leave draw",{drawId:t,error:i}),this.events.emit("draw:error",{drawId:t,error:i}),i}}async status(t){const e=this.inFlightRequests.get(t);if(e)return this.logger.debug("Returning existing in-flight request",{drawId:t}),e;const i=this.lifecycleGeneration,r=this.bumpOperationGeneration(t),s=this.doStatus(t,i,void 0,r).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,s),s}async doStatus(t,e,i,r){try{const s=await this.http.get(`/draws/${t}/status`);if(!this.canApplyAsyncResult(t,e,i,r))return s;const a=this.buildTransition(t,s,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,a),this.events.emit("draw:status-updated",{drawId:t,status:s.status}),s}catch(s){if(s&&"object"==typeof s&&"status"in s&&404===s.status){const s={status:"NOT_ENTERED"};if(!this.canApplyAsyncResult(t,e,i,r))return s;const a=this.buildTransition(t,s,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,a),this.events.emit("draw:status-updated",{drawId:t,status:s.status}),s}if(!this.canApplyAsyncResult(t,e,i,r))return this.trackedParticipations.has(t)?{status:"ENTERED"}:{status:"NOT_ENTERED"};throw this.logger.error("Failed to get draw status",{drawId:t,error:s}),this.events.emit("draw:error",{drawId:t,error:s}),s}}startMonitoring(t,e,i){this.monitorRuntime.start(t,e,i),this.bumpMonitorGeneration(t);const r="string"==typeof e?.drawTime?e.drawTime:void 0,s=this.monitorRuntime.getDisplayAtom(t);s&&s.set({type:"draw",drawTime:r,entryNumber:this.trackedParticipations.get(t)?.entryNumber}),r&&this.scheduleInitialStatusCheck(t,r)}stopMonitoring(t){this.bumpMonitorGeneration(t),this.cancelScheduledCheck(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)||this.scheduledChecks.has(t)}buildTransition(t,e,{source:i,previousParticipation:r,enteredAt:s,metadata:a}){switch(e.status){case"ENTERED":return{trackedParticipation:{drawId:t,enteredAt:r?.enteredAt??s??/* @__PURE__ */(new Date).toISOString(),entryNumber:r?.entryNumber,metadata:r?.metadata??a},events:"enter"===i?[{type:"draw:entered",payload:{drawId:t}}]:[],monitorUpdate:null,stopMonitoring:!1,retryStatusCheck:"status"===i};case"WON":return{trackedParticipation:null,events:[{type:"draw:won",payload:{drawId:t,result:{won:!0,drawTime:e.wonAt}}}],monitorUpdate:"status"===i?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"COMPLETED":return{trackedParticipation:null,events:[{type:"draw:won",payload:{drawId:t,result:{won:!0,drawTime:e.completedAt}}}],monitorUpdate:"status"===i?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"DENIED":return{trackedParticipation:null,events:[{type:"draw:expired",payload:{drawId:t}}],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"denied",reason:e.reason}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"NOT_ENTERED":return{trackedParticipation:null,events:r?[{type:"draw:lost",payload:{drawId:t,result:this.buildLostResult()}}]:[],monitorUpdate:"status"===i?{type:"ended",outcome:r?{type:"lost"}:{type:"closed",reason:"not_entered"}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1}}}applyTransition(t,e){e.trackedParticipation?this.trackedParticipations.set(t,e.trackedParticipation):this.trackedParticipations.delete(t);for(const i of e.events)this.events.emit(i.type,i.payload);e.monitorUpdate&&this.monitorRuntime.notify(t,e.monitorUpdate),e.stopMonitoring?this.stopMonitoring(t):e.retryStatusCheck&&this.monitorRuntime.has(t)&&this.scheduleRetryStatusCheck(t)}scheduleInitialStatusCheck(t,e){const i=new Date(e).getTime(),r=Math.max(0,i-Date.now()+this.initialCheckBufferMs);this.scheduleStatusCheck(t,r)}scheduleRetryStatusCheck(t){this.scheduleStatusCheck(t,this.retryCheckDelayMs)}scheduleStatusCheck(t,i){if(i>2147483647)return void this.logger.warn("Draw time too far in future to schedule",{drawId:t,delayMs:i});this.cancelScheduledCheck(t);const r=this.monitorGenerations.get(t),s=this.getOperationGeneration(t),a=setTimeout(async()=>{this.scheduledChecks.delete(t);try{if(void 0===r)return;await this.doStatus(t,this.lifecycleGeneration,r,s)}catch(i){if(!this.canApplyAsyncResult(t,this.lifecycleGeneration,r,s))return;if(e(i)&&i.retryable&&this.monitorRuntime.has(t))return void this.scheduleRetryStatusCheck(t);this.stopMonitoring(t)}},i);this.scheduledChecks.set(t,a)}cancelScheduledCheck(t){const e=this.scheduledChecks.get(t);e&&(clearTimeout(e),this.scheduledChecks.delete(t))}buildLostResult(){return{won:!1,drawTime:/* @__PURE__ */(new Date).toISOString()}}bumpMonitorGeneration(t){const e=(this.monitorGenerations.get(t)??0)+1;return this.monitorGenerations.set(t,e),e}getOperationGeneration(t){return this.operationGenerations.get(t)??0}bumpOperationGeneration(t){const e=this.getOperationGeneration(t)+1;return this.operationGenerations.set(t,e),e}canApplyAsyncResult(t,e,i,r){return!this.destroyed&&this.lifecycleGeneration===e&&((void 0===i||this.monitorGenerations.get(t)===i)&&(void 0===r||this.getOperationGeneration(t)===r))}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.scheduledChecks.forEach(t=>clearTimeout(t)),this.scheduledChecks.clear(),this.inFlightRequests.clear(),this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.monitorRuntime.clear()}}export{n as DrawManagementModule};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draw module exports
|
|
3
|
+
*/
|
|
4
|
+
export { DrawManagementModule } from './draw.module';
|
|
5
|
+
export type { Draw, DrawCompletedState, DrawConsumerState, DrawDeniedState, DrawEnteredState, DrawModule, DrawNotEnteredState, DrawParticipation, DrawResult, DrawWonState, } from './types';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public draw surface. The runtime `DrawManagementModule`/`DrawModule`/`DrawParticipation`
|
|
3
|
+
* symbols from `./types` are internal and not exported here.
|
|
4
|
+
*/
|
|
5
|
+
export { DrawDenyReason } from './types';
|
|
6
|
+
export type { Draw, DrawCompletedState, DrawConsumerState, DrawDeniedState, DrawEnteredState, DrawNotEnteredState, DrawResult, DrawWonState, } from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DrawDenyReason as o}from"./types.js";export{o as DrawDenyReason};
|