@fanfare-io/fanfare-sdk-core 0.2.0 → 0.3.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/dist/appointments/appointment.driver.d.ts +44 -0
- package/dist/appointments/appointment.driver.js +1 -0
- package/dist/appointments/appointment.sequence.d.ts +194 -0
- package/dist/auctions/auction.driver.d.ts +46 -0
- package/dist/auctions/auction.driver.js +1 -0
- package/dist/auctions/auction.module.js +1 -1
- package/dist/auctions/auction.sequence.d.ts +263 -0
- package/dist/core/client.js +1 -1
- package/dist/draws/draw.driver.d.ts +45 -0
- package/dist/draws/draw.driver.js +1 -0
- package/dist/draws/draw.module.js +1 -1
- package/dist/draws/draw.sequence.d.ts +249 -0
- package/dist/experiences/distribution-monitor.types.d.ts +92 -2
- package/dist/experiences/experience.module.js +1 -1
- package/dist/experiences/journey-view.d.ts +8 -10
- package/dist/experiences/journey-view.js +1 -1
- package/dist/experiences/journey.d.ts +45 -20
- package/dist/experiences/journey.js +1 -1
- package/dist/experiences/journey.machine.d.ts +3 -20
- package/dist/experiences/journey.machine.js +1 -1
- package/dist/experiences/journey.types.d.ts +422 -217
- package/dist/experiences/public.d.ts +1 -1
- package/dist/experiences/sequence-driver.d.ts +93 -0
- package/dist/experiences/types.d.ts +185 -23
- package/dist/internals.d.ts +1 -1
- package/dist/internals.js +1 -1
- package/dist/queues/queue.driver.d.ts +45 -0
- package/dist/queues/queue.driver.js +1 -0
- package/dist/queues/queue.module.js +1 -1
- package/dist/queues/queue.sequence.d.ts +222 -0
- package/dist/state/capability-token-registry.d.ts +1 -0
- package/dist/state/capability-token-registry.js +1 -1
- package/dist/state/store.d.ts +5 -1
- package/dist/state/store.js +1 -1
- package/dist/storefront.d.ts +44 -18
- package/dist/storefront.js +1 -1
- package/dist/test-utils/harness-scenarios.d.ts +8 -1
- package/dist/test-utils/harness-scenarios.js +1 -1
- package/dist/test-utils/mock-journey.d.ts +15 -4
- package/dist/test-utils/mock-journey.js +1 -1
- package/dist/test-utils/mock-sdk.js +1 -1
- package/dist/test-utils/verification-scenarios.d.ts +9 -1
- package/dist/test-utils/verification-scenarios.js +1 -1
- package/dist/timed-releases/timed-release.driver.d.ts +45 -0
- package/dist/timed-releases/timed-release.driver.js +1 -0
- package/dist/timed-releases/timed-release.module.d.ts +6 -1
- package/dist/timed-releases/timed-release.module.js +1 -1
- package/dist/timed-releases/timed-release.sequence.d.ts +227 -0
- package/dist/timed-releases/types.d.ts +16 -1
- package/dist/types/distribution-type.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { AdmissionGrant, AuctionConsumerStatus, SequenceOutcome } from '@fanfare-io/fanfare-sdk-contracts/consumer-me';
|
|
2
|
+
import { ReadableAtom } from 'nanostores';
|
|
3
|
+
import { AuctionDisplayState } from '../experiences/distribution-monitor.types';
|
|
4
|
+
import { DistributionSummary, WaitlistAttachment, WaitlistAttachmentView } from '../experiences/journey.types';
|
|
5
|
+
/**
|
|
6
|
+
* Auction participation facts for one consumer.
|
|
7
|
+
*
|
|
8
|
+
* The server is authoritative for every field. Timestamps are ISO 8601 strings,
|
|
9
|
+
* and bid amounts are decimal money strings. Optional fields are present only
|
|
10
|
+
* after the matching auction lifecycle event has occurred.
|
|
11
|
+
*/
|
|
12
|
+
export interface AuctionSequenceConsumerState {
|
|
13
|
+
/** Server auction status for this consumer. */
|
|
14
|
+
status: AuctionConsumerStatus;
|
|
15
|
+
/** Auction participation id, when the server has created one. */
|
|
16
|
+
id?: string;
|
|
17
|
+
/** Distribution id this auction participation belongs to. */
|
|
18
|
+
distributionId?: string;
|
|
19
|
+
/** ISO 8601 timestamp for when the consumer first bid. */
|
|
20
|
+
enteredAt?: string;
|
|
21
|
+
/** Opaque admission credential token, when one has been issued inline. */
|
|
22
|
+
admissionGrant?: string;
|
|
23
|
+
/** ISO 8601 timestamp for when the consumer won and was granted admission. */
|
|
24
|
+
grantedAt?: string;
|
|
25
|
+
/** Server-provided denial reason for denied auction outcomes. */
|
|
26
|
+
deniedReason?: string;
|
|
27
|
+
/** Highest known auction bid as a decimal money string. */
|
|
28
|
+
highestBid?: string;
|
|
29
|
+
/** Consumer's current bid as a decimal money string. */
|
|
30
|
+
currentBid?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* An auction sequence as seen by one consumer.
|
|
34
|
+
*
|
|
35
|
+
* Lifecycle: scheduled -> enterable -> participating -> settling -> granted or
|
|
36
|
+
* ended. The first bid is the entry: `bid()` from `enterable` creates the
|
|
37
|
+
* participation. Settlement is server-driven; the client observes `settling`
|
|
38
|
+
* and waits.
|
|
39
|
+
*/
|
|
40
|
+
export type AuctionSequence = AuctionScheduled | AuctionEnterable | AuctionParticipating | AuctionSettling | AuctionGranted | AuctionEnded;
|
|
41
|
+
/**
|
|
42
|
+
* The auction exists but bidding has not opened yet.
|
|
43
|
+
*/
|
|
44
|
+
export interface AuctionScheduled {
|
|
45
|
+
/** Mechanism discriminator for auction narrowing. */
|
|
46
|
+
mechanism: "auction";
|
|
47
|
+
/** Phase discriminator; scheduled auctions cannot be bid on yet. */
|
|
48
|
+
phase: "scheduled";
|
|
49
|
+
/** Auction distribution facts from the journey projector. */
|
|
50
|
+
distribution: DistributionSummary & {
|
|
51
|
+
type: "auction";
|
|
52
|
+
};
|
|
53
|
+
/** Scheduled-only waitlist attachment, cleared when the auction opens. */
|
|
54
|
+
waitlist?: WaitlistAttachment;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Bidding is open and this consumer has not bid yet.
|
|
58
|
+
*/
|
|
59
|
+
export interface AuctionEnterable {
|
|
60
|
+
/** Mechanism discriminator for auction narrowing. */
|
|
61
|
+
mechanism: "auction";
|
|
62
|
+
/** Phase discriminator; `bid()` is legal on the matching view. */
|
|
63
|
+
phase: "enterable";
|
|
64
|
+
/** Open auction distribution facts from the journey projector. */
|
|
65
|
+
distribution: DistributionSummary & {
|
|
66
|
+
type: "auction";
|
|
67
|
+
};
|
|
68
|
+
/** Consumer auction facts before the first bid. */
|
|
69
|
+
consumer: AuctionSequenceConsumerState;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The consumer has bid and is participating in the auction.
|
|
73
|
+
*/
|
|
74
|
+
export interface AuctionParticipating {
|
|
75
|
+
/** Mechanism discriminator for auction narrowing. */
|
|
76
|
+
mechanism: "auction";
|
|
77
|
+
/** Phase discriminator; auction display and bidding are live. */
|
|
78
|
+
phase: "participating";
|
|
79
|
+
/** Auction distribution facts from the journey projector. */
|
|
80
|
+
distribution: DistributionSummary & {
|
|
81
|
+
type: "auction";
|
|
82
|
+
};
|
|
83
|
+
/** Consumer auction participation facts. */
|
|
84
|
+
consumer: AuctionSequenceConsumerState;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The auction has closed and the server is determining the outcome.
|
|
88
|
+
*/
|
|
89
|
+
export interface AuctionSettling {
|
|
90
|
+
/** Mechanism discriminator for auction narrowing. */
|
|
91
|
+
mechanism: "auction";
|
|
92
|
+
/** Phase discriminator; no consumer action is legal while settling. */
|
|
93
|
+
phase: "settling";
|
|
94
|
+
/** Settling auction distribution facts from the journey projector. */
|
|
95
|
+
distribution: DistributionSummary & {
|
|
96
|
+
type: "auction";
|
|
97
|
+
};
|
|
98
|
+
/** Consumer auction participation facts during settlement. */
|
|
99
|
+
consumer: AuctionSequenceConsumerState;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The auction has issued an admission grant to this consumer.
|
|
103
|
+
*/
|
|
104
|
+
export interface AuctionGranted {
|
|
105
|
+
/** Mechanism discriminator for auction narrowing. */
|
|
106
|
+
mechanism: "auction";
|
|
107
|
+
/** Phase discriminator; `claim()` is legal on the matching view. */
|
|
108
|
+
phase: "granted";
|
|
109
|
+
/** Auction distribution facts from the journey projector. */
|
|
110
|
+
distribution: DistributionSummary & {
|
|
111
|
+
type: "auction";
|
|
112
|
+
};
|
|
113
|
+
/** Consumer auction facts associated with the grant. */
|
|
114
|
+
consumer: AuctionSequenceConsumerState;
|
|
115
|
+
/** Admission credential issued by the server for this auction win. */
|
|
116
|
+
grant: AdmissionGrant;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The auction journey is terminal for this consumer.
|
|
120
|
+
*/
|
|
121
|
+
export interface AuctionEnded {
|
|
122
|
+
/** Mechanism discriminator for auction narrowing. */
|
|
123
|
+
mechanism: "auction";
|
|
124
|
+
/** Phase discriminator; no auction actions are legal after this phase. */
|
|
125
|
+
phase: "ended";
|
|
126
|
+
/** Auction distribution facts from the journey projector. */
|
|
127
|
+
distribution: DistributionSummary & {
|
|
128
|
+
type: "auction";
|
|
129
|
+
};
|
|
130
|
+
/** Consumer auction facts at the terminal outcome. */
|
|
131
|
+
consumer: AuctionSequenceConsumerState;
|
|
132
|
+
/** Server-derived terminal outcome for the auction participation. */
|
|
133
|
+
outcome: SequenceOutcome;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Auction views exposed by `JourneyView.sequence`.
|
|
137
|
+
*
|
|
138
|
+
* The view lifecycle mirrors the auction sequence lifecycle. Actions exist only
|
|
139
|
+
* on the phases where they are legal, and each action may reject if the server
|
|
140
|
+
* has moved the sequence before the request is handled.
|
|
141
|
+
*/
|
|
142
|
+
export type AuctionSequenceView = AuctionScheduledView | AuctionEnterableView | AuctionParticipatingView | AuctionSettlingView | AuctionGrantedView | AuctionEndedView;
|
|
143
|
+
/**
|
|
144
|
+
* Scheduled auction view rendered before bidding opens.
|
|
145
|
+
*/
|
|
146
|
+
export interface AuctionScheduledView {
|
|
147
|
+
/** Phase discriminator; no auction action is legal yet. */
|
|
148
|
+
phase: "scheduled";
|
|
149
|
+
/** Mechanism discriminator for auction rendering. */
|
|
150
|
+
mechanism: "auction";
|
|
151
|
+
/** Auction distribution facts from the journey projector. */
|
|
152
|
+
distribution: DistributionSummary & {
|
|
153
|
+
type: "auction";
|
|
154
|
+
};
|
|
155
|
+
/** ISO 8601 opening timestamp chosen from the distribution start/open time. */
|
|
156
|
+
startsAt?: string;
|
|
157
|
+
/** Scheduled-only waitlist actions; this attachment never grants access. */
|
|
158
|
+
waitlist?: WaitlistAttachmentView;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Enterable auction view rendered when the first bid can be placed.
|
|
162
|
+
*/
|
|
163
|
+
export interface AuctionEnterableView {
|
|
164
|
+
/** Phase discriminator; `bid()` is legal on this view. */
|
|
165
|
+
phase: "enterable";
|
|
166
|
+
/** Mechanism discriminator for auction rendering. */
|
|
167
|
+
mechanism: "auction";
|
|
168
|
+
/** Open auction distribution facts from the journey projector. */
|
|
169
|
+
distribution: DistributionSummary & {
|
|
170
|
+
type: "auction";
|
|
171
|
+
};
|
|
172
|
+
/** Consumer auction facts before the first bid. */
|
|
173
|
+
consumer: AuctionSequenceConsumerState;
|
|
174
|
+
/**
|
|
175
|
+
* Place the first auction bid.
|
|
176
|
+
*
|
|
177
|
+
* Legal only while this view is current. `amount` is a decimal money string.
|
|
178
|
+
* The promise rejects if bidding is closed, the amount is invalid, or the
|
|
179
|
+
* server rejects the bid.
|
|
180
|
+
*/
|
|
181
|
+
bid(amount: string): Promise<void>;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Participating auction view rendered while the consumer is bidding.
|
|
185
|
+
*/
|
|
186
|
+
export interface AuctionParticipatingView {
|
|
187
|
+
/** Phase discriminator; `bid()` and `leave()` are legal on this view. */
|
|
188
|
+
phase: "participating";
|
|
189
|
+
/** Mechanism discriminator for auction rendering. */
|
|
190
|
+
mechanism: "auction";
|
|
191
|
+
/** Consumer auction participation facts. */
|
|
192
|
+
consumer: AuctionSequenceConsumerState;
|
|
193
|
+
/** Live auction display facts, such as highest bid, bid count, and close time. */
|
|
194
|
+
display$: ReadableAtom<AuctionDisplayState>;
|
|
195
|
+
/**
|
|
196
|
+
* Place or raise an auction bid.
|
|
197
|
+
*
|
|
198
|
+
* Legal only while this view is current. `amount` is a decimal money string.
|
|
199
|
+
* The promise rejects if the amount is below the minimum increment, bidding is
|
|
200
|
+
* closed, or the server rejects the bid.
|
|
201
|
+
*/
|
|
202
|
+
bid(amount: string): Promise<void>;
|
|
203
|
+
/**
|
|
204
|
+
* Leave the auction participation.
|
|
205
|
+
*
|
|
206
|
+
* Legal only while this view is current. The promise rejects if the consumer
|
|
207
|
+
* is no longer participating or the server rejects the leave request.
|
|
208
|
+
*/
|
|
209
|
+
leave(): Promise<void>;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Settling auction view rendered while the server computes the auction result.
|
|
213
|
+
*/
|
|
214
|
+
export interface AuctionSettlingView {
|
|
215
|
+
/** Phase discriminator; no auction action is legal while settling. */
|
|
216
|
+
phase: "settling";
|
|
217
|
+
/** Mechanism discriminator for auction rendering. */
|
|
218
|
+
mechanism: "auction";
|
|
219
|
+
/** Settling auction distribution facts from the journey projector. */
|
|
220
|
+
distribution: DistributionSummary & {
|
|
221
|
+
type: "auction";
|
|
222
|
+
};
|
|
223
|
+
/** Consumer auction participation facts during settlement. */
|
|
224
|
+
consumer: AuctionSequenceConsumerState;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Granted auction view rendered for a winning auction participation.
|
|
228
|
+
*/
|
|
229
|
+
export interface AuctionGrantedView {
|
|
230
|
+
/** Phase discriminator; `claim()` is legal on this view. */
|
|
231
|
+
phase: "granted";
|
|
232
|
+
/** Mechanism discriminator for auction rendering. */
|
|
233
|
+
mechanism: "auction";
|
|
234
|
+
/** Auction distribution facts from the journey projector. */
|
|
235
|
+
distribution: DistributionSummary & {
|
|
236
|
+
type: "auction";
|
|
237
|
+
};
|
|
238
|
+
/** Consumer auction facts associated with the grant. */
|
|
239
|
+
consumer: AuctionSequenceConsumerState;
|
|
240
|
+
/** Admission credential issued by the server for this auction win. */
|
|
241
|
+
grant: AdmissionGrant;
|
|
242
|
+
/**
|
|
243
|
+
* Claim the auction admission credential for handoff.
|
|
244
|
+
*
|
|
245
|
+
* Legal only while this view is current. It returns the credential
|
|
246
|
+
* synchronously and may throw if the journey has already left the granted
|
|
247
|
+
* phase.
|
|
248
|
+
*/
|
|
249
|
+
claim(): AdmissionGrant;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Ended auction view rendered once the auction journey is terminal.
|
|
253
|
+
*/
|
|
254
|
+
export interface AuctionEndedView {
|
|
255
|
+
/** Phase discriminator; no auction action is legal after this phase. */
|
|
256
|
+
phase: "ended";
|
|
257
|
+
/** Mechanism discriminator for auction rendering. */
|
|
258
|
+
mechanism: "auction";
|
|
259
|
+
/** Consumer auction facts at the terminal outcome. */
|
|
260
|
+
consumer: AuctionSequenceConsumerState;
|
|
261
|
+
/** Server-derived terminal outcome for the auction participation. */
|
|
262
|
+
outcome: SequenceOutcome;
|
|
263
|
+
}
|
package/dist/core/client.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
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:
|
|
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};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DistributionMonitor, MonitorUpdate } from '../experiences/distribution-monitor.types';
|
|
2
|
+
import { DistributionSummary, JourneyEvent, JourneyEventAudience, JourneyEventKind, JourneyEventSeverity, MechanismConsumerState, Participation, SequenceOutcome, SequenceState } from '../experiences/journey.types';
|
|
3
|
+
import { SequenceDriver, SequenceProjectionFacts } from '../experiences/sequence-driver';
|
|
4
|
+
import { DrawModule } from './types';
|
|
5
|
+
type DrawMonitorModule = DrawModule & Pick<DistributionMonitor, "startMonitoring" | "stopMonitoring">;
|
|
6
|
+
/**
|
|
7
|
+
* Narrow shell and module capabilities required by the draw driver.
|
|
8
|
+
*/
|
|
9
|
+
export interface DrawDriverDependencies {
|
|
10
|
+
/** Draw module functions used by draw actions and monitoring. */
|
|
11
|
+
draws: Pick<DrawMonitorModule, "enter" | "leave" | "startMonitoring" | "stopMonitoring">;
|
|
12
|
+
/** Serialize a draw action with the shell's operation queue. */
|
|
13
|
+
runSerializedOperation(operation: () => Promise<void>): Promise<void>;
|
|
14
|
+
/** Read the current routed sequence before validating a draw action. */
|
|
15
|
+
getCurrentSequence(): SequenceState | undefined;
|
|
16
|
+
/** Commit a concrete next draw sequence into the shell. */
|
|
17
|
+
commit(next: SequenceState, opts?: {
|
|
18
|
+
events?: JourneyEvent[];
|
|
19
|
+
}): void;
|
|
20
|
+
/** Apply shell-owned grant and terminal lifecycle side effects. */
|
|
21
|
+
applyMonitorUpdate(update: MonitorUpdate): void;
|
|
22
|
+
/** Ensure the shell-owned display atom exists for this draw participation. */
|
|
23
|
+
ensureDisplayAtom(participation: Participation): void;
|
|
24
|
+
/** Stop shell runtime monitoring and optionally clear display state. */
|
|
25
|
+
stopRuntimeMonitoring(clearDisplay?: boolean): void;
|
|
26
|
+
/** Append a shell-visible journey event to the snapshot. */
|
|
27
|
+
emitEvent(kind: JourneyEventKind, severity: JourneyEventSeverity, audience: JourneyEventAudience, message: string, detail?: Record<string, unknown>): void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create the draw sequence driver for one journey instance.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createDrawDriver(deps: DrawDriverDependencies): SequenceDriver;
|
|
33
|
+
/**
|
|
34
|
+
* Project draw server facts into a draw sequence state.
|
|
35
|
+
*/
|
|
36
|
+
export declare function projectDrawSequence(facts: SequenceProjectionFacts): SequenceState;
|
|
37
|
+
/**
|
|
38
|
+
* Return the draw consumer state that represents an issued grant.
|
|
39
|
+
*/
|
|
40
|
+
export declare function withDrawGrantedConsumer(consumer: MechanismConsumerState | undefined, distribution: DistributionSummary): MechanismConsumerState;
|
|
41
|
+
/**
|
|
42
|
+
* Return the draw consumer state that represents a terminal outcome.
|
|
43
|
+
*/
|
|
44
|
+
export declare function withDrawTerminalConsumer(consumer: MechanismConsumerState | undefined, outcome?: SequenceOutcome): MechanismConsumerState | undefined;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createError as t}from"../core/errors.js";function e(t){return new r(t)}function i(e){const{distribution:i,consumer:n,waitlist:s,grant:r,outcome:o}=e;if(!i)return{phase:"unavailable"};const u=function(e){if("draw"!==e.type)throw t.validationError("Draw driver received a non-draw distribution");return e}(i),c=n&&"draw"===n.mechanism?function(t){const{mechanism:e,...i}=t;return i}(n):a(i);if(r)return"completed"===o?.type?d(u,c,o):r.expiresAt&&Date.now()>new Date(r.expiresAt).getTime()?"open"===u.lifecycle?{phase:"enterable",mechanism:"draw",distribution:u,consumer:a(u)}:d(u,c,{type:"expired"}):{phase:"granted",mechanism:"draw",distribution:u,consumer:c,grant:r};if(!o&&"draw"===n?.mechanism&&"closed"===u.lifecycle)return d(u,c,{type:"lost"});const p=o??function(t){return"completed"===t.status?{type:"completed"}:"denied"===t.status?{type:"denied",reason:t.deniedReason}:void 0}(c);if(p)return d(u,c,p);if(function(t){return"entered"===t.status}(c))return{phase:"participating",mechanism:"draw",distribution:u,consumer:c};if("scheduled"===u.lifecycle){const t=s&&s.waitlistId===u.waitlistId?s:u.waitlistId?{waitlistId:u.waitlistId,status:"not_waitlisted"}:void 0;return{phase:"scheduled",mechanism:"draw",distribution:u,...t?{waitlist:t}:{}}}return"open"===u.lifecycle?{phase:"enterable",mechanism:"draw",distribution:u,consumer:c}:"settling"===u.lifecycle?{phase:"settling",mechanism:"draw",distribution:u,consumer:c}:d(u,c,{type:"closed"})}function n(t,e){return"draw"===t?.mechanism?{...t,status:"won"}:{mechanism:"draw",status:"won",id:e.id,distributionId:e.id}}function s(t,e){return t&&"draw"===t.mechanism&&e?"denied"===e.type?{...t,status:"denied",deniedReason:e.reason}:"completed"===e.type?{...t,status:"completed"}:"won"===e.type?{...t,status:"won"}:{...t,status:"not_entered"}:t}class r{constructor(t){this.deps=t,this.mechanism="draw"}project(t){return i(t)}buildView(e,i){if("unavailable"===e.phase)return{phase:"unavailable",reason:e.reason};const n=function(e){if(!("mechanism"in e)||"draw"!==e.mechanism)throw t.validationError("Draw driver received a non-draw sequence");return e}(e);switch(n.phase){case"scheduled":return{phase:"scheduled",mechanism:"draw",distribution:n.distribution,startsAt:n.distribution.startsAt??n.distribution.opensAt,waitlist:n.waitlist?{status:n.waitlist.status,join:()=>i.joinWaitlist(),leave:()=>i.exitWaitlist()}:void 0};case"enterable":return{phase:"enterable",mechanism:"draw",distribution:n.distribution,consumer:n.consumer,enter:()=>this.enter()};case"participating":return{phase:"participating",mechanism:"draw",consumer:n.consumer,display$:i.displayAtom,leave:()=>this.leave()};case"settling":return{phase:"settling",mechanism:"draw",distribution:n.distribution,consumer:n.consumer};case"granted":return{phase:"granted",mechanism:"draw",distribution:n.distribution,consumer:n.consumer,grant:n.grant,claim:()=>i.claim()};case"ended":return{phase:"ended",mechanism:"draw",consumer:n.consumer,outcome:n.outcome}}}startMonitoring(t){this.activeMonitorId=t.participation.id;const e=this.deps.getCurrentSequence(),i=e&&"participating"===e.phase&&"mechanism"in e&&"draw"===e.mechanism&&"draw"===e.distribution.details?.type?e.distribution.details.drawTime:void 0;this.deps.draws.startMonitoring(t.participation.id,{displayAtom:t.displayAtom,drawTime:i},t.onUpdate)}stopMonitoring(){this.activeMonitorId&&(this.deps.draws.stopMonitoring(this.activeMonitorId),this.activeMonitorId=void 0)}async enter(){await this.deps.runSerializedOperation(async()=>{await this.enterWithinOperation()})}async enterWithinOperation(){const t=this.currentDrawSequence("enterable","No open draw to enter").distribution,e=function(t){if("ENTERED"===t.status)return null;if("COMPLETED"===t.status)return{type:"ended",outcome:{type:"completed"}};if("WON"===t.status)return{type:"granted",token:t.admissionGrant,expiresAt:t.expiresAt?new Date(t.expiresAt).getTime():void 0};return{type:"ended",outcome:{type:"DENIED"===t.status?"denied":"lost",reason:"reason"in t?t.reason:void 0}}}(await this.deps.draws.enter(t.id,void 0));if(e)return void this.deps.applyMonitorUpdate(e);const n={mechanism:"draw",status:"entered",id:s=t.id,distributionId:s};var s;this.deps.ensureDisplayAtom({id:t.id,type:"draw"}),this.deps.commit(i({distribution:t,consumer:n})),this.deps.emitEvent("sequence_change","success","user","Entered draw",{distributionId:t.id})}async leave(){await this.deps.runSerializedOperation(async()=>{await this.leaveWithinOperation()})}async leaveWithinOperation(){const t=this.currentDrawSequence("participating"),e=t.consumer.distributionId??t.consumer.id??t.distribution.id;this.deps.stopRuntimeMonitoring(!0),await this.deps.draws.leave(e),this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"left"}}),this.deps.emitEvent("sequence_change","info","user","Left participation",{type:"draw"})}currentDrawSequence(e,i="Not participating in a draw"){const n=this.deps.getCurrentSequence();if(!n||n.phase!==e||!("mechanism"in n)||"draw"!==n.mechanism)throw t.validationError(i);return n}}function a(t){return{status:"not_entered",distributionId:t.id}}function d(t,e,i){return{phase:"ended",mechanism:"draw",distribution:t,consumer:e,outcome:{...i,distributionType:i.distributionType??t.type,distributionId:i.distributionId??t.id,at:i.at??/* @__PURE__ */(new Date).toISOString()}}}export{e as createDrawDriver,i as projectDrawSequence,n as withDrawGrantedConsumer,s as withDrawTerminalConsumer};
|
|
@@ -1 +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
|
|
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 n}from"../state/store.js";class a{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 n()}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 n=this.bumpOperationGeneration(e);this.logger.info("Entering draw",{drawId:e});const a=await this.http.post(`/draws/${e}/enter`,{metadata:i},r),o=this.buildTransition(e,a,{source:"enter",enteredAt:/* @__PURE__ */(new Date).toISOString(),metadata:i});return this.canApplyAsyncResult(e,s,void 0,n)?(this.applyTransition(e,o),a):a}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 n=this.buildTransition(t,s,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,n),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 n=this.buildTransition(t,s,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,n),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:n}){switch(e.status){case"ENTERED":return{trackedParticipation:{drawId:t,enteredAt:r?.enteredAt??s??/* @__PURE__ */(new Date).toISOString(),entryNumber:r?.entryNumber,metadata:r?.metadata??n},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:[],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"completed"}}: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),n=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,n)}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{a as DrawManagementModule};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { AdmissionGrant, DrawConsumerStatus, SequenceOutcome } from '@fanfare-io/fanfare-sdk-contracts/consumer-me';
|
|
2
|
+
import { ReadableAtom } from 'nanostores';
|
|
3
|
+
import { DrawDisplayState } from '../experiences/distribution-monitor.types';
|
|
4
|
+
import { DistributionSummary, WaitlistAttachment, WaitlistAttachmentView } from '../experiences/journey.types';
|
|
5
|
+
/**
|
|
6
|
+
* Draw participation facts for one consumer.
|
|
7
|
+
*
|
|
8
|
+
* The server is authoritative for every field. Timestamps are ISO 8601 strings.
|
|
9
|
+
* Optional fields are present only after the matching draw lifecycle event has
|
|
10
|
+
* occurred.
|
|
11
|
+
*/
|
|
12
|
+
export interface DrawSequenceConsumerState {
|
|
13
|
+
/** Server draw status for this consumer. */
|
|
14
|
+
status: DrawConsumerStatus;
|
|
15
|
+
/** Draw participation id, when the server has created one. */
|
|
16
|
+
id?: string;
|
|
17
|
+
/** Distribution id this draw participation belongs to. */
|
|
18
|
+
distributionId?: string;
|
|
19
|
+
/** ISO 8601 timestamp for when the consumer entered the draw. */
|
|
20
|
+
enteredAt?: string;
|
|
21
|
+
/** Opaque admission credential token, when one has been issued inline. */
|
|
22
|
+
admissionGrant?: string;
|
|
23
|
+
/** ISO 8601 timestamp for when the consumer was granted admission. */
|
|
24
|
+
grantedAt?: string;
|
|
25
|
+
/** Server-provided denial reason for denied draw outcomes. */
|
|
26
|
+
deniedReason?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A draw sequence as seen by one consumer.
|
|
30
|
+
*
|
|
31
|
+
* Lifecycle: scheduled -> enterable -> participating -> settling -> granted or
|
|
32
|
+
* ended. The consumer enters before the draw closes, settlement is server-driven,
|
|
33
|
+
* and a winning consumer receives a grant.
|
|
34
|
+
*/
|
|
35
|
+
export type DrawSequence = DrawScheduled | DrawEnterable | DrawParticipating | DrawSettling | DrawGranted | DrawEnded;
|
|
36
|
+
/**
|
|
37
|
+
* The draw exists but entry has not opened yet.
|
|
38
|
+
*/
|
|
39
|
+
export interface DrawScheduled {
|
|
40
|
+
/** Mechanism discriminator for draw narrowing. */
|
|
41
|
+
mechanism: "draw";
|
|
42
|
+
/** Phase discriminator; scheduled draws cannot be entered yet. */
|
|
43
|
+
phase: "scheduled";
|
|
44
|
+
/** Draw distribution facts from the journey projector. */
|
|
45
|
+
distribution: DistributionSummary & {
|
|
46
|
+
type: "draw";
|
|
47
|
+
};
|
|
48
|
+
/** Scheduled-only waitlist attachment, cleared when the draw opens. */
|
|
49
|
+
waitlist?: WaitlistAttachment;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The draw is open and this consumer has not entered it.
|
|
53
|
+
*/
|
|
54
|
+
export interface DrawEnterable {
|
|
55
|
+
/** Mechanism discriminator for draw narrowing. */
|
|
56
|
+
mechanism: "draw";
|
|
57
|
+
/** Phase discriminator; `enter()` is legal on the matching view. */
|
|
58
|
+
phase: "enterable";
|
|
59
|
+
/** Open draw distribution facts from the journey projector. */
|
|
60
|
+
distribution: DistributionSummary & {
|
|
61
|
+
type: "draw";
|
|
62
|
+
};
|
|
63
|
+
/** Consumer draw facts before entry. */
|
|
64
|
+
consumer: DrawSequenceConsumerState;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The consumer has entered the draw and is awaiting settlement.
|
|
68
|
+
*/
|
|
69
|
+
export interface DrawParticipating {
|
|
70
|
+
/** Mechanism discriminator for draw narrowing. */
|
|
71
|
+
mechanism: "draw";
|
|
72
|
+
/** Phase discriminator; the draw display is live while participating. */
|
|
73
|
+
phase: "participating";
|
|
74
|
+
/** Draw distribution facts from the journey projector. */
|
|
75
|
+
distribution: DistributionSummary & {
|
|
76
|
+
type: "draw";
|
|
77
|
+
};
|
|
78
|
+
/** Consumer draw participation facts. */
|
|
79
|
+
consumer: DrawSequenceConsumerState;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The draw has closed and the server is choosing outcomes.
|
|
83
|
+
*/
|
|
84
|
+
export interface DrawSettling {
|
|
85
|
+
/** Mechanism discriminator for draw narrowing. */
|
|
86
|
+
mechanism: "draw";
|
|
87
|
+
/** Phase discriminator; no consumer action is legal while settling. */
|
|
88
|
+
phase: "settling";
|
|
89
|
+
/** Settling draw distribution facts from the journey projector. */
|
|
90
|
+
distribution: DistributionSummary & {
|
|
91
|
+
type: "draw";
|
|
92
|
+
};
|
|
93
|
+
/** Consumer draw participation facts during settlement. */
|
|
94
|
+
consumer: DrawSequenceConsumerState;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The draw has issued an admission grant to this consumer.
|
|
98
|
+
*/
|
|
99
|
+
export interface DrawGranted {
|
|
100
|
+
/** Mechanism discriminator for draw narrowing. */
|
|
101
|
+
mechanism: "draw";
|
|
102
|
+
/** Phase discriminator; `claim()` is legal on the matching view. */
|
|
103
|
+
phase: "granted";
|
|
104
|
+
/** Draw distribution facts from the journey projector. */
|
|
105
|
+
distribution: DistributionSummary & {
|
|
106
|
+
type: "draw";
|
|
107
|
+
};
|
|
108
|
+
/** Consumer draw facts associated with the grant. */
|
|
109
|
+
consumer: DrawSequenceConsumerState;
|
|
110
|
+
/** Admission credential issued by the server for this draw win. */
|
|
111
|
+
grant: AdmissionGrant;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The draw journey is terminal for this consumer.
|
|
115
|
+
*/
|
|
116
|
+
export interface DrawEnded {
|
|
117
|
+
/** Mechanism discriminator for draw narrowing. */
|
|
118
|
+
mechanism: "draw";
|
|
119
|
+
/** Phase discriminator; no draw actions are legal after this phase. */
|
|
120
|
+
phase: "ended";
|
|
121
|
+
/** Draw distribution facts from the journey projector. */
|
|
122
|
+
distribution: DistributionSummary & {
|
|
123
|
+
type: "draw";
|
|
124
|
+
};
|
|
125
|
+
/** Consumer draw facts at the terminal outcome. */
|
|
126
|
+
consumer: DrawSequenceConsumerState;
|
|
127
|
+
/** Server-derived terminal outcome for the draw participation. */
|
|
128
|
+
outcome: SequenceOutcome;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Draw views exposed by `JourneyView.sequence`.
|
|
132
|
+
*
|
|
133
|
+
* The view lifecycle mirrors the draw sequence lifecycle. Actions exist only on
|
|
134
|
+
* the phases where they are legal, and each action may reject if the server has
|
|
135
|
+
* moved the sequence before the request is handled.
|
|
136
|
+
*/
|
|
137
|
+
export type DrawSequenceView = DrawScheduledView | DrawEnterableView | DrawParticipatingView | DrawSettlingView | DrawGrantedView | DrawEndedView;
|
|
138
|
+
/**
|
|
139
|
+
* Scheduled draw view rendered before entry opens.
|
|
140
|
+
*/
|
|
141
|
+
export interface DrawScheduledView {
|
|
142
|
+
/** Phase discriminator; no draw action is legal yet. */
|
|
143
|
+
phase: "scheduled";
|
|
144
|
+
/** Mechanism discriminator for draw rendering. */
|
|
145
|
+
mechanism: "draw";
|
|
146
|
+
/** Draw distribution facts from the journey projector. */
|
|
147
|
+
distribution: DistributionSummary & {
|
|
148
|
+
type: "draw";
|
|
149
|
+
};
|
|
150
|
+
/** ISO 8601 opening timestamp chosen from the distribution start/open time. */
|
|
151
|
+
startsAt?: string;
|
|
152
|
+
/** Scheduled-only waitlist actions; this attachment never grants access. */
|
|
153
|
+
waitlist?: WaitlistAttachmentView;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Enterable draw view rendered when the consumer can enter.
|
|
157
|
+
*/
|
|
158
|
+
export interface DrawEnterableView {
|
|
159
|
+
/** Phase discriminator; `enter()` is legal on this view. */
|
|
160
|
+
phase: "enterable";
|
|
161
|
+
/** Mechanism discriminator for draw rendering. */
|
|
162
|
+
mechanism: "draw";
|
|
163
|
+
/** Open draw distribution facts from the journey projector. */
|
|
164
|
+
distribution: DistributionSummary & {
|
|
165
|
+
type: "draw";
|
|
166
|
+
};
|
|
167
|
+
/** Consumer draw facts before entry. */
|
|
168
|
+
consumer: DrawSequenceConsumerState;
|
|
169
|
+
/**
|
|
170
|
+
* Enter the draw.
|
|
171
|
+
*
|
|
172
|
+
* Legal only while this view is current. The promise rejects if draw entry is
|
|
173
|
+
* no longer allowed or the server rejects the participation request.
|
|
174
|
+
*/
|
|
175
|
+
enter(): Promise<void>;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Participating draw view rendered while the consumer is entered.
|
|
179
|
+
*/
|
|
180
|
+
export interface DrawParticipatingView {
|
|
181
|
+
/** Phase discriminator; `leave()` is legal on this view. */
|
|
182
|
+
phase: "participating";
|
|
183
|
+
/** Mechanism discriminator for draw rendering. */
|
|
184
|
+
mechanism: "draw";
|
|
185
|
+
/** Consumer draw participation facts. */
|
|
186
|
+
consumer: DrawSequenceConsumerState;
|
|
187
|
+
/** Live draw display facts, such as participant counts and draw timing. */
|
|
188
|
+
display$: ReadableAtom<DrawDisplayState>;
|
|
189
|
+
/**
|
|
190
|
+
* Leave the draw.
|
|
191
|
+
*
|
|
192
|
+
* Legal only while this view is current. The promise rejects if the consumer
|
|
193
|
+
* is no longer participating or the server rejects the leave request.
|
|
194
|
+
*/
|
|
195
|
+
leave(): Promise<void>;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Settling draw view rendered while the server computes the draw result.
|
|
199
|
+
*/
|
|
200
|
+
export interface DrawSettlingView {
|
|
201
|
+
/** Phase discriminator; no draw action is legal while settling. */
|
|
202
|
+
phase: "settling";
|
|
203
|
+
/** Mechanism discriminator for draw rendering. */
|
|
204
|
+
mechanism: "draw";
|
|
205
|
+
/** Settling draw distribution facts from the journey projector. */
|
|
206
|
+
distribution: DistributionSummary & {
|
|
207
|
+
type: "draw";
|
|
208
|
+
};
|
|
209
|
+
/** Consumer draw participation facts during settlement. */
|
|
210
|
+
consumer: DrawSequenceConsumerState;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Granted draw view rendered for a winning draw participation.
|
|
214
|
+
*/
|
|
215
|
+
export interface DrawGrantedView {
|
|
216
|
+
/** Phase discriminator; `claim()` is legal on this view. */
|
|
217
|
+
phase: "granted";
|
|
218
|
+
/** Mechanism discriminator for draw rendering. */
|
|
219
|
+
mechanism: "draw";
|
|
220
|
+
/** Draw distribution facts from the journey projector. */
|
|
221
|
+
distribution: DistributionSummary & {
|
|
222
|
+
type: "draw";
|
|
223
|
+
};
|
|
224
|
+
/** Consumer draw facts associated with the grant. */
|
|
225
|
+
consumer: DrawSequenceConsumerState;
|
|
226
|
+
/** Admission credential issued by the server for this draw win. */
|
|
227
|
+
grant: AdmissionGrant;
|
|
228
|
+
/**
|
|
229
|
+
* Claim the draw admission credential for handoff.
|
|
230
|
+
*
|
|
231
|
+
* Legal only while this view is current. It returns the credential
|
|
232
|
+
* synchronously and may throw if the journey has already left the granted
|
|
233
|
+
* phase.
|
|
234
|
+
*/
|
|
235
|
+
claim(): AdmissionGrant;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Ended draw view rendered once the draw journey is terminal.
|
|
239
|
+
*/
|
|
240
|
+
export interface DrawEndedView {
|
|
241
|
+
/** Phase discriminator; no draw action is legal after this phase. */
|
|
242
|
+
phase: "ended";
|
|
243
|
+
/** Mechanism discriminator for draw rendering. */
|
|
244
|
+
mechanism: "draw";
|
|
245
|
+
/** Consumer draw facts at the terminal outcome. */
|
|
246
|
+
consumer: DrawSequenceConsumerState;
|
|
247
|
+
/** Server-derived terminal outcome for the draw participation. */
|
|
248
|
+
outcome: SequenceOutcome;
|
|
249
|
+
}
|