@fanfare-io/fanfare-sdk-core 0.11.0 → 0.12.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.module.d.ts +7 -6
- package/dist/appointments/appointment.module.js +1 -1
- package/dist/appointments/types.d.ts +11 -5
- package/dist/auctions/auction.driver.js +1 -1
- package/dist/auctions/auction.module.js +1 -1
- package/dist/auctions/auction.sequence.d.ts +2 -0
- package/dist/auctions/auction.sequence.js +1 -0
- package/dist/core/client.js +2 -2
- package/dist/core/error-display.js +1 -1
- package/dist/core/error-disposition.js +1 -1
- package/dist/core/errors.d.ts +32 -1
- package/dist/core/errors.js +1 -1
- package/dist/core/http.js +1 -1
- package/dist/draws/draw.driver.d.ts +2 -2
- package/dist/draws/draw.driver.js +1 -1
- package/dist/draws/draw.module.d.ts +8 -3
- package/dist/draws/draw.module.js +1 -1
- package/dist/draws/draw.sequence.d.ts +216 -12
- package/dist/draws/draw.sequence.js +1 -0
- package/dist/draws/public.d.ts +1 -1
- package/dist/draws/types.d.ts +33 -3
- package/dist/experiences/distribution-monitor.types.d.ts +31 -6
- package/dist/experiences/dom-bridge.d.ts +40 -0
- package/dist/experiences/dom-bridge.js +1 -0
- package/dist/experiences/experience.module.d.ts +4 -4
- package/dist/experiences/experience.module.js +1 -1
- package/dist/experiences/journey-display.d.ts +59 -0
- package/dist/experiences/journey-display.js +1 -0
- package/dist/experiences/journey-view.d.ts +5 -0
- package/dist/experiences/journey-view.js +1 -1
- package/dist/experiences/journey.d.ts +79 -1
- package/dist/experiences/journey.js +1 -1
- package/dist/experiences/journey.machine.d.ts +11 -0
- package/dist/experiences/journey.machine.js +1 -1
- package/dist/experiences/journey.types.d.ts +9 -45
- package/dist/experiences/public.d.ts +15 -5
- package/dist/experiences/public.js +1 -1
- package/dist/experiences/sequence-driver.d.ts +31 -4
- package/dist/experiences/settlement.d.ts +3 -0
- package/dist/experiences/settlement.js +1 -0
- package/dist/experiences/types.d.ts +13 -4
- package/dist/internals.d.ts +5 -1
- package/dist/internals.js +1 -1
- package/dist/payment/checkout-contract.d.ts +39 -0
- package/dist/payment/checkout-contract.js +1 -0
- package/dist/payment/payment.module.d.ts +16 -0
- package/dist/payment/payment.module.js +1 -0
- package/dist/payment/reservations.module.d.ts +17 -0
- package/dist/payment/reservations.module.js +1 -0
- package/dist/payment/types.d.ts +68 -0
- package/dist/queues/queue.driver.js +1 -1
- package/dist/queues/queue.module.d.ts +2 -1
- package/dist/queues/queue.module.js +1 -1
- package/dist/queues/queue.sequence.d.ts +214 -10
- package/dist/queues/types.d.ts +47 -13
- package/dist/ssr/ssr-sdk.js +1 -1
- package/dist/state/events.d.ts +1 -16
- package/dist/state/store.js +1 -1
- package/dist/test-utils/harness-scenarios.d.ts +20 -5
- package/dist/test-utils/harness-scenarios.js +1 -1
- package/dist/test-utils/index.d.ts +1 -0
- package/dist/test-utils/index.js +1 -1
- package/dist/test-utils/mock-journey.d.ts +2 -0
- package/dist/test-utils/mock-journey.js +1 -1
- package/dist/test-utils/mock-sdk-vitest.d.ts +30 -29
- package/dist/test-utils/mock-sdk-vitest.js +1 -1
- package/dist/test-utils/mock-sdk.d.ts +8 -0
- package/dist/test-utils/mock-sdk.js +1 -1
- package/dist/test-utils/mock-server.d.ts +20 -0
- package/dist/test-utils/mock-server.js +1 -1
- package/dist/test-utils/phone-formatter-contract.d.ts +24 -0
- package/dist/test-utils/phone-formatter-contract.js +1 -0
- package/dist/timed-releases/timed-release.module.js +1 -1
- package/dist/types/index.d.ts +12 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/waitlists/types.d.ts +3 -6
- package/dist/waitlists/waitlist.module.d.ts +3 -3
- package/package.json +12 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HttpClient } from '../core/http';
|
|
1
|
+
import { HttpClient, RequestOptions } from '../core/http';
|
|
2
2
|
import { MonitorUpdate } from '../experiences/distribution-monitor.types';
|
|
3
3
|
import { AppointmentDetails } from '../experiences/types';
|
|
4
4
|
import { AppointmentBooking, AppointmentModule, AppointmentSlot } from './types';
|
|
@@ -8,21 +8,22 @@ export declare class AppointmentManagementModule implements AppointmentModule {
|
|
|
8
8
|
private readonly monitorRuntime;
|
|
9
9
|
private readonly pollSchedulers;
|
|
10
10
|
private readonly monitorGenerations;
|
|
11
|
+
private readonly observedBooking;
|
|
11
12
|
private readonly pollingIntervalMs;
|
|
12
13
|
private destroyed;
|
|
13
14
|
constructor(http: HttpClient, opts?: {
|
|
14
15
|
pollingIntervalMs?: number;
|
|
15
16
|
});
|
|
16
|
-
get(appointmentId: string): Promise<AppointmentDetails>;
|
|
17
|
+
get(appointmentId: string, options?: RequestOptions): Promise<AppointmentDetails>;
|
|
17
18
|
getSlots(appointmentId: string, range: {
|
|
18
19
|
startDate: string;
|
|
19
20
|
endDate: string;
|
|
20
21
|
timezone?: string;
|
|
21
|
-
}): Promise<AppointmentSlot[]>;
|
|
22
|
+
}, options?: RequestOptions): Promise<AppointmentSlot[]>;
|
|
22
23
|
getMe(appointmentId: string): Promise<AppointmentBooking | null>;
|
|
23
|
-
book(appointmentId: string, slotId: string, locationId?: string): Promise<AppointmentBooking>;
|
|
24
|
-
cancel(appointmentId: string, slotId: string, locationId?: string, reason?: string): Promise<void>;
|
|
25
|
-
reschedule(appointmentId: string, newSlotId: string, newLocationId?: string): Promise<AppointmentBooking>;
|
|
24
|
+
book(appointmentId: string, slotId: string, locationId?: string, options?: RequestOptions): Promise<AppointmentBooking>;
|
|
25
|
+
cancel(appointmentId: string, slotId: string, locationId?: string, reason?: string, options?: RequestOptions): Promise<void>;
|
|
26
|
+
reschedule(appointmentId: string, newSlotId: string, newLocationId?: string, options?: RequestOptions): Promise<AppointmentBooking>;
|
|
26
27
|
startMonitoring(id: string, context?: Record<string, unknown>, onUpdate?: (update: MonitorUpdate) => void, onDegradedChange?: (degraded: boolean) => void): void;
|
|
27
28
|
stopMonitoring(id: string): void;
|
|
28
29
|
isMonitoring(id: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as t from"valibot";import{isFanfareError as o,createError as
|
|
1
|
+
import*as t from"valibot";import{isFanfareError as o,createError as i}from"../core/errors.js";import{getLogger as n}from"../core/logger.js";import{parseResponse as e}from"../core/parse-response.js";import{createPollScheduler as r}from"../core/poll-scheduler.js";import{DistributionMonitorRuntime as s}from"../experiences/distribution-monitor.runtime.js";function a(t,o,i){const n=e(t,o,{endpoint:i},{throwOnFailure:!0});if(!n.ok)throw n.error;return n.value}const l=t.object({id:t.string(),startTime:t.string(),endTime:t.string(),locationId:t.nullish(t.string()),locationName:t.nullish(t.string()),timezone:t.nullish(t.string()),capacity:t.number(),available:t.number()}),d=t.object({slots:t.array(l)}),m=t.object({id:t.string(),type:t.literal("appointment"),openAt:t.nullish(t.string()),closeAt:t.nullish(t.string()),slotDurationMinutes:t.nullish(t.number()),timeZone:t.nullish(t.string()),locationId:t.nullish(t.string()),totalSlots:t.nullish(t.number()),availableSlots:t.nullish(t.number())}),c=t.picklist(["booked","checked_in","completed","cancelled","no_show"]),p=t.object({bookingId:t.string(),appointmentId:t.string(),consumerId:t.string(),slotId:t.string(),locationId:t.nullish(t.string()),locationName:t.nullish(t.string()),startTime:t.string(),endTime:t.string(),confirmationCode:t.string(),status:c,createdAt:t.string()}),h=t.object({booking:t.nullish(t.object({bookingId:t.string(),appointmentId:t.string(),consumerId:t.string(),slotId:t.nullish(t.string()),locationId:t.nullish(t.string()),locationName:t.nullish(t.string()),joinUrl:t.nullish(t.string()),startTime:t.nullish(t.string()),endTime:t.nullish(t.string()),confirmationCode:t.nullish(t.string()),status:c}))}),g=t.object({bookingId:t.string(),previousSlotId:t.string(),newSlotId:t.string(),confirmationCode:t.string(),status:c});function u(t){return{id:t.id,startTime:t.startTime,endTime:t.endTime,locationId:t.locationId??void 0,locationName:t.locationName??void 0,timezone:t.timezone??void 0,capacity:t.capacity,available:t.available}}class I{constructor(t,o){this.logger=n(),this.monitorRuntime=new s,this.pollSchedulers=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.observedBooking=/* @__PURE__ */new Map,this.destroyed=!1,this.http=t,this.pollingIntervalMs=o?.pollingIntervalMs??3e4}async get(t,n){try{const o=await this.http.get(`/appointments/${t}`,n),i=a(m,o,`/appointments/${t}`);return{id:i.id,type:"appointment",openAt:i.openAt??void 0,closeAt:i.closeAt??void 0,slotDurationMinutes:i.slotDurationMinutes??void 0,bookingWindowOpenAt:i.openAt??void 0,bookingWindowCloseAt:i.closeAt??void 0,timezone:i.timeZone??void 0,locationId:i.locationId??void 0,totalSlots:i.totalSlots??void 0,availableSlots:i.availableSlots??void 0}}catch(e){if(this.logger.error("Failed to get appointment",{appointmentId:t,error:e}),o(e))throw e;throw i.networkError("Failed to get appointment",{error:e})}}async getSlots(t,n,e){try{const o=new URLSearchParams({startDate:n.startDate,endDate:n.endDate});n.timezone&&o.set("timezone",n.timezone);const i=await this.http.get(`/appointments/${t}/slots?${o.toString()}`,e);return a(d,i,`/appointments/${t}/slots`).slots.map(u)}catch(r){if(this.logger.error("Failed to get appointment slots",{appointmentId:t,error:r}),o(r))throw r;throw i.networkError("Failed to get appointment slots",{error:r})}}async getMe(t){try{const o=await this.http.get(`/appointments/${t}/me`),i=a(h,o,`/appointments/${t}/me`);return i.booking?function(t){return{bookingId:t.bookingId,appointmentId:t.appointmentId,slotId:t.slotId??"",locationId:t.locationId??void 0,locationName:t.locationName??void 0,joinUrl:t.joinUrl??void 0,startTime:t.startTime??"",endTime:t.endTime??"",confirmationCode:t.confirmationCode??void 0,consumerStatus:t.status}}(i.booking):null}catch(n){if(this.logger.error("Failed to get current booking",{appointmentId:t,error:n}),o(n))throw n;throw i.networkError("Failed to get current booking",{error:n})}}async book(t,n,e,r){try{const o=await this.http.post(`/appointments/${t}/book`,{slotId:n,locationId:e??null},r);return function(t){return{bookingId:t.bookingId,appointmentId:t.appointmentId,slotId:t.slotId,locationId:t.locationId??void 0,locationName:t.locationName??void 0,startTime:t.startTime,endTime:t.endTime,confirmationCode:t.confirmationCode,consumerStatus:t.status}}(a(p,o,`/appointments/${t}/book`))}catch(s){if(this.logger.error("Failed to book appointment",{appointmentId:t,slotId:n,locationId:e,error:s}),o(s))throw s;throw i.networkError("Failed to book appointment",{error:s})}}async cancel(t,n,e,r,s){try{await this.http.post(`/appointments/${t}/cancel`,{slotId:n,locationId:e??null,reason:r??null},s)}catch(a){if(this.logger.error("Failed to cancel appointment",{appointmentId:t,slotId:n,locationId:e,error:a}),o(a))throw a;throw i.networkError("Failed to cancel appointment",{error:a})}}async reschedule(t,n,e,r){try{const o=await this.http.post(`/appointments/${t}/reschedule`,{newSlotId:n,newLocationId:e??null},r),i=a(g,o,`/appointments/${t}/reschedule`),s=await this.getMe(t);return s?{...s,confirmationCode:i.confirmationCode}:{bookingId:i.bookingId,appointmentId:t,slotId:i.newSlotId,locationId:e,startTime:"",endTime:"",confirmationCode:i.confirmationCode,consumerStatus:i.status}}catch(s){if(this.logger.error("Failed to reschedule appointment",{appointmentId:t,newSlotId:n,newLocationId:e,error:s}),o(s))throw s;throw i.networkError("Failed to reschedule appointment",{error:s})}}startMonitoring(t,o,i,n){if(this.destroyed)return;this.monitorRuntime.start(t,o,i),this.stopPolling(t);const e=this.monitorRuntime.getDisplayAtom(t)?.get();this.observedBooking.set(t,"appointment"===e?.type&&void 0!==e.booking);const s=(this.monitorGenerations.get(t)??0)+1;this.monitorGenerations.set(t,s);const a=r({baseMs:this.pollingIntervalMs,suppressBackoff:()=>!1,onDegradedChange:n,shouldContinue:()=>!this.destroyed&&this.monitorGenerations.get(t)===s&&this.pollSchedulers.has(t),run:async()=>{if(!this.destroyed&&this.monitorGenerations.get(t)===s)try{const o=await this.getMe(t);if(this.destroyed||this.monitorGenerations.get(t)!==s)return;const i=this.monitorRuntime.getDisplayAtom(t);if(!i)return;if(!o)return void(this.observedBooking.get(t)&&(this.monitorRuntime.notify(t,{type:"ended",outcome:{type:"closed"}}),this.stopMonitoring(t)));if(this.observedBooking.set(t,!0),"cancelled"===o.consumerStatus||"no_show"===o.consumerStatus)return this.monitorRuntime.notify(t,{type:"ended",outcome:{type:o.consumerStatus}}),void this.stopMonitoring(t);const n=Math.max(0,Date.parse(o.startTime)-Date.now());i.set({type:"appointment",booking:{slotId:o.slotId,locationId:o.locationId,locationName:o.locationName,joinUrl:o.joinUrl,startTime:o.startTime,endTime:o.endTime,confirmationCode:o.confirmationCode,consumerStatus:o.consumerStatus},msUntilStart:n})}catch(o){throw this.logger.error("Appointment polling tick failed",{id:t,error:o}),o}}});this.pollSchedulers.set(t,a),a.start()}stopMonitoring(t){this.stopPolling(t),this.monitorRuntime.stop(t),this.observedBooking.delete(t),this.monitorGenerations.set(t,(this.monitorGenerations.get(t)??0)+1)}isMonitoring(t){return this.monitorRuntime.has(t)||this.pollSchedulers.has(t)}bumpMonitorGeneration(t){this.monitorGenerations.set(t,(this.monitorGenerations.get(t)??0)+1)}destroy(){this.destroyed=!0,this.monitorGenerations.clear(),this.observedBooking.clear();for(const t of Array.from(this.pollSchedulers.keys()))this.stopPolling(t);this.monitorRuntime.clear()}stopPolling(t){const o=this.pollSchedulers.get(t);void 0!==o&&(o.stop(),this.pollSchedulers.delete(t))}}export{I as AppointmentManagementModule};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { RequestOptions } from '../core/http';
|
|
1
2
|
import { MonitorUpdate } from '../experiences/distribution-monitor.types';
|
|
3
|
+
import { AppointmentDetails } from '../experiences/types';
|
|
2
4
|
export interface AppointmentSlot {
|
|
3
5
|
id: string;
|
|
4
6
|
startTime: string;
|
|
@@ -23,16 +25,20 @@ export interface AppointmentBooking {
|
|
|
23
25
|
consumerStatus: "booked" | "checked_in" | "completed" | "cancelled" | "no_show";
|
|
24
26
|
}
|
|
25
27
|
export interface AppointmentModule {
|
|
26
|
-
get(appointmentId: string): Promise<
|
|
28
|
+
get(appointmentId: string, options?: RequestOptions): Promise<AppointmentDetails>;
|
|
27
29
|
getSlots(appointmentId: string, range: {
|
|
28
30
|
startDate: string;
|
|
29
31
|
endDate: string;
|
|
30
32
|
timezone?: string;
|
|
31
|
-
}): Promise<AppointmentSlot[]>;
|
|
33
|
+
}, options?: RequestOptions): Promise<AppointmentSlot[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Own-booking-scoped read. Ungated server-side, so it carries no capability
|
|
36
|
+
* grant and takes no request options.
|
|
37
|
+
*/
|
|
32
38
|
getMe(appointmentId: string): Promise<AppointmentBooking | null>;
|
|
33
|
-
book(appointmentId: string, slotId: string, locationId?: string): Promise<AppointmentBooking>;
|
|
34
|
-
cancel(appointmentId: string, slotId: string, locationId?: string, reason?: string): Promise<void>;
|
|
35
|
-
reschedule(appointmentId: string, newSlotId: string, newLocationId?: string): Promise<AppointmentBooking>;
|
|
39
|
+
book(appointmentId: string, slotId: string, locationId?: string, options?: RequestOptions): Promise<AppointmentBooking>;
|
|
40
|
+
cancel(appointmentId: string, slotId: string, locationId?: string, reason?: string, options?: RequestOptions): Promise<void>;
|
|
41
|
+
reschedule(appointmentId: string, newSlotId: string, newLocationId?: string, options?: RequestOptions): Promise<AppointmentBooking>;
|
|
36
42
|
startMonitoring(id: string, context?: Record<string, unknown>, onUpdate?: (update: MonitorUpdate) => void, onDegradedChange?: (degraded: boolean) => void): void;
|
|
37
43
|
stopMonitoring(id: string): void;
|
|
38
44
|
isMonitoring(id: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AuctionDistributionResults as t}from"@fanfare-io/fanfare-sdk-contracts";import{createError as i}from"../core/errors.js";import{isMoneyString as e,addMoney as n}from"../core/money.js";function
|
|
1
|
+
import{AuctionDistributionResults as t}from"@fanfare-io/fanfare-sdk-contracts";import{createError as i}from"../core/errors.js";import{isMoneyString as e,addMoney as n}from"../core/money.js";import{isUnresolvedAuctionConsumer as s}from"./auction.sequence.js";function o(t){return!t||("lost"===t.type||"closed"===t.type)&&!t.reason}function a(t){return new d(t)}function r(e){const{distribution:n,consumer:a,grant:r,outcome:c}=e;if(!n)return{phase:"unavailable"};const u=function(t){if("auction"!==t.type)throw i.validationError("Auction driver received a non-auction distribution");return t}(n),d=a&&"auction"===a.mechanism?function(t){const{mechanism:i,...e}=t;return e}(a):p(n);if(r)return"completed"===c?.type?h(u,d,c):r.expiresAt&&Date.now()>new Date(r.expiresAt).getTime()?"open"===u.lifecycle?{phase:"enterable",mechanism:"auction",distribution:u,consumer:p(u)}:h(u,d,{type:"expired"}):{phase:"granted",mechanism:"auction",distribution:u,consumer:d,grant:r};if("closed"===u.lifecycle){if("not_bid"===d.status&&u.result===t.FLOOR_UNSOLD&&o(c))return h(u,d,{type:"lost",reason:t.FLOOR_UNSOLD});if("auction"===a?.mechanism&&!c)return h(u,d,{type:"lost"})}const m=c??function(t){return"lost"===t.status?{type:"lost"}:void 0}(d);return m?h(u,d,m):"settling"===u.lifecycle?{phase:"settling",mechanism:"auction",distribution:u,consumer:d}:s(d)?{phase:"participating",mechanism:"auction",distribution:u,consumer:d}:"scheduled"===u.lifecycle?{phase:"scheduled",mechanism:"auction",distribution:u}:"open"===u.lifecycle?{phase:"enterable",mechanism:"auction",distribution:u,consumer:d}:h(u,d,{type:"closed"})}function c(t,i){return"auction"===t?.mechanism?{...t,status:"won"}:{mechanism:"auction",status:"won",id:i.id,distributionId:i.id}}function u(t,i,e){return t&&"auction"===t.mechanism&&i?"won"===i.type?{...t,status:"won"}:!e?.finalize&&"not_bid"===t.status&&o(i)?t:{...t,status:"lost"}:t}class d{constructor(t){this.deps=t,this.mechanism="auction"}project(t){return r(t)}buildView(t,e){if("unavailable"===t.phase)return{phase:"unavailable",reason:t.reason};const n=function(t){if(!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("Auction driver received a non-auction sequence");return t}(t);switch(n.phase){case"scheduled":return{phase:"scheduled",mechanism:"auction",distribution:n.distribution,startsAt:n.distribution.startsAt??n.distribution.opensAt};case"enterable":return{phase:"enterable",mechanism:"auction",distribution:n.distribution,consumer:n.consumer,bid:t=>this.bid(t)};case"participating":return{phase:"participating",mechanism:"auction",consumer:n.consumer,display$:e.displayAtom,bid:t=>this.bid(t)};case"settling":return{phase:"settling",mechanism:"auction",distribution:n.distribution,consumer:n.consumer};case"granted":return{phase:"granted",mechanism:"auction",distribution:n.distribution,consumer:n.consumer,grant:n.grant,claim:()=>e.claim()};case"ended":return{phase:"ended",mechanism:"auction",consumer:n.consumer,outcome:n.outcome}}}startMonitoring(t){this.activeMonitorId=t.participation.id,this.deps.auctions.startMonitoring(t.participation.id,{displayAtom:t.displayAtom},t.onUpdate,t.onDegradedChange)}stopMonitoring(){this.activeMonitorId&&(this.deps.auctions.stopMonitoring(this.activeMonitorId),this.activeMonitorId=void 0)}async enter(){await this.deps.runSerializedOperation(async()=>{await this.enterWithinOperation()})}async enterWithinOperation(){const t=this.currentAuctionSequence("enterable","No open auction to enter").distribution;await this.deps.auctions.enter(t.id,void 0),await this.deps.auctions.status(t.id);const i={mechanism:"auction",status:"not_bid",id:e=t.id,distributionId:e};var e;this.deps.ensureDisplayAtom({id:t.id,type:"auction"}),this.deps.commit(r({distribution:t,consumer:i})),this.deps.emitEvent("sequence_change","success","user","Entered auction",{distributionId:t.id})}async leave(){await this.deps.runSerializedOperation(async()=>{await this.leaveWithinOperation()})}async leaveWithinOperation(){const t=this.currentAuctionSequence("participating"),i=t.consumer.distributionId??t.consumer.id??t.distribution.id;await this.deps.auctions.leave(i),this.deps.stopRuntimeMonitoring(!0),this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"left"}}),this.deps.emitEvent("sequence_change","info","user","Left participation",{type:"auction"})}async bid(t){await this.deps.runSerializedOperation(async()=>{const i=this.currentAuctionSequenceForBid(),s=i.consumer.distributionId??i.consumer.id??i.distribution.id,o=await this.deps.auctions.bid(s,t);if("won"===o.status||"lost"===o.status){const t=this.deps.getCurrentSequence();return void(t&&("enterable"===t.phase||"participating"===t.phase||"settling"===t.phase)&&"mechanism"in t&&"auction"===t.mechanism&&t.distribution.id===s&&("won"===o.status&&o.admissionGrant?this.deps.applyMonitorUpdate({type:"granted",token:o.admissionGrant}):this.deps.applyMonitorUpdate({type:"ended",outcome:{type:o.status}})))}const a=function(t){switch(t){case"not_bid":case"bidding":case"winning":case"outbid":return t;case"won":case"lost":return}}(o.status);if(void 0===a)return;const c="auction"===i.distribution.details?.type?i.distribution.details:void 0,u=c?.bidIncrement,d=o.highestBid&&u&&e(o.highestBid)&&e(u)?n(o.highestBid,u):void 0,p=this.deps.getDisplayState(),h="auction"===p?.type?p.settleAt:void 0,m=this.deps.ensureDisplayAtom({id:s,type:"auction"});"enterable"===i.phase&&this.deps.commit(r({distribution:i.distribution,consumer:{mechanism:"auction",status:a,id:s,distributionId:s,currentBid:t,highestBid:o.highestBid}})),m.set({type:"auction",currentBid:t,highestBid:o.highestBid,bidCount:o.bidCount,status:a,currencyCode:c?.currencyCode,bidIncrement:u,minNextBid:d,closeAt:c?.closeAt??c?.endsAt,settleAt:h})})}currentAuctionSequence(t,e="Not participating in an auction"){const n=this.deps.getCurrentSequence();if(!n||n.phase!==t||!("mechanism"in n)||"auction"!==n.mechanism)throw i.validationError(e);return n}currentAuctionSequenceForBid(){const t=this.deps.getCurrentSequence();if(!t||"enterable"!==t.phase&&"participating"!==t.phase||!("mechanism"in t)||"auction"!==t.mechanism)throw i.validationError("No auction is available to bid on");return t}}function p(t){return{status:"not_bid",distributionId:t.id}}function h(t,i,e){return{phase:"ended",mechanism:"auction",distribution:t,consumer:i,outcome:{...e,distributionType:e.distributionType??t.type,distributionId:e.distributionId??t.id,at:e.at??/* @__PURE__ */(new Date).toISOString()}}}export{a as createAuctionDriver,o as isUpgradeableGenericLoss,r as projectAuctionSequence,c as withAuctionGrantedConsumer,u as withAuctionTerminalConsumer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AuctionBidderStateResponseSchema as t,AuctionHighestBidResponseSchema as i,AuctionBidHistoryResponseSchema as e}from"@fanfare-io/fanfare-sdk-contracts/auction";import{createError as s}from"../core/errors.js";import{getLogger as n}from"../core/logger.js";import{isMoneyString as a,addMoney as o,isMoneyGreaterThan as r,normalizeMoney as u,isMoneyLessThanOrEqualTo as d,subtractMoney as h}from"../core/money.js";import{parseResponse as c}from"../core/parse-response.js";import{createPollScheduler as l}from"../core/poll-scheduler.js";import{DistributionMonitorRuntime as g}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as p}from"../state/events.js";import{getSDKStore as m}from"../state/store.js";import{isTerminalAuctionStatus as b,toAuctionConsumerStatus as y}from"./auction-status.js";class f{constructor(t){this.logger=n(),this.events=p(),this.trackedParticipations=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.pollSchedulers=/* @__PURE__ */new Map,this.lastStatusCache=/* @__PURE__ */new Map,this.hasSuccessfulReadThisSession=/* @__PURE__ */new Map,this.lastSettleAtMs=/* @__PURE__ */new Map,this.visibilityHandlers=/* @__PURE__ */new Map,this.autoRebidConfigs=/* @__PURE__ */new Map,this.monitorRuntime=new g,this.bidHistoryCache=/* @__PURE__ */new Map,this.auctionDetailsCache=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.BID_HISTORY_CACHE_TTL=5e3,this.AUCTION_DETAILS_CACHE_TTL=3e4,this.NO_BID_END_GRACE_MS=5e3,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return m()}computeMinNextBid(t,i){if(i&&a(t)&&a(i))return o(t,i)}async getAuctionDetailsCached(t){const i=this.auctionDetailsCache.get(t);if(i&&Date.now()-i.fetchedAt<this.AUCTION_DETAILS_CACHE_TTL)return i.details;const e=await this.http.get(`/auctions/${t}`);return this.auctionDetailsCache.set(t,{details:e,fetchedAt:Date.now()}),e}getTrackedParticipation(t){return this.trackedParticipations.get(t)}getAuctionCloseAt(t){return t.closeAt??t.settleAt}getAuctionSettleAt(t){return t.settleAt??t.closeAt}buildDisplayState(t,{status:i,highestBid:e,currentBid:s,bidCount:n}){return{highestBid:e,currentBid:s,bidCount:n,closeAt:this.getAuctionCloseAt(t),settleAt:t.settleAt,status:i,currencyCode:t.currencyCode,minNextBid:e?this.computeMinNextBid(e,t.minBidIncrement):void 0,bidIncrement:t.minBidIncrement??void 0}}emitTransitionEvents(t){for(const i of t)this.events.emit(i.type,i.payload)}applyTransition(t,i){void 0!==i.trackedParticipation&&(i.trackedParticipation?this.trackedParticipations.set(t,i.trackedParticipation):this.trackedParticipations.delete(t));const e=this.monitorRuntime.getDisplayAtom(t);e&&i.displayState&&e.set({type:"auction",...i.displayState}),this.emitTransitionEvents(i.events),i.monitorUpdate&&this.monitorRuntime.notify(t,i.monitorUpdate),i.stopWatching&&this.stopWatching(t),i.autoRebidStatus&&this.handleAutoRebid(t,i.autoRebidStatus)}checkOutbidConditions(t,i,e){if("outbid"!==i.status)return!1;if("winning"===t.status&&"outbid"===i.status)return!0;if(e?.currentBid&&i.highestBid){const s=e.currentBid,n=i.highestBid,o=t.highestBid||"0";if(a(s)&&a(n)&&a(o))return r(n,o)&&r(n,s)&&"outbid"===i.status}return"outbid"!==t.status&&"outbid"===i.status}buildBidTransition(t,i,e,s,n,a){const o={type:"auction:bid-placed",payload:{auctionId:t,amount:e,status:i.status}};if("won"===i.status||"lost"===i.status){const a=this.buildTerminalTransition(t,i.status,s,{currentBid:e,highestBid:i.highestBid,bidCount:i.bidCount,admissionGrant:n});return{...a,events:[o,...a.events]}}return{trackedParticipation:{auctionId:t,enteredAt:a?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:i.status,currentBid:e,highestBid:i.highestBid,bidCount:i.bidCount,lastBidAt:/* @__PURE__ */(new Date).toISOString(),metadata:a?.metadata,fingerprint:a?.fingerprint},displayState:this.buildDisplayState(s,{status:i.status,highestBid:i.highestBid,currentBid:e,bidCount:i.bidCount}),events:[o,..."winning"===i.status?[{type:"auction:winning",payload:{auctionId:t,amount:e,highestBid:i.highestBid}}]:[],..."outbid"===i.status?[{type:"auction:outbid",payload:{auctionId:t,yourBid:e,highestBid:i.highestBid}}]:[]],monitorUpdate:null,stopWatching:!1,autoRebidStatus:null}}buildTerminalTransition(t,i,e,s){return"won"===i?{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:i,highestBid:s.highestBid,currentBid:s.currentBid,bidCount:s.bidCount}),events:[{type:"auction:won",payload:{auctionId:t,winningBid:s.currentBid??"",admissionGrant:s.admissionGrant}}],monitorUpdate:s.admissionGrant?{type:"granted",token:s.admissionGrant}:{type:"ended",outcome:{type:"won",distributionType:"auction",distributionId:t}},stopWatching:!0,autoRebidStatus:null}:{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:i,highestBid:s.highestBid,currentBid:s.currentBid,bidCount:s.bidCount}),events:[{type:"auction:lost",payload:{auctionId:t,highestBid:s.highestBid}}],monitorUpdate:{type:"ended",outcome:{type:"lost"}},stopWatching:!0,autoRebidStatus:null}}buildStatusTransition(t,i,e,{previousParticipation:s,previousStatus:n}){const a=i.currentBid??s?.currentBid,o=i.bidCount??s?.bidCount;if(b(n?.status))return{displayState:null,events:[],monitorUpdate:null,stopWatching:!1,autoRebidStatus:null};if("won"===i.status||"lost"===i.status)return this.buildTerminalTransition(t,i.status,e,{currentBid:a,highestBid:i.highestBid,bidCount:o,admissionGrant:i.admissionGrant});const r=e.closeAt?new Date(e.closeAt).getTime():void 0;if("not_bid"===i.status&&void 0!==r&&Date.now()>=r+this.NO_BID_END_GRACE_MS)return{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:"ended",highestBid:i.highestBid,currentBid:a,bidCount:o}),events:[],monitorUpdate:{type:"ended",outcome:{type:"closed",reason:"ended"}},stopWatching:!0,autoRebidStatus:null};const u={auctionId:t,enteredAt:s?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:i.status,currentBid:a,highestBid:i.highestBid,bidCount:o,metadata:s?.metadata,fingerprint:s?.fingerprint},d=[];let h=null;return n&&(this.checkOutbidConditions(n,i,s)&&(d.push({type:"auction:outbid",payload:{auctionId:t,yourBid:n.currentBid||s?.currentBid||"",highestBid:i.highestBid}}),h=i),"winning"!==n.status&&"winning"===i.status&&d.push({type:"auction:winning",payload:{auctionId:t,amount:a||"",highestBid:i.highestBid}}),n.highestBid!==i.highestBid&&"winning"!==i.status&&d.push({type:"auction:bid-updated",payload:{auctionId:t,highestBid:i.highestBid,bidCount:i.bidCount||0}})),{trackedParticipation:u,displayState:this.buildDisplayState(e,{status:i.status,highestBid:i.highestBid,currentBid:a,bidCount:o}),events:d,monitorUpdate:null,stopWatching:!1,autoRebidStatus:h}}buildMissingTransition(){return{trackedParticipation:null,displayState:{status:"lost"},events:[],monitorUpdate:{type:"ended",outcome:{type:"closed",reason:"not_participating"}},stopWatching:!0,autoRebidStatus:null}}async get(t){try{return await this.getAuctionDetailsCached(t)}catch(i){throw this.logger.error("Failed to get auction",{auctionId:t,error:i}),i}}async bid(e,n,o){try{const r=this.lifecycleGeneration;if(!a(n))throw s.validationError("Invalid bid amount format. Must be a numeric string (e.g., '100.00')");if(!this.store.session)throw s.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");let d=this.bumpOperationGeneration(e),h=this.getTrackedParticipation(e);h||(this.logger.warn("Attempting to bid without entering auction first",{auctionId:e}),await this.enter(e,void 0,o),h=this.getTrackedParticipation(e),d=this.bumpOperationGeneration(e)),this.logger.info("Placing bid",{auctionId:e,amount:n});const l=`/auctions/${e}/bid`,g=`/auctions/${e}/highest-bid`,[p,m,f]=await Promise.all([this.http.post(l,{amount:n},o),this.http.get(g),this.getAuctionDetailsCached(e)]),B=c(t,p,{endpoint:l},{throwOnFailure:!0});if(!B.ok)throw B.error;const S=c(i,m,{endpoint:g},{throwOnFailure:!0});if(!S.ok)throw S.error;const v=B.value,C=S.value,w=u(C.amount),A=y(v.status),I={status:A,amount:n,highestBid:w,bidCount:v.bidCount??(h?.bidCount||0)+1,admissionGrant:v.admissionGrant};if(!this.canApplyAsyncResult(e,r,void 0,d))return I;this.applyTransition(e,this.buildBidTransition(e,I,n,f,v.admissionGrant,h));const R=this.lastStatusCache.get(e);return b(R?.status)||this.lastStatusCache.set(e,{auctionId:e,status:A,currentBid:n,highestBid:w,timeRemaining:0,bidCount:I.bidCount,admissionGrant:v.admissionGrant}),I}catch(r){throw this.logger.error("Failed to place bid",{auctionId:e,amount:n,error:r}),this.events.emit("auction:error",{auctionId:e,error:r}),r}}async enter(t,i,e){try{const n=this.lifecycleGeneration,a=this.store.session;if(!a)throw s.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const o=this.bumpOperationGeneration(t);if(this.logger.info("Entering auction",{auctionId:t}),await this.http.post(`/auctions/${t}/enter`,{metadata:i},e),!this.canApplyAsyncResult(t,n,void 0,o))return;this.trackedParticipations.set(t,{auctionId:t,enteredAt:/* @__PURE__ */(new Date).toISOString(),status:"not_bid",metadata:i,fingerprint:a.deviceFingerprint}),this.lastStatusCache.delete(t),this.emitTransitionEvents([{type:"auction:entered",payload:{auctionId:t}}])}catch(n){throw this.logger.error("Failed to enter auction",{auctionId:t,error:n}),this.events.emit("auction:error",{auctionId:t,error:n}),n}}async leave(t,i){const e=this.getTrackedParticipation(t);if(e&&(void 0!==e.currentBid||"not_bid"!==e.status))throw s.cannotLeaveAfterBid(t);try{const e=this.lifecycleGeneration,s=this.bumpOperationGeneration(t);this.logger.info("Leaving auction",{auctionId:t});try{await this.http.post(`/auctions/${t}/leave`,void 0,i)}catch(n){if(404!==n?.status)throw n;this.logger.debug("Leave endpoint not available",{auctionId:t})}if(!this.canApplyAsyncResult(t,e,void 0,s))return;this.stopMonitoring(t),this.trackedParticipations.delete(t),this.emitTransitionEvents([{type:"auction:left",payload:{auctionId:t}}])}catch(n){throw this.logger.error("Failed to leave auction",{auctionId:t,error:n}),this.events.emit("auction:error",{auctionId:t,error:n}),n}}async status(t){const i=this.inFlightRequests.get(t);if(i)return this.logger.debug("Returning existing in-flight request",{auctionId:t}),i;const e=this.lifecycleGeneration,s=this.bumpOperationGeneration(t),n=this.doStatus(t,e,void 0,s).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,n),n}async doStatus(e,s,n,a){try{const o=`/auctions/${e}/status`,r=`/auctions/${e}/highest-bid`,[d,h,l]=await Promise.all([this.http.get(o),this.http.get(r),this.getAuctionDetailsCached(e)]),g=c(t,d,{endpoint:o},{throwOnFailure:!0});if(!g.ok)throw g.error;const p=c(i,h,{endpoint:r},{throwOnFailure:!0});if(!p.ok)throw p.error;const m=g.value,f=p.value,B=this.getAuctionSettleAt(l),S=B?new Date(B).getTime():Number.NaN,v=u(f.amount),C=this.getTrackedParticipation(e),w=m.lastBidAmount??m.winningBidAmount,A={auctionId:e,status:y(m.status),currentBid:C?.currentBid??(w?u(w):void 0),highestBid:v,timeRemaining:Number.isFinite(S)?Math.max(0,S-Date.now()):0,bidCount:m.bidCount,admissionGrant:m.admissionGrant};if(!this.canApplyAsyncResult(e,s,n,a))return A;const I=this.lastStatusCache.get(e),R=this.buildStatusTransition(e,A,l,{previousParticipation:C,previousStatus:I});return this.applyTransition(e,R),this.hasSuccessfulReadThisSession.set(e,!0),Number.isFinite(S)?this.lastSettleAtMs.set(e,S):this.lastSettleAtMs.delete(e),b(I?.status)?this.lastStatusCache.set(e,I):b(A.status)?this.lastStatusCache.set(e,A):R.stopWatching?this.lastStatusCache.delete(e):this.lastStatusCache.set(e,A),this.events.emit("auction:status-updated",{auctionId:e,status:A}),A}catch(o){if(o&&"object"==typeof o&&"status"in o&&404===o.status){const t={auctionId:e,status:"not_bid",highestBid:"0.00",timeRemaining:0};if(!this.canApplyAsyncResult(e,s,n,a))return t;const i=this.lastStatusCache.get(e);if(!b(i?.status)){const t=this.buildMissingTransition();this.applyTransition(e,t),this.lastStatusCache.delete(e)}return this.hasSuccessfulReadThisSession.set(e,!0),this.lastSettleAtMs.delete(e),this.events.emit("auction:status-updated",{auctionId:e,status:t}),t}if(!this.canApplyAsyncResult(e,s,n,a))return this.lastStatusCache.get(e)??{auctionId:e,status:"not_bid",highestBid:"0.00",timeRemaining:0};throw this.logger.error("Failed to get auction status",{auctionId:e,error:o}),this.events.emit("auction:error",{auctionId:e,error:o}),o}}async getBidHistory(t){try{const i=this.bidHistoryCache.get(t);if(i&&Date.now()-i.fetchedAt<this.BID_HISTORY_CACHE_TTL)return this.logger.debug("Returning cached bid history",{auctionId:t}),i.bids;this.logger.info("Fetching bid history",{auctionId:t});const s=`/auctions/${t}/bids/history`,n=await this.http.get(s),a=c(e,n,{endpoint:s},{throwOnFailure:!0});if(!a.ok)throw a.error;const o=a.value.map(t=>({amount:u(t.amount),timestamp:t.timestamp,isWinning:t.isHighest,isYours:!0}));return this.bidHistoryCache.set(t,{bids:o,fetchedAt:Date.now()}),this.events.emit("auction:history-fetched",{auctionId:t,bidCount:o.length}),o}catch(i){throw this.logger.error("Failed to get bid history",{auctionId:t,error:i}),this.events.emit("auction:error",{auctionId:t,error:i}),i}}startWatching(t,i=5e3,e){this.stopWatching(t);const s=this.lifecycleGeneration,n=this.bumpMonitorGeneration(t);this.hasSuccessfulReadThisSession.set(t,!1),this.lastStatusCache.delete(t),this.lastSettleAtMs.delete(t),this.logger.info("Starting auction status polling",{auctionId:t,intervalMs:i});const a=l({baseMs:i,suppressBackoff:()=>this.shouldSuppressBackoff(t),onDegradedChange:e,shouldContinue:()=>this.pollSchedulers.has(t)&&this.canApplyAsyncResult(t,s,n),run:async()=>{const i=this.getOperationGeneration(t);try{await this.doStatus(t,s,n,i)}catch(e){if(!this.canApplyAsyncResult(t,s,n,i))return;throw this.logger.error("Polling failed",{auctionId:t,error:e}),e}}});this.pollSchedulers.set(t,a),this.registerVisibilityPoke(t,a),a.start(),this.events.emit("auction:watching-started",{auctionId:t,intervalMs:i})}stopWatching(t){this.bumpMonitorGeneration(t);const i=this.pollSchedulers.get(t);i&&(i.stop(),this.pollSchedulers.delete(t),this.unregisterVisibilityPoke(t),this.lastStatusCache.delete(t),this.hasSuccessfulReadThisSession.delete(t),this.lastSettleAtMs.delete(t),this.logger.info("Stopped watching auction",{auctionId:t}),this.events.emit("auction:watching-stopped",{auctionId:t}))}stopAllWatching(){for(const t of Array.from(this.pollSchedulers.keys()))this.stopWatching(t)}shouldSuppressBackoff(t){return!this.hasSuccessfulReadThisSession.get(t)||this.isInSettlingWindow(t)}isInSettlingWindow(t){const i=this.lastSettleAtMs.get(t);return void 0!==i&&(Date.now()>=i-12e4&&!b(this.lastStatusCache.get(t)?.status))}registerVisibilityPoke(t,i){if("undefined"==typeof document)return;const e=()=>{"visible"===document.visibilityState&&this.isInSettlingWindow(t)&&i.poke()};document.addEventListener("visibilitychange",e),this.visibilityHandlers.set(t,e)}unregisterVisibilityPoke(t){if("undefined"==typeof document)return void this.visibilityHandlers.delete(t);const i=this.visibilityHandlers.get(t);i&&(document.removeEventListener("visibilitychange",i),this.visibilityHandlers.delete(t))}bumpMonitorGeneration(t){const i=(this.monitorGenerations.get(t)??0)+1;return this.monitorGenerations.set(t,i),i}getOperationGeneration(t){return this.operationGenerations.get(t)??0}bumpOperationGeneration(t){const i=this.getOperationGeneration(t)+1;return this.operationGenerations.set(t,i),i}canApplyAsyncResult(t,i,e,s){return!this.destroyed&&this.lifecycleGeneration===i&&((void 0===e||this.monitorGenerations.get(t)===e)&&(void 0===s||this.getOperationGeneration(t)===s))}enableAutoRebid(t,i,e){if(!a(i)||!a(e))throw s.validationError("Invalid monetary format. Must be numeric strings (e.g., '100.00')");if(d(i,"0")||d(e,"0"))throw s.validationError("Max bid and increment must be positive");if(r(e,i))throw s.validationError("Increment cannot be greater than max bid");const n=this.getTrackedParticipation(t);if(!n)throw s.validationError("Must enter auction before enabling auto-rebid");const o=u(n.currentBid),c=h(i,o);this.autoRebidConfigs.set(t,{enabled:!0,maxBid:i,increment:e,remainingBudget:c,rebidCount:0}),this.logger.info("Auto-rebid enabled",{auctionId:t,maxBid:i,increment:e,remainingBudget:c}),this.events.emit("auction:auto-rebid-enabled",{auctionId:t,maxBid:i,increment:e})}disableAutoRebid(t){const i=this.autoRebidConfigs.get(t);i&&(i.enabled=!1,this.logger.info("Auto-rebid disabled",{auctionId:t}),this.events.emit("auction:auto-rebid-disabled",{auctionId:t,finalRebidCount:i.rebidCount||0}))}getAutoRebidConfig(t){return this.autoRebidConfigs.get(t)}async handleAutoRebid(t,i){const e=this.autoRebidConfigs.get(t);if(e&&e.enabled)try{if(!a(i.highestBid)||!a(e.maxBid)||!a(e.increment))return void this.logger.error("Invalid auto-rebid amounts",{auctionId:t,config:e});const s=o(i.highestBid,e.increment);if(r(s,e.maxBid))return this.logger.info("Auto-rebid would exceed max bid, disabling",{auctionId:t,wouldBid:s,maxBid:e.maxBid}),this.disableAutoRebid(t),void this.events.emit("auction:auto-rebid-max-reached",{auctionId:t,maxBid:e.maxBid,wouldNeedToBid:s});this.logger.info("Placing auto-rebid",{auctionId:t,amount:s});const n=await this.bid(t,s);e.lastRebidAt=/* @__PURE__ */(new Date).toISOString(),e.rebidCount=(e.rebidCount||0)+1,e.remainingBudget=h(e.maxBid,s),this.events.emit("auction:auto-rebid-placed",{auctionId:t,amount:s,remainingBudget:e.remainingBudget,status:n.status})}catch(s){this.logger.error("Auto-rebid failed",{auctionId:t,error:s}),this.events.emit("auction:auto-rebid-failed",{auctionId:t,error:s})}}startMonitoring(t,i,e,s){b(this.lastStatusCache.get(t)?.status)||(this.monitorRuntime.start(t,i,e),this.trackedParticipations.has(t)||this.trackedParticipations.set(t,{auctionId:t,enteredAt:/* @__PURE__ */(new Date).toISOString(),status:"not_bid"}),this.startWatching(t,5e3,s))}stopMonitoring(t){this.stopWatching(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)||this.pollSchedulers.has(t)}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.stopAllWatching(),this.inFlightRequests.clear(),this.lastStatusCache.clear(),this.hasSuccessfulReadThisSession.clear(),this.lastSettleAtMs.clear(),this.visibilityHandlers.clear(),this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.bidHistoryCache.clear(),this.auctionDetailsCache.clear(),this.autoRebidConfigs.clear(),this.monitorRuntime.clear()}}export{f as AuctionManagementModule};
|
|
1
|
+
import{AuctionBidderStateResponseSchema as t,AuctionHighestBidResponseSchema as i,AuctionBidHistoryResponseSchema as e}from"@fanfare-io/fanfare-sdk-contracts/auction";import{createError as s}from"../core/errors.js";import{getLogger as n}from"../core/logger.js";import{isMoneyString as a,addMoney as o,isMoneyGreaterThan as r,normalizeMoney as u,isMoneyLessThanOrEqualTo as d,subtractMoney as h}from"../core/money.js";import{parseResponse as c}from"../core/parse-response.js";import{createPollScheduler as l}from"../core/poll-scheduler.js";import{DistributionMonitorRuntime as g}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as p}from"../state/events.js";import{getSDKStore as m}from"../state/store.js";import{isTerminalAuctionStatus as b,toAuctionConsumerStatus as y}from"./auction-status.js";class f{constructor(t){this.logger=n(),this.events=p(),this.trackedParticipations=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.pollSchedulers=/* @__PURE__ */new Map,this.lastStatusCache=/* @__PURE__ */new Map,this.hasSuccessfulReadThisSession=/* @__PURE__ */new Map,this.lastSettleAtMs=/* @__PURE__ */new Map,this.visibilityHandlers=/* @__PURE__ */new Map,this.autoRebidConfigs=/* @__PURE__ */new Map,this.monitorRuntime=new g,this.bidHistoryCache=/* @__PURE__ */new Map,this.auctionDetailsCache=/* @__PURE__ */new Map,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.BID_HISTORY_CACHE_TTL=5e3,this.AUCTION_DETAILS_CACHE_TTL=3e4,this.NO_BID_END_GRACE_MS=5e3,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return m()}computeMinNextBid(t,i){if(i&&a(t)&&a(i))return o(t,i)}async getAuctionDetailsCached(t){const i=this.auctionDetailsCache.get(t);if(i&&Date.now()-i.fetchedAt<this.AUCTION_DETAILS_CACHE_TTL)return i.details;const e=await this.http.get(`/auctions/${t}`);return this.auctionDetailsCache.set(t,{details:e,fetchedAt:Date.now()}),e}getTrackedParticipation(t){return this.trackedParticipations.get(t)}getAuctionCloseAt(t){return t.closeAt??t.settleAt}getAuctionSettleAt(t){return t.settleAt??t.closeAt}buildDisplayState(t,{status:i,highestBid:e,currentBid:s,bidCount:n}){return{highestBid:e,currentBid:s,bidCount:n,closeAt:this.getAuctionCloseAt(t),settleAt:t.settleAt,status:i,currencyCode:t.currencyCode,minNextBid:e?this.computeMinNextBid(e,t.minBidIncrement):void 0,bidIncrement:t.minBidIncrement??void 0}}emitTransitionEvents(t){for(const i of t)this.events.emit(i.type,i.payload)}applyTransition(t,i){void 0!==i.trackedParticipation&&(i.trackedParticipation?this.trackedParticipations.set(t,i.trackedParticipation):this.trackedParticipations.delete(t));const e=this.monitorRuntime.getDisplayAtom(t);e&&i.displayState&&e.set({type:"auction",...i.displayState}),this.emitTransitionEvents(i.events),i.monitorUpdate&&this.monitorRuntime.notify(t,i.monitorUpdate),i.stopWatching&&this.stopWatching(t),i.autoRebidStatus&&this.handleAutoRebid(t,i.autoRebidStatus)}checkOutbidConditions(t,i,e){if("outbid"!==i.status)return!1;if("winning"===t.status&&"outbid"===i.status)return!0;if(e?.currentBid&&i.highestBid){const s=e.currentBid,n=i.highestBid,o=t.highestBid||"0";if(a(s)&&a(n)&&a(o))return r(n,o)&&r(n,s)&&"outbid"===i.status}return"outbid"!==t.status&&"outbid"===i.status}buildBidTransition(t,i,e,s,n,a){const o={type:"auction:bid-placed",payload:{auctionId:t,amount:e,status:i.status}};if("won"===i.status||"lost"===i.status){const a=this.buildTerminalTransition(t,i.status,s,{currentBid:e,highestBid:i.highestBid,bidCount:i.bidCount,admissionGrant:n});return{...a,events:[o,...a.events]}}return{trackedParticipation:{auctionId:t,enteredAt:a?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:i.status,currentBid:e,highestBid:i.highestBid,bidCount:i.bidCount,lastBidAt:/* @__PURE__ */(new Date).toISOString(),metadata:a?.metadata,fingerprint:a?.fingerprint},displayState:this.buildDisplayState(s,{status:i.status,highestBid:i.highestBid,currentBid:e,bidCount:i.bidCount}),events:[o,..."winning"===i.status?[{type:"auction:winning",payload:{auctionId:t,amount:e,highestBid:i.highestBid}}]:[],..."outbid"===i.status?[{type:"auction:outbid",payload:{auctionId:t,yourBid:e,highestBid:i.highestBid}}]:[]],monitorUpdate:null,stopWatching:!1,autoRebidStatus:null}}buildTerminalTransition(t,i,e,s){return"won"===i?{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:i,highestBid:s.highestBid,currentBid:s.currentBid,bidCount:s.bidCount}),events:[{type:"auction:won",payload:{auctionId:t,winningBid:s.currentBid??"",admissionGrant:s.admissionGrant}}],monitorUpdate:s.admissionGrant?{type:"granted",token:s.admissionGrant}:{type:"ended",outcome:{type:"won",distributionType:"auction",distributionId:t}},stopWatching:!0,autoRebidStatus:null}:{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:i,highestBid:s.highestBid,currentBid:s.currentBid,bidCount:s.bidCount}),events:[{type:"auction:lost",payload:{auctionId:t,highestBid:s.highestBid}}],monitorUpdate:{type:"ended",outcome:{type:"lost"}},stopWatching:!0,autoRebidStatus:null}}buildStatusTransition(t,i,e,{previousParticipation:s,previousStatus:n}){const a=i.currentBid??s?.currentBid,o=i.bidCount??s?.bidCount;if(b(n?.status))return{displayState:null,events:[],monitorUpdate:null,stopWatching:!1,autoRebidStatus:null};if("won"===i.status||"lost"===i.status)return this.buildTerminalTransition(t,i.status,e,{currentBid:a,highestBid:i.highestBid,bidCount:o,admissionGrant:i.admissionGrant});const r=e.closeAt?new Date(e.closeAt).getTime():void 0;if("not_bid"===i.status&&void 0!==r&&Date.now()>=r+this.NO_BID_END_GRACE_MS)return{trackedParticipation:null,displayState:this.buildDisplayState(e,{status:"ended",highestBid:i.highestBid,currentBid:a,bidCount:o}),events:[],monitorUpdate:{type:"ended",outcome:{type:"closed",reason:"ended"}},stopWatching:!0,autoRebidStatus:null};const u={auctionId:t,enteredAt:s?.enteredAt??/* @__PURE__ */(new Date).toISOString(),status:i.status,currentBid:a,highestBid:i.highestBid,bidCount:o,metadata:s?.metadata,fingerprint:s?.fingerprint},d=[];let h=null;return n&&(this.checkOutbidConditions(n,i,s)&&(d.push({type:"auction:outbid",payload:{auctionId:t,yourBid:n.currentBid||s?.currentBid||"",highestBid:i.highestBid}}),h=i),"winning"!==n.status&&"winning"===i.status&&d.push({type:"auction:winning",payload:{auctionId:t,amount:a||"",highestBid:i.highestBid}}),n.highestBid!==i.highestBid&&"winning"!==i.status&&d.push({type:"auction:bid-updated",payload:{auctionId:t,highestBid:i.highestBid,bidCount:i.bidCount||0}})),{trackedParticipation:u,displayState:this.buildDisplayState(e,{status:i.status,highestBid:i.highestBid,currentBid:a,bidCount:o}),events:d,monitorUpdate:null,stopWatching:!1,autoRebidStatus:h}}buildMissingTransition(){return{trackedParticipation:null,displayState:{status:"lost"},events:[],monitorUpdate:{type:"ended",outcome:{type:"closed",reason:"not_participating"}},stopWatching:!0,autoRebidStatus:null}}async get(t){try{return await this.getAuctionDetailsCached(t)}catch(i){throw this.logger.error("Failed to get auction",{auctionId:t,error:i}),i}}async bid(e,n,o){try{const r=this.lifecycleGeneration;if(!a(n))throw s.validationError("Invalid bid amount format. Must be a numeric string (e.g., '100.00')");if(!this.store.session)throw s.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");let d=this.bumpOperationGeneration(e),h=this.getTrackedParticipation(e);h||(this.logger.warn("Attempting to bid without entering auction first",{auctionId:e}),await this.enter(e,void 0,o),h=this.getTrackedParticipation(e),d=this.bumpOperationGeneration(e)),this.logger.info("Placing bid",{auctionId:e,amount:n});const l=`/auctions/${e}/bid`,g=`/auctions/${e}/highest-bid`,[p,m,f]=await Promise.all([this.http.post(l,{amount:n},o),this.http.get(g),this.getAuctionDetailsCached(e)]),B=c(t,p,{endpoint:l},{throwOnFailure:!0});if(!B.ok)throw B.error;const S=c(i,m,{endpoint:g},{throwOnFailure:!0});if(!S.ok)throw S.error;const v=B.value,C=S.value,w=u(C.amount),A=y(v.status),I={status:A,amount:n,highestBid:w,bidCount:v.bidCount??(h?.bidCount||0)+1,admissionGrant:v.admissionGrant};if(!this.canApplyAsyncResult(e,r,void 0,d))return I;this.applyTransition(e,this.buildBidTransition(e,I,n,f,v.admissionGrant,h));const R=this.lastStatusCache.get(e);return b(R?.status)||this.lastStatusCache.set(e,{auctionId:e,status:A,currentBid:n,highestBid:w,timeRemaining:0,bidCount:I.bidCount,admissionGrant:v.admissionGrant}),I}catch(r){throw this.logger.error("Failed to place bid",{auctionId:e,amount:n,error:r}),this.events.emit("auction:error",{auctionId:e,error:r}),r}}async enter(t,i,e){try{const n=this.lifecycleGeneration,a=this.store.session;if(!a)throw s.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const o=this.bumpOperationGeneration(t);if(this.logger.info("Entering auction",{auctionId:t}),await this.http.post(`/auctions/${t}/enter`,{metadata:i},e),!this.canApplyAsyncResult(t,n,void 0,o))return;this.trackedParticipations.set(t,{auctionId:t,enteredAt:/* @__PURE__ */(new Date).toISOString(),status:"not_bid",metadata:i,fingerprint:a.deviceFingerprint}),this.lastStatusCache.delete(t),this.emitTransitionEvents([{type:"auction:entered",payload:{auctionId:t}},{type:"experience:distribution-entered",payload:{distributionType:"auction",distributionId:t}}])}catch(n){throw this.logger.error("Failed to enter auction",{auctionId:t,error:n}),this.events.emit("auction:error",{auctionId:t,error:n}),n}}async leave(t,i){const e=this.getTrackedParticipation(t);if(e&&(void 0!==e.currentBid||"not_bid"!==e.status))throw s.cannotLeaveAfterBid(t);try{const e=this.lifecycleGeneration,s=this.bumpOperationGeneration(t);this.logger.info("Leaving auction",{auctionId:t});try{await this.http.post(`/auctions/${t}/leave`,void 0,i)}catch(n){if(404!==n?.status)throw n;this.logger.debug("Leave endpoint not available",{auctionId:t})}if(!this.canApplyAsyncResult(t,e,void 0,s))return;this.stopMonitoring(t),this.trackedParticipations.delete(t),this.emitTransitionEvents([{type:"auction:left",payload:{auctionId:t}}])}catch(n){throw this.logger.error("Failed to leave auction",{auctionId:t,error:n}),this.events.emit("auction:error",{auctionId:t,error:n}),n}}async status(t){const i=this.inFlightRequests.get(t);if(i)return this.logger.debug("Returning existing in-flight request",{auctionId:t}),i;const e=this.lifecycleGeneration,s=this.bumpOperationGeneration(t),n=this.doStatus(t,e,void 0,s).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,n),n}async doStatus(e,s,n,a){try{const o=`/auctions/${e}/status`,r=`/auctions/${e}/highest-bid`,[d,h,l]=await Promise.all([this.http.get(o),this.http.get(r),this.getAuctionDetailsCached(e)]),g=c(t,d,{endpoint:o},{throwOnFailure:!0});if(!g.ok)throw g.error;const p=c(i,h,{endpoint:r},{throwOnFailure:!0});if(!p.ok)throw p.error;const m=g.value,f=p.value,B=this.getAuctionSettleAt(l),S=B?new Date(B).getTime():Number.NaN,v=u(f.amount),C=this.getTrackedParticipation(e),w=m.lastBidAmount??m.winningBidAmount,A={auctionId:e,status:y(m.status),currentBid:C?.currentBid??(w?u(w):void 0),highestBid:v,timeRemaining:Number.isFinite(S)?Math.max(0,S-Date.now()):0,bidCount:m.bidCount,admissionGrant:m.admissionGrant};if(!this.canApplyAsyncResult(e,s,n,a))return A;const I=this.lastStatusCache.get(e),R=this.buildStatusTransition(e,A,l,{previousParticipation:C,previousStatus:I});return this.applyTransition(e,R),this.hasSuccessfulReadThisSession.set(e,!0),Number.isFinite(S)?this.lastSettleAtMs.set(e,S):this.lastSettleAtMs.delete(e),b(I?.status)?this.lastStatusCache.set(e,I):b(A.status)?this.lastStatusCache.set(e,A):R.stopWatching?this.lastStatusCache.delete(e):this.lastStatusCache.set(e,A),this.events.emit("auction:status-updated",{auctionId:e,status:A}),A}catch(o){if(o&&"object"==typeof o&&"status"in o&&404===o.status){const t={auctionId:e,status:"not_bid",highestBid:"0.00",timeRemaining:0};if(!this.canApplyAsyncResult(e,s,n,a))return t;const i=this.lastStatusCache.get(e);if(!b(i?.status)){const t=this.buildMissingTransition();this.applyTransition(e,t),this.lastStatusCache.delete(e)}return this.hasSuccessfulReadThisSession.set(e,!0),this.lastSettleAtMs.delete(e),this.events.emit("auction:status-updated",{auctionId:e,status:t}),t}if(!this.canApplyAsyncResult(e,s,n,a))return this.lastStatusCache.get(e)??{auctionId:e,status:"not_bid",highestBid:"0.00",timeRemaining:0};throw this.logger.error("Failed to get auction status",{auctionId:e,error:o}),this.events.emit("auction:error",{auctionId:e,error:o}),o}}async getBidHistory(t){try{const i=this.bidHistoryCache.get(t);if(i&&Date.now()-i.fetchedAt<this.BID_HISTORY_CACHE_TTL)return this.logger.debug("Returning cached bid history",{auctionId:t}),i.bids;this.logger.info("Fetching bid history",{auctionId:t});const s=`/auctions/${t}/bids/history`,n=await this.http.get(s),a=c(e,n,{endpoint:s},{throwOnFailure:!0});if(!a.ok)throw a.error;const o=a.value.map(t=>({amount:u(t.amount),timestamp:t.timestamp,isWinning:t.isHighest,isYours:!0}));return this.bidHistoryCache.set(t,{bids:o,fetchedAt:Date.now()}),this.events.emit("auction:history-fetched",{auctionId:t,bidCount:o.length}),o}catch(i){throw this.logger.error("Failed to get bid history",{auctionId:t,error:i}),this.events.emit("auction:error",{auctionId:t,error:i}),i}}startWatching(t,i=5e3,e){this.stopWatching(t);const s=this.lifecycleGeneration,n=this.bumpMonitorGeneration(t);this.hasSuccessfulReadThisSession.set(t,!1),this.lastStatusCache.delete(t),this.lastSettleAtMs.delete(t),this.logger.info("Starting auction status polling",{auctionId:t,intervalMs:i});const a=l({baseMs:i,suppressBackoff:()=>this.shouldSuppressBackoff(t),onDegradedChange:e,shouldContinue:()=>this.pollSchedulers.has(t)&&this.canApplyAsyncResult(t,s,n),run:async()=>{const i=this.getOperationGeneration(t);try{await this.doStatus(t,s,n,i)}catch(e){if(!this.canApplyAsyncResult(t,s,n,i))return;throw this.logger.error("Polling failed",{auctionId:t,error:e}),e}}});this.pollSchedulers.set(t,a),this.registerVisibilityPoke(t,a),a.start(),this.events.emit("auction:watching-started",{auctionId:t,intervalMs:i})}stopWatching(t){this.bumpMonitorGeneration(t);const i=this.pollSchedulers.get(t);i&&(i.stop(),this.pollSchedulers.delete(t),this.unregisterVisibilityPoke(t),this.lastStatusCache.delete(t),this.hasSuccessfulReadThisSession.delete(t),this.lastSettleAtMs.delete(t),this.logger.info("Stopped watching auction",{auctionId:t}),this.events.emit("auction:watching-stopped",{auctionId:t}))}stopAllWatching(){for(const t of Array.from(this.pollSchedulers.keys()))this.stopWatching(t)}shouldSuppressBackoff(t){return!this.hasSuccessfulReadThisSession.get(t)||this.isInSettlingWindow(t)}isInSettlingWindow(t){const i=this.lastSettleAtMs.get(t);return void 0!==i&&(Date.now()>=i-12e4&&!b(this.lastStatusCache.get(t)?.status))}registerVisibilityPoke(t,i){if("undefined"==typeof document)return;const e=()=>{"visible"===document.visibilityState&&this.isInSettlingWindow(t)&&i.poke()};document.addEventListener("visibilitychange",e),this.visibilityHandlers.set(t,e)}unregisterVisibilityPoke(t){if("undefined"==typeof document)return void this.visibilityHandlers.delete(t);const i=this.visibilityHandlers.get(t);i&&(document.removeEventListener("visibilitychange",i),this.visibilityHandlers.delete(t))}bumpMonitorGeneration(t){const i=(this.monitorGenerations.get(t)??0)+1;return this.monitorGenerations.set(t,i),i}getOperationGeneration(t){return this.operationGenerations.get(t)??0}bumpOperationGeneration(t){const i=this.getOperationGeneration(t)+1;return this.operationGenerations.set(t,i),i}canApplyAsyncResult(t,i,e,s){return!this.destroyed&&this.lifecycleGeneration===i&&((void 0===e||this.monitorGenerations.get(t)===e)&&(void 0===s||this.getOperationGeneration(t)===s))}enableAutoRebid(t,i,e){if(!a(i)||!a(e))throw s.validationError("Invalid monetary format. Must be numeric strings (e.g., '100.00')");if(d(i,"0")||d(e,"0"))throw s.validationError("Max bid and increment must be positive");if(r(e,i))throw s.validationError("Increment cannot be greater than max bid");const n=this.getTrackedParticipation(t);if(!n)throw s.validationError("Must enter auction before enabling auto-rebid");const o=u(n.currentBid),c=h(i,o);this.autoRebidConfigs.set(t,{enabled:!0,maxBid:i,increment:e,remainingBudget:c,rebidCount:0}),this.logger.info("Auto-rebid enabled",{auctionId:t,maxBid:i,increment:e,remainingBudget:c}),this.events.emit("auction:auto-rebid-enabled",{auctionId:t,maxBid:i,increment:e})}disableAutoRebid(t){const i=this.autoRebidConfigs.get(t);i&&(i.enabled=!1,this.logger.info("Auto-rebid disabled",{auctionId:t}),this.events.emit("auction:auto-rebid-disabled",{auctionId:t,finalRebidCount:i.rebidCount||0}))}getAutoRebidConfig(t){return this.autoRebidConfigs.get(t)}async handleAutoRebid(t,i){const e=this.autoRebidConfigs.get(t);if(e&&e.enabled)try{if(!a(i.highestBid)||!a(e.maxBid)||!a(e.increment))return void this.logger.error("Invalid auto-rebid amounts",{auctionId:t,config:e});const s=o(i.highestBid,e.increment);if(r(s,e.maxBid))return this.logger.info("Auto-rebid would exceed max bid, disabling",{auctionId:t,wouldBid:s,maxBid:e.maxBid}),this.disableAutoRebid(t),void this.events.emit("auction:auto-rebid-max-reached",{auctionId:t,maxBid:e.maxBid,wouldNeedToBid:s});this.logger.info("Placing auto-rebid",{auctionId:t,amount:s});const n=await this.bid(t,s);e.lastRebidAt=/* @__PURE__ */(new Date).toISOString(),e.rebidCount=(e.rebidCount||0)+1,e.remainingBudget=h(e.maxBid,s),this.events.emit("auction:auto-rebid-placed",{auctionId:t,amount:s,remainingBudget:e.remainingBudget,status:n.status})}catch(s){this.logger.error("Auto-rebid failed",{auctionId:t,error:s}),this.events.emit("auction:auto-rebid-failed",{auctionId:t,error:s})}}startMonitoring(t,i,e,s){b(this.lastStatusCache.get(t)?.status)||(this.monitorRuntime.start(t,i,e),this.trackedParticipations.has(t)||this.trackedParticipations.set(t,{auctionId:t,enteredAt:/* @__PURE__ */(new Date).toISOString(),status:"not_bid"}),this.startWatching(t,5e3,s))}stopMonitoring(t){this.stopWatching(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)||this.pollSchedulers.has(t)}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.stopAllWatching(),this.inFlightRequests.clear(),this.lastStatusCache.clear(),this.hasSuccessfulReadThisSession.clear(),this.lastSettleAtMs.clear(),this.visibilityHandlers.clear(),this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.bidHistoryCache.clear(),this.auctionDetailsCache.clear(),this.autoRebidConfigs.clear(),this.monitorRuntime.clear()}}export{f as AuctionManagementModule};
|
|
@@ -29,6 +29,8 @@ export interface AuctionSequenceConsumerState {
|
|
|
29
29
|
/** Consumer's current bid as a decimal money string. */
|
|
30
30
|
currentBid?: string;
|
|
31
31
|
}
|
|
32
|
+
/** True while a bidder still awaits a terminal auction outcome. */
|
|
33
|
+
export declare function isUnresolvedAuctionConsumer(consumer: AuctionSequenceConsumerState): boolean;
|
|
32
34
|
/**
|
|
33
35
|
* An auction sequence as seen by one consumer.
|
|
34
36
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function t(t){return"bidding"===t.status||"winning"===t.status||"outbid"===t.status}export{t as isUnresolvedAuctionConsumer};
|
package/dist/core/client.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{AppointmentManagementModule as e}from"../appointments/appointment.module.js";import{AuctionManagementModule as t}from"../auctions/auction.module.js";import{AuthenticationModule as r}from"../auth/auth.module.js";import{BeaconTrackingModule as n}from"../beacon/beacon.module.js";import{ChallengeManagementModule as i}from"../challenges/challenge.module.js";import{Config as o}from"../config/index.js";import{DrawManagementModule as s}from"../draws/draw.module.js";import{ExperienceManagementModule as a}from"../experiences/experience.module.js";import{ExperienceJourney as d}from"../experiences/journey.js";import{
|
|
2
|
-
/* @__PURE__ */new Set([...
|
|
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{PaymentMethodManagementModule as l}from"../payment/payment.module.js";import{ReservationManagementModule as u}from"../payment/reservations.module.js";import{QueueManagementModule as c}from"../queues/queue.module.js";import{resolveCapabilityOptions as g}from"../state/capability-token-registry.js";import{getEventBus as y}from"../state/events.js";import{getSDKStore as m}from"../state/store.js";import{CrossTabCoordinator as p}from"../sync/cross-tab-coordinator.js";import{TimedReleaseManagementModule as f}from"../timed-releases/timed-release.module.js";import{version as b}from"../version.js";import{WaitlistManagementModule as w}from"../waitlists/waitlist.module.js";import{FanfareError as h,ErrorCodes as v}from"./errors.js";import{createHttpClient as I}from"./http.js";import{createLogger as M}from"./logger.js";let S=null;async function j(j){const A=new o(j),k=A.get(),x=M({enabled:k.debug||!1,level:k.debug?"debug":"warn",prefix:"Fanfare"});if(x.info("Initializing Fanfare SDK",{organizationId:j.organizationId,environment:k.environment}),S)throw new h("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.",v.ALREADY_INITIALIZED);const D=/* @__PURE__ */Symbol("fanfare-sdk"),E=I({baseUrl:A.apiUrl,credentials:k.credentials,headers:{"X-Organization-Id":j.organizationId,"X-Publishable-Key":j.publishableKey,"X-Fanfare-API-Version":"2025-01-15","X-Fanfare-SDK-Version":b,"X-Fanfare-Client-Type":"browser"},timeout:3e4,retryConfig:{maxRetries:3,delayTimer:1e3,retryOnNetworkError:!0}},k),q=I({baseUrl:A.beaconUrl,credentials:"omit",headers:{"X-Organization-Id":j.organizationId,"X-Publishable-Key":j.publishableKey,"X-Fanfare-API-Version":"2025-01-15","X-Fanfare-SDK-Version":b,"X-Fanfare-Client-Type":"browser"},timeout:3e4,retryConfig:{maxRetries:3,delayTimer:1e3,retryOnNetworkError:!0}},k),R=new r(E,q),C=new i(E),z=new c(E),O=new s(E),F=new t(E),K=new e(E),X=new w(E),P=new f(E),T=new a(E,z,O,F,X,P),$=new l(E),J=new u(E),N=y(),V=m();N.on("auth:logout",()=>$.destroy());const B=/* @__PURE__ */new Map,U=/* @__PURE__ */new Map,L=new n(q,k.beacon||{},e=>{const t=U.get(e)?.snapshot$.get();return"routed"===t?.journeyStage?t.sequenceId:void 0}),G=/* @__PURE__ */new Map;let H,_;const Y=e=>{const t=U.get(e);if(t)return x.debug("Returning existing journey handle",{experienceId:e}),t;x.info("Creating new journey",{experienceId:e});const r=new d(e,H);B.set(e,r),_?.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(),_?.unregisterJourney(e),B.delete(e),U.delete(e)}};return U.set(e,n),n},Z=async e=>{const t=e??await T.getMe(),r=/* @__PURE__ */new Set,n=new Set(t.active.journeys.map(e=>e.experienceId));for(const i of n)Y(i);for(const i of n){const e=B.get(i);e&&await e.resumeFromMe(t)&&r.add(i)}return[...r]},Q=async e=>{const t=/* @__PURE__ */new Set;for(const r of Object.keys(V.activeJourneys))Y(r),t.add(r);if(!e&&!R.getSession())return[...t];for(const r of await Z(e))t.add(r);return[...t]},W={check:()=>R.check(),guest:()=>R.guest(),requestOtp:e=>R.requestOtp(e),verifyOtp:e=>R.verifyOtp(e),exchangeExternal:e=>R.exchangeExternal(e),login:e=>R.login(e),logout:()=>R.logout(),getSession:()=>R.getSession(),refresh:()=>R.refresh()},ee={initiate:e=>C.initiate(e),verify:e=>C.verify(e)},te={get:e=>Y(e),list:()=>Array.from(
|
|
2
|
+
/* @__PURE__ */new Set([...U.keys(),...B.keys(),...Object.keys(V.activeJourneys)])),resumeAll:e=>Q(e)},re={bind:e=>L.bind(e),track:e=>L.track(e),trackBatch:e=>L.trackBatch(e),flush:()=>L.flush()},ne=(e,t)=>{if(G.has(e.id))x.warn("CDP adapter already registered, skipping",{adapterId:e.id});else{try{e.init(N,t)}catch(r){return void x.error("CDP adapter failed to initialize",{adapterId:e.id,error:r})}G.set(e.id,e),x.info("CDP adapter registered",{adapterId:e.id})}},ie=(e,t)=>N.on(e,t),oe=async()=>{x.info("Destroying SDK instance");for(const t of G.values())try{t.destroy()}catch(e){x.error("CDP adapter failed to destroy",{adapterId:t.id,error:e})}G.clear();for(const t of Array.from(U.values()))try{t.destroy()}catch(e){x.error("Journey handle failed to destroy",{error:e})}B.clear(),U.clear(),"destroy"in R&&"function"==typeof R.destroy&&R.destroy(),z.destroy(),O.destroy(),F.destroy(),K.destroy(),X.destroy(),P.destroy(),T.destroy(),L.destroy(),$.destroy(),_?.close(),N.off(),S===D&&(S=null)};H={auth:W,challenges:ee,journeys:te,queues:{get:e=>z.get(e),enter:(e,t,r,n)=>z.enter(e,t,g({distributionId:e},r),n),leave:(e,t)=>z.leave(e,g({distributionId:e},t)),status:e=>z.status(e),startMonitoring:(e,t,r)=>z.startMonitoring(e,t,r),stopMonitoring:e=>z.stopMonitoring(e),isMonitoring:e=>z.isMonitoring(e)},draws:{get:e=>O.get(e),enter:(e,t,r,n)=>O.enter(e,t,g({distributionId:e},r),n),leave:(e,t)=>O.leave(e,g({distributionId:e},t)),status:e=>O.status(e),startMonitoring:(e,t,r)=>O.startMonitoring(e,t,r),stopMonitoring:e=>O.stopMonitoring(e),isMonitoring:e=>O.isMonitoring(e)},auctions:{get:e=>F.get(e),bid:(e,t,r)=>F.bid(e,t,g({distributionId:e},r)),enter:(e,t,r)=>F.enter(e,t,g({distributionId:e},r)),leave:(e,t)=>F.leave(e,g({distributionId:e},t)),status:e=>F.status(e),getBidHistory:e=>F.getBidHistory(e),enableAutoRebid:(e,t,r)=>F.enableAutoRebid(e,t,r),disableAutoRebid:e=>F.disableAutoRebid(e),getAutoRebidConfig:e=>F.getAutoRebidConfig(e),startMonitoring:(e,t,r)=>F.startMonitoring(e,t,r),stopMonitoring:e=>F.stopMonitoring(e),isMonitoring:e=>F.isMonitoring(e),destroy:()=>F.destroy()},experiences:{get:e=>T.get(e),enter:e=>T.enter(e),leave:e=>T.leave(e),getMe:()=>T.getMe(),findSequence:(e,t)=>T.findSequence(e,t),validateSequenceAccess:(e,t)=>T.validateSequenceAccess(e,t),selectSequence:e=>T.selectSequence(e),getCurrentDistributions:(e,t)=>T.getCurrentDistributions(e,t),enterDistribution:e=>{if(!e)throw new h("Distribution is required",v.VALIDATION_ERROR);return T.enterDistribution(e)},getActiveSession:()=>T.getActiveSession(),isInExperience:e=>T.isInExperience(e),getSelectedSequence:()=>T.getSelectedSequence(),createJourney:e=>Y(e),resumeJourneysFromMe:e=>Z(e),destroy:()=>T.destroy()},waitlists:{enter:(e,t)=>X.enter(e,g({waitlistId:e},t)),leave:(e,t)=>X.leave(e,g({waitlistId:e},t)),getStatus:e=>X.getStatus(e),destroy:()=>X.destroy()},reservations:J,timedReleases:{get:e=>P.get(e),enter:(e,t,r)=>P.enter(e,t,g({distributionId:e},r)),leave:(e,t)=>P.leave(e,g({distributionId:e},t)),complete:e=>P.complete(e),status:e=>P.status(e),startMonitoring:(e,t,r)=>P.startMonitoring(e,t,r),stopMonitoring:e=>P.stopMonitoring(e),isMonitoring:e=>P.isMonitoring(e)},appointments:{get:(e,t)=>K.get(e,g({distributionId:e},t)),getSlots:(e,t,r)=>K.getSlots(e,t,g({distributionId:e},r)),getMe:e=>K.getMe(e),book:(e,t,r,n)=>K.book(e,t,r,g({distributionId:e},n)),cancel:(e,t,r,n,i)=>K.cancel(e,t,r,n,g({distributionId:e},i)),reschedule:(e,t,r,n)=>K.reschedule(e,t,r,g({distributionId:e},n)),startMonitoring:(e,t,r)=>K.startMonitoring(e,t,r),stopMonitoring:e=>K.stopMonitoring(e),isMonitoring:e=>K.isMonitoring(e),bumpMonitorGeneration:e=>K.bumpMonitorGeneration(e),destroy:()=>K.destroy()},beacon:re,payment:$,use:ne,on:ie,destroy:oe};const se=!1!==j.sync&&"object"==typeof j.sync?j.sync:{};_=new p({enabled:!1!==j.sync&&!1!==se.enabled,channelName:se.channelName}),x.info("Inter-tab sync initialized",{tabId:_.getTabId(),enabled:_.isEnabled()});const ae={auth:W,challenges:ee,journeys:te,beacon:re,appointments:H.appointments,payment:$,use:ne,on:ie,destroy:oe};if(await R.rehydrate(),k.autoResume&&R.getSession())try{await Q()}catch(de){x.warn("Auto-resume on init failed; journeys can be resumed manually",{error:de instanceof Error?de.message:String(de)})}return S=D,x.info("SDK initialized successfully"),ae}export{j as init};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{classify as
|
|
1
|
+
import{classify as r}from"./error-disposition.js";import{FanfareError as e,ErrorCodes as o}from"./errors.js";const t={[o.OTP_INVALID]:"auth.invalidCode",[o.OTP_EXPIRED]:"auth.codeExpired",[o.INVALID_SESSION]:"common.sessionExpired",[o.INVALID_REFRESH_TOKEN]:"common.sessionExpired",[o.SERVICE_UNAVAILABLE]:"error.overloadedGeneric",[o.TIMEOUT]:"error.overloadedGeneric",[o.NETWORK_ERROR]:"error.overloadedGeneric",[o.FEATURE_DISABLED]:"error.featureDisabled",[o.NO_ACCESS]:"widget.noAccess.message",[o.FORBIDDEN]:"error.forbidden",[o.DISTRIBUTION_NOT_OPEN]:"error.notOpen",[o.ALREADY_BOOKED]:"appointment.error.alreadyBooked",[o.ACTIVE_BOOKING_EXISTS]:"appointment.error.activeBookingExists",[o.SLOT_NOT_AVAILABLE]:"appointment.error.slotNotAvailable",[o.SLOT_EXPIRED]:"appointment.error.slotExpired",[o.SLOT_OUT_OF_WINDOW]:"appointment.error.slotOutOfWindow",[o.CANNOT_CANCEL_ONGOING_OR_PAST]:"appointment.error.cannotCancel",[o.NEW_SLOT_FULL]:"appointment.error.newSlotFull",[o.NO_EXISTING_BOOKING]:"appointment.error.noExistingBooking",[o.INVALID_SLOT]:"appointment.error.invalidSlot"};function i(r){return"INLINE"===r||"STEP_RESET"===r?"error.inlineGeneric":"error.description"}function n(e,n){const{disposition:s}=r(e.code,e.status,e.action),d="number"==typeof e.retryAfter&&Number.isFinite(e.retryAfter)?e.retryAfter:void 0,E=function(r,e,n){return r===o.RATE_LIMITED?n?"error.rateLimited":"error.rateLimitedGeneric":r===o.OVERLOADED?n?"error.overloaded":"error.overloadedGeneric":t[r]??i(e)}(e.code,s,void 0!==d);let a=n(E,void 0!==d?{values:{seconds:d}}:void 0);a.includes("{{")&&(a=n(i(s)));const c="PANEL"===s?n("error.title"):void 0;return{code:e.code,title:c,description:a,disposition:s,retryAfter:e.retryAfter,support:e.support}}function s(r){if(r instanceof e)return r;const t=r instanceof Error?r.message:String(r);return new e(t,o.INTERNAL_ERROR)}function d(r){return{code:r.code,status:r.status,requestId:r.requestId,correlationId:r.correlationId,details:r.details,issues:r.issues}}export{s as ensureFanfareError,d as toDebug,n as toDisplay};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ErrorCodes as E}from"./errors.js";const I={[E.OTP_INVALID]:"INLINE",[E.INVALID_CREDENTIALS]:"INLINE",[E.VALIDATION_ERROR]:"INLINE",[E.RATE_LIMITED]:"INLINE",[E.SLOT_OUT_OF_WINDOW]:"INLINE",[E.OVERLOADED]:"INLINE",[E.SERVICE_UNAVAILABLE]:"INLINE",[E.TIMEOUT]:"INLINE",[E.NETWORK_ERROR]:"INLINE",[E.FEATURE_DISABLED]:"INLINE",[E.OTP_EXPIRED]:"STEP_RESET",[E.BOT_CHALLENGE_INVALID]:"STEP_RESET",[E.BOT_CHALLENGE_EXPIRED]:"STEP_RESET",[E.SLOT_EXPIRED]:"STEP_RESET",[E.SLOT_NOT_AVAILABLE]:"STEP_RESET",[E.INVALID_SESSION]:"RESTART",[E.INVALID_REFRESH_TOKEN]:"RESTART",[E.UNAUTHORIZED]:"RESTART",[E.ENTRY_TOKEN_REQUIRED]:"RESTART",[E.ENTRY_TOKEN_INVALID]:"RESTART",[E.ENTRY_TOKEN_EXPIRED]:"RESTART",[E.ADMISSION_GRANT_INVALID]:"RESTART",[E.ADMISSION_PROOF_REQUIRED]:"RESTART",[E.ADMISSION_KEY_THUMBPRINT_REQUIRED]:"RESTART",[E.ADMISSION_ORIGINAL_ENTRY_REQUIRED]:"RESTART",[E.RATE_LIMIT_UNAVAILABLE]:"PANEL",[E.FINGERPRINT_REQUIRED]:"PANEL",[E.FINGERPRINT_DEVICE_MISMATCH]:"PANEL",[E.FINGERPRINT_INVALID]:"PANEL",[E.BOT_CHALLENGE_SUBJECT_MISMATCH]:"PANEL",[E.ADMISSION_KEY_MISMATCH]:"PANEL",[E.DISTRIBUTION_NOT_OPEN]:"PANEL",[E.DISTRIBUTION_CLOSED]:"PANEL",[E.DISTRIBUTION_FULL]:"PANEL",[E.DISTRIBUTION_ORDER_LIMIT]:"PANEL",[E.PROTOCOL_ERROR]:"PANEL",[E.NO_ACCESS]:"PANEL"},
|
|
1
|
+
import{ErrorCodes as E}from"./errors.js";const I={[E.OTP_INVALID]:"INLINE",[E.INVALID_CREDENTIALS]:"INLINE",[E.VALIDATION_ERROR]:"INLINE",[E.RATE_LIMITED]:"INLINE",[E.SLOT_OUT_OF_WINDOW]:"INLINE",[E.OVERLOADED]:"INLINE",[E.SERVICE_UNAVAILABLE]:"INLINE",[E.TIMEOUT]:"INLINE",[E.NETWORK_ERROR]:"INLINE",[E.FEATURE_DISABLED]:"INLINE",[E.PAYMENT_REQUIRED]:"INLINE",[E.PAYMENT_FAILED]:"INLINE",[E.PAYMENT_AUTHENTICATION_FAILED]:"INLINE",[E.OTP_EXPIRED]:"STEP_RESET",[E.BOT_CHALLENGE_INVALID]:"STEP_RESET",[E.BOT_CHALLENGE_EXPIRED]:"STEP_RESET",[E.SLOT_EXPIRED]:"STEP_RESET",[E.SLOT_NOT_AVAILABLE]:"STEP_RESET",[E.NEW_SLOT_FULL]:"STEP_RESET",[E.RESERVATION_EXPIRED]:"STEP_RESET",[E.INVALID_SESSION]:"RESTART",[E.INVALID_REFRESH_TOKEN]:"RESTART",[E.UNAUTHORIZED]:"RESTART",[E.ENTRY_TOKEN_REQUIRED]:"RESTART",[E.ENTRY_TOKEN_INVALID]:"RESTART",[E.ENTRY_TOKEN_EXPIRED]:"RESTART",[E.ADMISSION_GRANT_INVALID]:"RESTART",[E.ADMISSION_PROOF_REQUIRED]:"RESTART",[E.ADMISSION_KEY_THUMBPRINT_REQUIRED]:"RESTART",[E.ADMISSION_ORIGINAL_ENTRY_REQUIRED]:"RESTART",[E.RATE_LIMIT_UNAVAILABLE]:"PANEL",[E.FINGERPRINT_REQUIRED]:"PANEL",[E.FINGERPRINT_DEVICE_MISMATCH]:"PANEL",[E.FINGERPRINT_INVALID]:"PANEL",[E.BOT_CHALLENGE_SUBJECT_MISMATCH]:"PANEL",[E.ADMISSION_KEY_MISMATCH]:"PANEL",[E.DISTRIBUTION_NOT_OPEN]:"PANEL",[E.DISTRIBUTION_CLOSED]:"PANEL",[E.DISTRIBUTION_FULL]:"PANEL",[E.DISTRIBUTION_ORDER_LIMIT]:"PANEL",[E.PROTOCOL_ERROR]:"PANEL",[E.NO_ACCESS]:"PANEL",[E.CHECKOUT_MISCONFIGURED]:"PANEL"},N=/* @__PURE__ */new Set([E.ENTRY_TOKEN_MISMATCH]);function T(E,T,R){const A=function(E,T){return"reroute"===T&&N.has(E)?"RESTART":I[E]??"PANEL"}(E,R);return{disposition:A,autoRetryOnce:false}}export{T as classify};
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -18,12 +18,33 @@ export declare const SdkClientErrorCodes: {
|
|
|
18
18
|
readonly NO_ACCESS: "NO_ACCESS";
|
|
19
19
|
};
|
|
20
20
|
export type SdkClientErrorCode = (typeof SdkClientErrorCodes)[keyof typeof SdkClientErrorCodes];
|
|
21
|
+
/**
|
|
22
|
+
* Checkout/reservation codes the payment-methods and reservation-checkout endpoints produce.
|
|
23
|
+
* Unlike `SdkClientErrorCodes` these DO cross the wire, so the canonical definition lives in
|
|
24
|
+
* `@fanfare-io/fanfare-sdk-contracts/errors`; this is a re-export so existing SDK consumers keep
|
|
25
|
+
* reading `ErrorCodes.PAYMENT_REQUIRED` etc. off this module unchanged.
|
|
26
|
+
*/
|
|
27
|
+
export declare const CheckoutErrorCodes: {
|
|
28
|
+
readonly PAYMENT_REQUIRED: "PAYMENT_REQUIRED";
|
|
29
|
+
readonly PAYMENT_FAILED: "PAYMENT_FAILED";
|
|
30
|
+
readonly PAYMENT_AUTHENTICATION_FAILED: "PAYMENT_AUTHENTICATION_FAILED";
|
|
31
|
+
readonly RESERVATION_EXPIRED: "RESERVATION_EXPIRED";
|
|
32
|
+
readonly RESERVATION_NOT_FOUND: "RESERVATION_NOT_FOUND";
|
|
33
|
+
readonly CHECKOUT_MISCONFIGURED: "CHECKOUT_MISCONFIGURED";
|
|
34
|
+
};
|
|
35
|
+
export type CheckoutErrorCode = (typeof CheckoutErrorCodes)[keyof typeof CheckoutErrorCodes];
|
|
21
36
|
/**
|
|
22
37
|
* The complete set of codes the SDK can surface: every canonical contract code
|
|
23
38
|
* plus the client-only codes above. Consumers read members off this object
|
|
24
39
|
* (e.g. `ErrorCodes.RATE_LIMITED`).
|
|
25
40
|
*/
|
|
26
41
|
export declare const ErrorCodes: {
|
|
42
|
+
readonly PAYMENT_REQUIRED: "PAYMENT_REQUIRED";
|
|
43
|
+
readonly PAYMENT_FAILED: "PAYMENT_FAILED";
|
|
44
|
+
readonly PAYMENT_AUTHENTICATION_FAILED: "PAYMENT_AUTHENTICATION_FAILED";
|
|
45
|
+
readonly RESERVATION_EXPIRED: "RESERVATION_EXPIRED";
|
|
46
|
+
readonly RESERVATION_NOT_FOUND: "RESERVATION_NOT_FOUND";
|
|
47
|
+
readonly CHECKOUT_MISCONFIGURED: "CHECKOUT_MISCONFIGURED";
|
|
27
48
|
readonly NETWORK_ERROR: "NETWORK_ERROR";
|
|
28
49
|
readonly TIMEOUT: "TIMEOUT";
|
|
29
50
|
readonly ABORTED: "ABORTED";
|
|
@@ -34,6 +55,16 @@ export declare const ErrorCodes: {
|
|
|
34
55
|
readonly PROTOCOL_ERROR: "PROTOCOL_ERROR";
|
|
35
56
|
readonly CANNOT_LEAVE_AFTER_BID: "CANNOT_LEAVE_AFTER_BID";
|
|
36
57
|
readonly NO_ACCESS: "NO_ACCESS";
|
|
58
|
+
readonly SELECTION_REQUIRED: "SELECTION_REQUIRED";
|
|
59
|
+
readonly SELECTION_INVALID_CHOICE: "SELECTION_INVALID_CHOICE";
|
|
60
|
+
readonly SELECTION_NOT_REQUIRED: "SELECTION_NOT_REQUIRED";
|
|
61
|
+
readonly SELECTION_LOCKED: "SELECTION_LOCKED";
|
|
62
|
+
readonly SELECTION_MISMATCH: "SELECTION_MISMATCH";
|
|
63
|
+
readonly SELECTION_UNAVAILABLE: "SELECTION_UNAVAILABLE";
|
|
64
|
+
readonly SELECTION_LIMIT_REACHED: "SELECTION_LIMIT_REACHED";
|
|
65
|
+
readonly ADMISSION_SELECTION_UNRESOLVED: "ADMISSION_SELECTION_UNRESOLVED";
|
|
66
|
+
readonly SOLD_OUT: "SOLD_OUT";
|
|
67
|
+
readonly STOCK_UNSEEDED: "STOCK_UNSEEDED";
|
|
37
68
|
readonly OVERLOADED: "OVERLOADED";
|
|
38
69
|
readonly FEATURE_DISABLED: "FEATURE_DISABLED";
|
|
39
70
|
readonly BOT_CHALLENGE_INVALID: "BOT_CHALLENGE_INVALID";
|
|
@@ -83,7 +114,7 @@ export declare const ErrorCodes: {
|
|
|
83
114
|
* Codes the SDK recognizes. Open-ended via `(string & {})` so an unknown
|
|
84
115
|
* forward-compatible code from a newer server still type-checks at call sites.
|
|
85
116
|
*/
|
|
86
|
-
export type KnownErrorCode = ContractErrorCode | SdkClientErrorCode;
|
|
117
|
+
export type KnownErrorCode = ContractErrorCode | SdkClientErrorCode | CheckoutErrorCode;
|
|
87
118
|
export type ErrorCode = KnownErrorCode | (string & {});
|
|
88
119
|
/** Parses an RFC 7231 Retry-After header (delta-seconds or HTTP-date) into whole seconds, or undefined. */
|
|
89
120
|
export declare function parseRetryAfterSeconds(header: string | null): number | undefined;
|
package/dist/core/errors.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ErrorCodeMap as
|
|
1
|
+
import{CheckoutErrorCodes as e,ErrorCodeMap as t}from"@fanfare-io/fanfare-sdk-contracts/errors";const I={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",CANNOT_LEAVE_AFTER_BID:"CANNOT_LEAVE_AFTER_BID",NO_ACCESS:"NO_ACCESS"},r=e,E={...t,...I,...r};function o(e,t){if(e&&"object"==typeof e&&t in e){const I=e[t];if("string"==typeof I)return I}}function N(e){if(!e)return;const t=+e,I=Math.ceil(isFinite(t)?t:(Date.parse(e)-Date.now())/1e3);return I>=0?I:I<0?0:void 0}const O=/* @__PURE__ */new Set([t.VALIDATION_ERROR,t.UNAUTHORIZED,t.FORBIDDEN,t.NOT_FOUND,t.RATE_LIMITED,t.RATE_LIMIT_UNAVAILABLE,t.PAYLOAD_TOO_LARGE,t.INVALID_CREDENTIALS,t.INVALID_SESSION,t.INVALID_REFRESH_TOKEN,t.OTP_INVALID,t.OTP_EXPIRED,t.ENTRY_TOKEN_REQUIRED,t.ENTRY_TOKEN_INVALID,t.ENTRY_TOKEN_EXPIRED,t.ENTRY_TOKEN_MISMATCH,t.SLOT_EXPIRED,t.SLOT_OUT_OF_WINDOW,t.ALREADY_BOOKED,t.ACTIVE_BOOKING_EXISTS,t.NO_EXISTING_BOOKING,t.INVALID_SLOT,t.CANNOT_CANCEL_ONGOING_OR_PAST,t.FINGERPRINT_REQUIRED,t.FINGERPRINT_DEVICE_MISMATCH,t.FINGERPRINT_INVALID,t.SLOT_NOT_AVAILABLE,t.NEW_SLOT_FULL,t.DISTRIBUTION_NOT_OPEN,t.DISTRIBUTION_CLOSED,t.DISTRIBUTION_FULL,t.ADMISSION_KEY_THUMBPRINT_REQUIRED,t.ADMISSION_PROOF_REQUIRED,t.ADMISSION_GRANT_INVALID,t.ADMISSION_ORIGINAL_ENTRY_REQUIRED,t.ADMISSION_KEY_MISMATCH,t.BOT_CHALLENGE_INVALID,t.BOT_CHALLENGE_EXPIRED,t.BOT_CHALLENGE_SUBJECT_MISMATCH]);class _ extends Error{constructor(e,t,I,r,E,o,N=!1,O,n){super(e),this.name="FanfareError",this.code=t,this.status=I,this.details=r,this.requestId=E,this.correlationId=o,this.retryable=N,this.retryAfter=O,this.issues=n?.issues,this.action=n?.action,this.support=n?.support,this.quietable=n?.quietable,Error.captureStackTrace&&Error.captureStackTrace(this,_)}static fromResponse(e,t,I){const r=e.headers.get("X-Request-Id")||void 0,E=e.headers.get("X-Correlation-Id")||I,R=N(e.headers.get("Retry-After")),{code:T,retryable:i}=n(e.status,t),s=o(t,"message"),A=(s&&e.status<500&&O.has(T)?s:null)||e.statusText||"An error occurred",a=(t&&"object"==typeof t&&"details"in t?t.details:t)??t,D=t&&"object"==typeof t&&Array.isArray(t.issues)?t.issues:void 0,c=o(t,"action"),L=t&&"object"==typeof t&&"support"in t?t.support:void 0;return new _(A,T,e.status,a,r,E,i,R,{issues:D,action:c,support:L})}is(e){return this.code===e}}function n(e,t){const I=429===e||e>=500,r=o(t,"code");return r?{code:r,retryable:I}:429===e?{code:E.RATE_LIMITED,retryable:I}:401===e?{code:E.UNAUTHORIZED,retryable:I}:403===e?{code:E.FORBIDDEN,retryable:I}:413===e?{code:E.PAYLOAD_TOO_LARGE,retryable:I}:422===e||400===e?{code:E.VALIDATION_ERROR,retryable:I}:404===e?{code:E.NOT_FOUND,retryable:I}:423===e?{code:E.DISTRIBUTION_NOT_OPEN,retryable:I}:503===e?{code:E.SERVICE_UNAVAILABLE,retryable:I}:e>=500?{code:E.INTERNAL_ERROR,retryable:I}:{code:`HTTP_${e}`,retryable:I}}class R extends _{constructor(e,t,I,r){super(e,E.PROTOCOL_ERROR,void 0,I,r),this.name="ProtocolError",this.endpoint=t,this.issues=I}}function T(e){return e instanceof _}function i(e){return e instanceof R}const s=/* @__PURE__ */new Set([E.ENTRY_TOKEN_REQUIRED,E.ENTRY_TOKEN_INVALID,E.ENTRY_TOKEN_EXPIRED,E.ENTRY_TOKEN_MISMATCH]);function A(e){return e instanceof _&&(403===e.status&&s.has(e.code))}function a(e){return A(e)}function D(e){return!!A(e)&&"reroute"===e.action}function c(e){return D(e)}const L={networkError:(e,t)=>new _(e,E.NETWORK_ERROR,void 0,t),timeout:(e="Request timed out")=>new _(e,E.TIMEOUT),unauthorized:(e="Unauthorized")=>new _(e,E.UNAUTHORIZED,401),invalidSession:(e="Session is no longer valid")=>new _(e,E.INVALID_SESSION,401),notInitialized:(e="SDK not initialized")=>new _(e,E.NOT_INITIALIZED),invalidConfig:(e,t)=>new _(e,E.INVALID_CONFIG,void 0,t),validationError:(e,t)=>new _(e,E.VALIDATION_ERROR,void 0,t),internalError:(e,t)=>new _(e,E.INTERNAL_ERROR,void 0,t),cannotLeaveAfterBid:e=>new _(`Cannot leave auction ${e} after placing a bid — a bid is a binding commitment`,E.CANNOT_LEAVE_AFTER_BID,void 0,{auctionId:e})};export{r as CheckoutErrorCodes,E as ErrorCodes,_ as FanfareError,R as ProtocolError,I as SdkClientErrorCodes,n as classifyHttpError,L as createError,a as isCapabilityGrantError,c as isCapabilityGrantRerouteError,A as isCapabilityTokenError,D as isCapabilityTokenRerouteError,T as isFanfareError,i as isProtocolError,N as parseRetryAfterSeconds};
|
package/dist/core/http.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"wretch";import{retry as t}from"wretch/middlewares";import{buildAdmissionProofHeaders as r,getAdmissionKeyThumbprint as i}from"../security/admission-proof.js";import{generateFingerprint as n}from"../utils/fingerprint.module.js";import{parseRetryAfterSeconds as o,FanfareError as s,ErrorCodes as a}from"./errors.js";import{getLogger as c}from"./logger.js";import{isStorageAvailable as l,generateId as g}from"./utils.js";class h{constructor(e,t){this.logger=c(),this.fingerprint=null,this.inFlightGetRequests=/* @__PURE__ */new Map,this.config=e,this.fingerprintingEnabled=!1!==t?.features?.fingerprinting;const r="fanfare_client_session_id";let i=null;if(l("localStorage"))try{i=window.localStorage.getItem(r)}catch{i=null}if(this.clientSessionId=i||g(),!i&&l("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(i,n){const{retryConfig:s={}}=this.config;let a;const c={"Content-Type":"application/json","X-Client-Session-Id":this.clientSessionId,...this.config.headers};return this.fingerprint&&(c["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(c).middlewares([(l=this.inFlightGetRequests,g=i?.signal||i?.skipUnauthorizedHandler?void 0:JSON.stringify([this.config.headers,i?.headers,this.fingerprint,n]),e=>(t,r)=>{if(!g||"GET"!==r.method)return e(t,r);const i=`${t}\n${g}`,n=l.get(i);if(n)return n.then(e=>e.clone());const o=e(t,r);return l.set(i,o),o.finally(()=>l.delete(i))}),t({delayTimer:s.delayTimer||1e3,maxAttempts:s.maxRetries||3,delayRamp:(e,t)=>a??Math.random()*Math.min(e*2**(t-1),s.maxDelay||1e4),until:e=>{if(a=void 0,!e)return!1;const t=e.status,r=o(e.headers.get("Retry-After"));return 429===t&&void 0!==r&&r>30||(void 0!==r&&(a=1e3*Math.min(r,30)),!!e.ok||t>=400&&t<500&&429!==t)},onRetry:async e=>{const t=e.options.headers??{};if(!t.DPoP&&!t.dpop)return;const i="string"==typeof e.options.method?e.options.method:"POST",n=this.config.baseUrl,o=e.url.startsWith(n)?new URL(e.url.slice(n.length),n).toString():e.url,s=await r({url:o,method:i});return s?{options:{...e.options,headers:{...t,...s}}}:void 0}})]).catcher(401,async e=>{throw!i?.skipUnauthorizedHandler&&this.config.onUnauthorized&&(this.logger.debug("Received 401, triggering re-authentication"),await this.config.onUnauthorized()),await this.toFanfareError(e)}).catcherFallback(async e=>{throw await this.toFanfareError(e)});var l,g}async toFanfareError(e){if(e&&"object"==typeof e&&"response"in e&&e.response instanceof Response){let t=null;const r=await e.response.text().catch(()=>null),i=e;if(r)try{t=JSON.parse(r)}catch{t={message:r}}else if(null!==i.json&&void 0!==i.json&&"object"==typeof i.json)t=i.json;else if("string"==typeof i.text&&i.text.length>0)try{t=JSON.parse(i.text)}catch{t={message:i.text}}const n=("correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0)||void 0;return s.fromResponse(e.response,t,n)}return e&&"object"==typeof e&&"name"in e&&"AbortError"===e.name?new s("Request timed out",a.TIMEOUT,void 0,void 0,"correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0):new s(e&&"object"==typeof e&&"message"in e&&"string"==typeof e.message?e.message:"Network error occurred",a.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,o){const c=g(),l=g(),h=this.config.timeout||3e4,d=await i(),p=function(e){return/^\/(queues|draws|timed-releases)\/[^/]+\/enter$/.test(e)}(t),f=new URL(t,this.config.baseUrl).toString(),u=new AbortController,m=setTimeout(()=>u.abort(),h);try{const i={"X-Correlation-Id":c,"X-Request-Id":l,...o?.headers};if(p&&!d&&!i["X-Admission-Key-Thumbprint"])throw new s("Admission proof requires persistent browser crypto support",a.ADMISSION_PROOF_UNAVAILABLE,void 0,{path:t});if(p){const n=await r({url:f,method:e.toUpperCase()});if(!n)throw new s("Admission proof requires persistent browser crypto support",a.ADMISSION_PROOF_UNAVAILABLE,void 0,{path:t});Object.assign(i,n)}if(!p&&function(e){return
|
|
1
|
+
import e from"wretch";import{retry as t}from"wretch/middlewares";import{buildAdmissionProofHeaders as r,getAdmissionKeyThumbprint as i}from"../security/admission-proof.js";import{generateFingerprint as n}from"../utils/fingerprint.module.js";import{parseRetryAfterSeconds as o,FanfareError as s,ErrorCodes as a}from"./errors.js";import{getLogger as c}from"./logger.js";import{isStorageAvailable as l,generateId as g}from"./utils.js";class h{constructor(e,t){this.logger=c(),this.fingerprint=null,this.inFlightGetRequests=/* @__PURE__ */new Map,this.config=e,this.fingerprintingEnabled=!1!==t?.features?.fingerprinting;const r="fanfare_client_session_id";let i=null;if(l("localStorage"))try{i=window.localStorage.getItem(r)}catch{i=null}if(this.clientSessionId=i||g(),!i&&l("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(i,n){const{retryConfig:s={}}=this.config;let a;const c={"Content-Type":"application/json","X-Client-Session-Id":this.clientSessionId,...this.config.headers};return this.fingerprint&&(c["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(c).middlewares([(l=this.inFlightGetRequests,g=i?.signal||i?.skipUnauthorizedHandler?void 0:JSON.stringify([this.config.headers,i?.headers,this.fingerprint,n]),e=>(t,r)=>{if(!g||"GET"!==r.method)return e(t,r);const i=`${t}\n${g}`,n=l.get(i);if(n)return n.then(e=>e.clone());const o=e(t,r);return l.set(i,o),o.finally(()=>l.delete(i))}),t({delayTimer:s.delayTimer||1e3,maxAttempts:s.maxRetries||3,delayRamp:(e,t)=>a??Math.random()*Math.min(e*2**(t-1),s.maxDelay||1e4),until:e=>{if(a=void 0,!e)return!1;const t=e.status,r=o(e.headers.get("Retry-After"));return 429===t&&void 0!==r&&r>30||(void 0!==r&&(a=1e3*Math.min(r,30)),!!e.ok||t>=400&&t<500&&429!==t)},onRetry:async e=>{const t=e.options.headers??{};if(!t.DPoP&&!t.dpop)return;const i="string"==typeof e.options.method?e.options.method:"POST",n=this.config.baseUrl,o=e.url.startsWith(n)?new URL(e.url.slice(n.length),n).toString():e.url,s=await r({url:o,method:i});return s?{options:{...e.options,headers:{...t,...s}}}:void 0}})]).catcher(401,async e=>{throw!i?.skipUnauthorizedHandler&&this.config.onUnauthorized&&(this.logger.debug("Received 401, triggering re-authentication"),await this.config.onUnauthorized()),await this.toFanfareError(e)}).catcherFallback(async e=>{throw await this.toFanfareError(e)});var l,g}async toFanfareError(e){if(e&&"object"==typeof e&&"response"in e&&e.response instanceof Response){let t=null;const r=await e.response.text().catch(()=>null),i=e;if(r)try{t=JSON.parse(r)}catch{t={message:r}}else if(null!==i.json&&void 0!==i.json&&"object"==typeof i.json)t=i.json;else if("string"==typeof i.text&&i.text.length>0)try{t=JSON.parse(i.text)}catch{t={message:i.text}}const n=("correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0)||void 0;return s.fromResponse(e.response,t,n)}return e&&"object"==typeof e&&"name"in e&&"AbortError"===e.name?new s("Request timed out",a.TIMEOUT,void 0,void 0,"correlationId"in e&&"string"==typeof e.correlationId?e.correlationId:void 0):new s(e&&"object"==typeof e&&"message"in e&&"string"==typeof e.message?e.message:"Network error occurred",a.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,o){const c=g(),l=g(),h=this.config.timeout||3e4,d=await i(),p=function(e){return/^\/(queues|draws|timed-releases)\/[^/]+\/enter$/.test(e)||/^\/auctions\/[^/]+\/bid$/.test(e)}(t),f=new URL(t,this.config.baseUrl).toString(),u=new AbortController,m=setTimeout(()=>u.abort(),h);try{const i={"X-Correlation-Id":c,"X-Request-Id":l,...o?.headers};if(p&&!d&&!i["X-Admission-Key-Thumbprint"])throw new s("Admission proof requires persistent browser crypto support",a.ADMISSION_PROOF_UNAVAILABLE,void 0,{path:t});if(p){const n=await r({url:f,method:e.toUpperCase()});if(!n)throw new s("Admission proof requires persistent browser crypto support",a.ADMISSION_PROOF_UNAVAILABLE,void 0,{path:t});Object.assign(i,n)}if(!p&&function(e){return"/consumers/me"===e}(t)&&d){const t=await r({url:f,method:e.toUpperCase()});t&&Object.assign(i,t)}d&&!i["X-Admission-Key-Thumbprint"]&&(i["X-Admission-Key-Thumbprint"]=d);const g=this.getClient(o,d).url(t).headers(i).options({signal:o?.signal||u.signal});return await("get"===e||"delete"===e?g[e]():g[e](n)).json()}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 d(e,t){return new h(e,t)}export{h as DefaultHttpClient,d as createHttpClient};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { DistributionMonitor, MonitorUpdate } from '../experiences/distribution-monitor.types';
|
|
1
|
+
import { DistributionMonitor, DrawMonitorContext, MonitorUpdate } from '../experiences/distribution-monitor.types';
|
|
2
2
|
import { DistributionSummary, JourneyEvent, JourneyEventAudience, JourneyEventKind, JourneyEventSeverity, MechanismConsumerState, Participation, SequenceOutcome, SequenceState } from '../experiences/journey.types';
|
|
3
3
|
import { SequenceDriver, SequenceProjectionFacts } from '../experiences/sequence-driver';
|
|
4
4
|
import { DrawModule } from './types';
|
|
5
|
-
type DrawMonitorModule = DrawModule & Pick<DistributionMonitor
|
|
5
|
+
type DrawMonitorModule = DrawModule & Pick<DistributionMonitor<DrawMonitorContext>, "startMonitoring" | "stopMonitoring">;
|
|
6
6
|
/**
|
|
7
7
|
* Narrow shell and module capabilities required by the draw driver.
|
|
8
8
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createError as e}from"../core/errors.js";function t(e){return new
|
|
1
|
+
import{createError as e}from"../core/errors.js";import{resolveCheckoutContract as t,checkoutViewFields as i,isEntryActionRequired as n,canReenter as r,isPreAuthMisconfigured as s}from"../payment/checkout-contract.js";import{isUnresolvedDrawConsumer as a}from"./draw.sequence.js";function o(e){return s(t(e.details))}function d(e){return r(e.distribution.lifecycle,t(e.distribution.details))}function c(e){return new h(e)}function u(t){const{distribution:i,consumer:n,grant:r,checkout:s,reservation:d,outcome:c}=t;if(!i)return{phase:"unavailable"};const u=function(t){if("draw"!==t.type)throw e.validationError("Draw driver received a non-draw distribution");return t}(i),p=n&&"draw"===n.mechanism?function(e){const{mechanism:t,...i}=e;return i}(n):l(i);if(r)return"completed"===c?.type?w(u,p,c,s):r.expiresAt&&Date.now()>new Date(r.expiresAt).getTime()?"open"===u.lifecycle?o(u)?{phase:"unavailable",reason:"checkout_misconfigured"}:{phase:"enterable",mechanism:"draw",distribution:u,consumer:l(u)}:w(u,p,{type:"expired"}):{phase:"granted",mechanism:"draw",distribution:u,consumer:p,grant:r,...s?{checkout:s}:{}};if(d)return Date.now()>=d.expiresAt?w(u,p,{type:"expired"}):{phase:"reserved",mechanism:"draw",distribution:u,consumer:p,reservation:{token:d.token,expiresAt:d.expiresAt},...d.paymentProvider?{paymentProvider:d.paymentProvider}:{}};if(!c&&"draw"===n?.mechanism&&"closed"===u.lifecycle)return w(u,p,{type:"lost"});const m=c??function(e){return"completed"===e.status?{type:"completed"}:"denied"===e.status?{type:"denied",reason:e.deniedReason}:void 0}(p);return m?w(u,p,m,s):"settling"===u.lifecycle?{phase:"settling",mechanism:"draw",distribution:u,consumer:p}:a(p)?{phase:"participating",mechanism:"draw",distribution:u,consumer:p}:"scheduled"===u.lifecycle?{phase:"scheduled",mechanism:"draw",distribution:u}:"open"===u.lifecycle?o(u)?{phase:"unavailable",reason:"checkout_misconfigured"}:{phase:"enterable",mechanism:"draw",distribution:u,consumer:p}:w(u,p,{type:"closed"})}function p(e,t){return"draw"===e?.mechanism?{...e,status:"won"}:{mechanism:"draw",status:"won",id:t.id,distributionId:t.id}}function m(e,t){return e&&"draw"===e.mechanism&&t?"denied"===t.type?{...e,status:"denied",deniedReason:t.reason}:"completed"===t.type?{...e,status:"completed"}:"won"===t.type?{...e,status:"won"}:{...e,status:"not_entered"}:e}class h{constructor(e){this.deps=e,this.mechanism="draw",this.settlementCanStartWhileActive=!0}project(e){return u(e)}buildView(t,i){if("unavailable"===t.phase)return{phase:"unavailable",reason:t.reason};const n=function(t){if(!("mechanism"in t)||"draw"!==t.mechanism)throw e.validationError("Draw driver received a non-draw sequence");return t}(t);switch(n.phase){case"scheduled":return{phase:"scheduled",mechanism:"draw",distribution:n.distribution,startsAt:n.distribution.startsAt??n.distribution.opensAt};case"enterable":return this.buildEnterableView(n);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"reserved":return{phase:"reserved",mechanism:"draw",consumer:n.consumer,reservation:n.reservation,payment:{processor:"internal",confirmCheckout:e=>i.confirmCheckout(e),resumeCheckout:()=>i.resumeCheckout(),...n.pendingAction?{pendingAction:n.pendingAction}:{},...n.paymentProvider?{paymentProvider:n.paymentProvider}:{}}};case"granted":return{phase:"granted",mechanism:"draw",distribution:n.distribution,consumer:n.consumer,grant:n.grant,...n.checkout?{checkout:n.checkout}:{},claim:()=>i.claim()};case"ended":return this.buildEndedView(n)}}buildEnterableView(e){const n=t(e.distribution.details),r=i(n);return"pre_auth"===r.checkoutMode?{phase:"enterable",mechanism:"draw",...r,distribution:e.distribution,consumer:e.consumer,...e.pendingAction?{pendingAction:e.pendingAction}:{},...e.paymentProvider?{paymentProvider:e.paymentProvider}:{},enter:e=>this.enter({paymentInput:e})}:{phase:"enterable",mechanism:"draw",...r,distribution:e.distribution,consumer:e.consumer,enter:()=>this.enter()}}buildEndedView(e){const n={phase:"ended",mechanism:"draw",consumer:e.consumer,outcome:e.outcome,...e.checkout?{checkout:e.checkout}:{}};if(!d(e))return n;const r=t(e.distribution.details),s=i(r);return"pre_auth"===s.checkoutMode?{...n,...s,reenter:e=>this.enter({paymentInput:e})}:{...n,...s,reenter:()=>this.enter()}}startMonitoring(e){this.activeMonitorId=e.participation.id;const t=this.deps.getCurrentSequence(),i=t&&("participating"===t.phase||"settling"===t.phase)&&"mechanism"in t&&"draw"===t.mechanism&&"draw"===t.distribution.details?.type?t.distribution.details.drawTime:void 0;this.deps.draws.startMonitoring(e.participation.id,{displayAtom:e.displayAtom,drawTime:i,phase:"settling"===t?.phase?"settling":"participating"},e.onUpdate)}stopMonitoring(){this.activeMonitorId&&(this.deps.draws.stopMonitoring(this.activeMonitorId),this.activeMonitorId=void 0)}async enter(e){await this.deps.runSerializedOperation(async()=>{await this.enterWithinOperation(e)})}async enterWithinOperation(i){const r=this.currentDrawSequence("enterable","No open draw to enter"),s=r.distribution,a=t(s.details),o=i?.paymentInput;if("pre_auth"===a.mode&&!o)throw e.validationError("Payment input is required for pre-auth distributions");const d=o?await this.deps.draws.enter(s.id,void 0,void 0,o):await this.deps.draws.enter(s.id,void 0);if(n(d))return this.deps.commit({...r,pendingAction:d.nextAction}),void this.deps.emitEvent("sequence_change","info","user","Entry awaiting verification",{distributionId:s.id,processor:d.nextAction.processor});const c=function(e){if("ENTERED"===e.status)return null;if("COMPLETED"===e.status)return{type:"ended",outcome:{type:"completed"}};if("WON"===e.status)return"admissionGrant"in e?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:{type:"reserved",token:e.reservation.token,expiresAt:new Date(e.reservation.expiresAt).getTime()};return{type:"ended",outcome:{type:"DENIED"===e.status?"denied":"lost",reason:"reason"in e?e.reason:void 0}}}(d);if(c)return void this.deps.applyMonitorUpdate(c);const p={mechanism:"draw",status:"entered",id:m=s.id,distributionId:m};var m;this.deps.ensureDisplayAtom({id:s.id,type:"draw"}),this.deps.commit(u({distribution:s,consumer:p})),this.deps.emitEvent("sequence_change","success","user","Entered draw",{distributionId:s.id})}async leave(){await this.deps.runSerializedOperation(async()=>{await this.leaveWithinOperation()})}async leaveWithinOperation(){const e=this.currentDrawSequence("participating"),t=e.consumer.distributionId??e.consumer.id??e.distribution.id;this.deps.stopRuntimeMonitoring(!0),await this.deps.draws.leave(t),this.deps.applyMonitorUpdate({type:"ended",outcome:{type:"left"}}),this.deps.emitEvent("sequence_change","info","user","Left participation",{type:"draw"})}currentDrawSequence(t,i="Not participating in a draw"){const n=this.deps.getCurrentSequence();if(!n||!("mechanism"in n)||"draw"!==n.mechanism)throw e.validationError(i);if("enterable"===t&&"ended"===n.phase&&d(n))return{phase:"enterable",mechanism:"draw",distribution:n.distribution,consumer:l(n.distribution)};if(n.phase!==t)throw e.validationError(i);return n}}function l(e){return{status:"not_entered",distributionId:e.id}}function w(e,t,i,n){return{phase:"ended",mechanism:"draw",distribution:e,consumer:t,outcome:{...i,distributionType:i.distributionType??e.type,distributionId:i.distributionId??e.id,at:i.at??/* @__PURE__ */(new Date).toISOString()},...n?{checkout:n}:{}}}export{c as createDrawDriver,u as projectDrawSequence,p as withDrawGrantedConsumer,m as withDrawTerminalConsumer};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HttpClient, RequestOptions } from '../core/http';
|
|
2
|
-
import { MonitorUpdate } from '../experiences/distribution-monitor.types';
|
|
2
|
+
import { DrawMonitorContext, MonitorUpdate } from '../experiences/distribution-monitor.types';
|
|
3
|
+
import { DistributionEntryRequiresAction, PaymentInput } from '../payment/types';
|
|
3
4
|
import { Draw, DrawConsumerState, DrawModule } from './types';
|
|
4
5
|
export declare class DrawManagementModule implements DrawModule {
|
|
5
6
|
private readonly http;
|
|
@@ -8,6 +9,7 @@ export declare class DrawManagementModule implements DrawModule {
|
|
|
8
9
|
private readonly trackedParticipations;
|
|
9
10
|
private readonly inFlightRequests;
|
|
10
11
|
private readonly scheduledChecks;
|
|
12
|
+
private readonly settlingTimers;
|
|
11
13
|
private readonly monitorRuntime;
|
|
12
14
|
private readonly monitorGenerations;
|
|
13
15
|
private readonly operationGenerations;
|
|
@@ -20,19 +22,22 @@ export declare class DrawManagementModule implements DrawModule {
|
|
|
20
22
|
private get store();
|
|
21
23
|
constructor(http: HttpClient);
|
|
22
24
|
get(drawId: string): Promise<Draw>;
|
|
23
|
-
enter(drawId: string, metadata?: Record<string, unknown>, options?: RequestOptions): Promise<DrawConsumerState>;
|
|
25
|
+
enter(drawId: string, metadata?: Record<string, unknown>, options?: RequestOptions, paymentInput?: PaymentInput): Promise<DrawConsumerState | DistributionEntryRequiresAction>;
|
|
24
26
|
leave(drawId: string, options?: RequestOptions): Promise<void>;
|
|
25
27
|
status(drawId: string): Promise<DrawConsumerState>;
|
|
26
28
|
private doStatus;
|
|
27
|
-
startMonitoring(id: string, context?:
|
|
29
|
+
startMonitoring(id: string, context?: DrawMonitorContext, onUpdate?: (update: MonitorUpdate) => void): void;
|
|
28
30
|
stopMonitoring(id: string): void;
|
|
29
31
|
isMonitoring(id: string): boolean;
|
|
30
32
|
private buildTransition;
|
|
31
33
|
private applyTransition;
|
|
32
34
|
private scheduleInitialStatusCheck;
|
|
35
|
+
private scheduleSettlingTransition;
|
|
36
|
+
private cancelSettlingTransition;
|
|
33
37
|
private scheduleRetryStatusCheck;
|
|
34
38
|
private isInSettlingWindow;
|
|
35
39
|
private scheduleStatusCheck;
|
|
40
|
+
private scheduleStatusCheckAt;
|
|
36
41
|
private cancelScheduledCheck;
|
|
37
42
|
private buildLostResult;
|
|
38
43
|
private bumpMonitorGeneration;
|