@fanfare-io/fanfare-sdk-core 0.11.0 → 0.13.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.js +1 -1
- package/dist/appointments/types.d.ts +11 -5
- package/dist/auctions/auction-status.js +1 -1
- 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/auctions/types.d.ts +6 -0
- package/dist/core/client.js +2 -2
- package/dist/core/error-display.d.ts +5 -0
- package/dist/core/error-display.js +1 -1
- package/dist/core/error-disposition.js +1 -1
- package/dist/core/errors.d.ts +34 -1
- package/dist/core/errors.js +1 -1
- package/dist/core/http.js +1 -1
- package/dist/draws/draw.driver.js +1 -1
- 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.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.js +1 -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/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.js +1 -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.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/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 +4 -7
- package/dist/waitlists/waitlist.module.js +1 -1
- package/package.json +17 -35
- package/dist/appointments/appointment.driver.d.ts +0 -44
- package/dist/appointments/appointment.module.d.ts +0 -32
- package/dist/appointments/index.d.ts +0 -2
- package/dist/auctions/auction-status.d.ts +0 -4
- package/dist/auctions/auction.driver.d.ts +0 -60
- package/dist/auctions/auction.module.d.ts +0 -73
- package/dist/auctions/index.d.ts +0 -5
- package/dist/auth/auth.module.d.ts +0 -167
- package/dist/beacon/batching.d.ts +0 -52
- package/dist/challenges/challenge.module.d.ts +0 -9
- package/dist/challenges/index.d.ts +0 -3
- package/dist/core/logger.d.ts +0 -46
- package/dist/core/money.d.ts +0 -10
- package/dist/core/parse-response.d.ts +0 -62
- package/dist/core/poll-scheduler.d.ts +0 -21
- package/dist/draws/draw.driver.d.ts +0 -45
- package/dist/draws/draw.module.d.ts +0 -43
- package/dist/draws/index.d.ts +0 -5
- package/dist/experiences/distribution-monitor.runtime.d.ts +0 -12
- package/dist/experiences/experience.module.d.ts +0 -89
- package/dist/experiences/index.d.ts +0 -3
- package/dist/experiences/index.js +0 -1
- package/dist/experiences/journey-contract.fixtures.d.ts +0 -12
- package/dist/experiences/journey-view.d.ts +0 -20
- package/dist/experiences/journey.d.ts +0 -126
- package/dist/experiences/sequence-driver.d.ts +0 -89
- package/dist/harness.d.ts +0 -21
- package/dist/harness.js +0 -1
- package/dist/queues/queue.driver.d.ts +0 -45
- package/dist/queues/queue.module.d.ts +0 -45
- package/dist/state/capability-token-registry.d.ts +0 -58
- package/dist/state/store.d.ts +0 -61
- package/dist/sync/broadcast-transport.d.ts +0 -19
- package/dist/sync/cross-tab-coordinator.d.ts +0 -44
- package/dist/sync/index.d.ts +0 -8
- package/dist/sync/localstorage-transport.d.ts +0 -32
- package/dist/sync/protocol-transport.d.ts +0 -52
- package/dist/sync/protocol.d.ts +0 -283
- package/dist/sync/recovery.d.ts +0 -68
- package/dist/sync/transport-factory.d.ts +0 -16
- package/dist/sync/transport-interface.d.ts +0 -33
- package/dist/sync/transport.d.ts +0 -49
- package/dist/test-utils/harness-scenarios.d.ts +0 -70
- package/dist/test-utils/harness-scenarios.js +0 -1
- package/dist/test-utils/index.d.ts +0 -12
- package/dist/test-utils/index.js +0 -1
- package/dist/test-utils/mock-journey.d.ts +0 -74
- package/dist/test-utils/mock-journey.js +0 -1
- package/dist/test-utils/mock-sdk-vitest.d.ts +0 -80
- package/dist/test-utils/mock-sdk-vitest.js +0 -1
- package/dist/test-utils/mock-sdk.d.ts +0 -22
- package/dist/test-utils/mock-sdk.js +0 -1
- package/dist/test-utils/mock-server.d.ts +0 -105
- package/dist/test-utils/mock-server.js +0 -1
- package/dist/test-utils/sdk-factory.d.ts +0 -3
- package/dist/test-utils/sdk-factory.js +0 -1
- package/dist/test-utils/verification-scenarios.d.ts +0 -51
- package/dist/test-utils/verification-scenarios.js +0 -1
- package/dist/test-utils/verification-state.d.ts +0 -1
- package/dist/test-utils/verification-state.js +0 -1
- package/dist/timed-releases/index.d.ts +0 -6
- package/dist/timed-releases/timed-release.driver.d.ts +0 -45
- package/dist/timed-releases/timed-release.module.d.ts +0 -36
- package/dist/utils/index.d.ts +0 -4
- package/dist/waitlists/index.d.ts +0 -2
- package/dist/waitlists/waitlist.module.d.ts +0 -17
|
@@ -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
|
-
function n(n){switch(n){case"NOT_BID":return"not_bid";case"BIDDING":return"bidding";case"WINNING":return"winning";case"OUTBID":return"outbid";case"WON":return"won";case"LOST":return"lost"}}function
|
|
1
|
+
function n(n){switch(n){case"NOT_BID":return"not_bid";case"BIDDING":return"bidding";case"WINNING":return"winning";case"OUTBID":return"outbid";case"WON":return"won";case"LOST":return"lost"}}function t(n){return"won"===n||"lost"===n}function r(n){return"won"===n.status&&!0===n.degraded&&!n.admissionGrant}export{r as isGrantPendingWin,t as isTerminalAuctionStatus,n as toAuctionConsumerStatus};
|
|
@@ -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{isGrantPendingWin as s}from"./auction-status.js";import{isUnresolvedAuctionConsumer as o}from"./auction.sequence.js";function a(t){return!t||("lost"===t.type||"closed"===t.type)&&!t.reason}function r(t){return new p(t)}function c(e){const{distribution:n,consumer:s,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=s&&"auction"===s.mechanism?function(t){const{mechanism:i,...e}=t;return e}(s):h(n);if(r)return"completed"===c?.type?m(u,d,c):r.expiresAt&&Date.now()>new Date(r.expiresAt).getTime()?"open"===u.lifecycle?{phase:"enterable",mechanism:"auction",distribution:u,consumer:h(u)}:m(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&&a(c))return m(u,d,{type:"lost",reason:t.FLOOR_UNSOLD});if("auction"===s?.mechanism&&!c)return m(u,d,{type:"lost"})}const p=c??function(t){return"lost"===t.status?{type:"lost"}:void 0}(d);return p?m(u,d,p):"settling"===u.lifecycle?{phase:"settling",mechanism:"auction",distribution:u,consumer:d}:o(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}:m(u,d,{type:"closed"})}function u(t,i){return"auction"===t?.mechanism?{...t,status:"won"}:{mechanism:"auction",status:"won",id:i.id,distributionId:i.id}}function d(t,i,e){return t&&"auction"===t.mechanism&&i?"won"===i.type?{...t,status:"won"}:!e?.finalize&&"not_bid"===t.status&&a(i)?t:{...t,status:"lost"}:t}class p{constructor(t){this.deps=t,this.mechanism="auction"}project(t){return c(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(c({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(),o=i.consumer.distributionId??i.consumer.id??i.distribution.id,a=await this.deps.auctions.bid(o,t);if("won"===a.status||"lost"===a.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===o&&("won"===a.status&&a.admissionGrant?this.deps.applyMonitorUpdate({type:"granted",token:a.admissionGrant}):s(a)||this.deps.applyMonitorUpdate({type:"ended",outcome:{type:a.status}})))}const r=function(t){switch(t){case"not_bid":case"bidding":case"winning":case"outbid":return t;case"won":case"lost":return}}(a.status);if(void 0===r)return;const u="auction"===i.distribution.details?.type?i.distribution.details:void 0,d=u?.bidIncrement,p=a.highestBid&&d&&e(a.highestBid)&&e(d)?n(a.highestBid,d):void 0,h=this.deps.getDisplayState(),m="auction"===h?.type?h.settleAt:void 0,l=this.deps.ensureDisplayAtom({id:o,type:"auction"});"enterable"===i.phase&&this.deps.commit(c({distribution:i.distribution,consumer:{mechanism:"auction",status:r,id:o,distributionId:o,currentBid:t,highestBid:a.highestBid}})),l.set({type:"auction",currentBid:t,highestBid:a.highestBid,bidCount:a.bidCount,status:r,currencyCode:u?.currencyCode,bidIncrement:d,minNextBid:p,closeAt:u?.closeAt??u?.endsAt,settleAt:m})})}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 h(t){return{status:"not_bid",distributionId:t.id}}function m(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{r as createAuctionDriver,a as isUpgradeableGenericLoss,c as projectAuctionSequence,u as withAuctionGrantedConsumer,d 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 d,isMoneyLessThanOrEqualTo as u,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{isGrantPendingWin as b,isTerminalAuctionStatus as y,toAuctionConsumerStatus as f}from"./auction-status.js";class B{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&&!b(i)||"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(y(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 d={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},u=[];let h=null;return n&&(this.checkOutbidConditions(n,i,s)&&(u.push({type:"auction:outbid",payload:{auctionId:t,yourBid:n.currentBid||s?.currentBid||"",highestBid:i.highestBid}}),h=i),"winning"!==n.status&&"winning"===i.status&&u.push({type:"auction:winning",payload:{auctionId:t,amount:a||"",highestBid:i.highestBid}}),n.highestBid!==i.highestBid&&"winning"!==i.status&&u.push({type:"auction:bid-updated",payload:{auctionId:t,highestBid:i.highestBid,bidCount:i.bidCount||0}})),{trackedParticipation:d,displayState:this.buildDisplayState(e,{status:i.status,highestBid:i.highestBid,currentBid:a,bidCount:o}),events:u,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(i,e,n){try{const o=this.lifecycleGeneration;if(!a(e))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 r=this.bumpOperationGeneration(i),d=this.getTrackedParticipation(i);d||(this.logger.warn("Attempting to bid without entering auction first",{auctionId:i}),await this.enter(i,void 0,n),d=this.getTrackedParticipation(i),r=this.bumpOperationGeneration(i)),this.logger.info("Placing bid",{auctionId:i,amount:e});const u=`/auctions/${i}/bid`,h=await this.getAuctionDetailsCached(i),l=await this.http.post(u,{amount:e},n),g=c(t,l,{endpoint:u},{throwOnFailure:!0});if(!g.ok)throw g.error;const p=g.value;p.degraded&&this.logger.warn("Bid committed; the server deferred a post-commit effect to replay",{auctionId:i});const m=await this.proveHighestBid(i,p,e),B=f(p.status),S={status:B,amount:e,highestBid:m,bidCount:p.bidCount??(d?.bidCount||0)+1,admissionGrant:p.admissionGrant,...p.degraded?{degraded:!0}:{}};if(!this.canApplyAsyncResult(i,o,void 0,r))return S;if(this.applyTransition(i,this.buildBidTransition(i,S,e,h,p.admissionGrant,d)),b(S))return S;const v=this.lastStatusCache.get(i);return y(v?.status)||this.lastStatusCache.set(i,{auctionId:i,status:B,currentBid:e,highestBid:m,timeRemaining:0,bidCount:S.bidCount,admissionGrant:p.admissionGrant}),S}catch(o){throw this.logger.error("Failed to place bid",{auctionId:i,amount:e,error:o}),this.events.emit("auction:error",{auctionId:i,error:o}),o}}async proveHighestBid(t,e,s){const n=`/auctions/${t}/highest-bid`,a=d(e.winningBidAmount??e.lastBidAmount??s);try{const t=await this.http.get(n),e=c(i,t,{endpoint:n});return e.ok?d(e.value.amount):a}catch(o){return this.logger.warn("Highest-bid read failed after a committed bid; reporting the committed amount",{auctionId:t,error:o}),a}}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 r=`/auctions/${e}/status`,u=`/auctions/${e}/highest-bid`,[h,l,g]=await Promise.allSettled([this.http.get(r),this.http.get(u),this.getAuctionDetailsCached(e)]);if("rejected"===h.status){if("object"==typeof(o=h.reason)&&null!==o&&"status"in o&&404===o.status)return this.applyMissingStatus(e,s,n,a);throw h.reason}if("rejected"===l.status)throw l.reason;if("rejected"===g.status)throw g.reason;const p=h.value,m=l.value,b=g.value,B=c(t,p,{endpoint:r},{throwOnFailure:!0});if(!B.ok)throw B.error;const S=c(i,m,{endpoint:u},{throwOnFailure:!0});if(!S.ok)throw S.error;const v=B.value,w=S.value,C=this.getAuctionSettleAt(b),A=C?new Date(C).getTime():Number.NaN,I=d(w.amount),R=this.getTrackedParticipation(e),T=v.lastBidAmount??v.winningBidAmount,G={auctionId:e,status:f(v.status),currentBid:R?.currentBid??(T?d(T):void 0),highestBid:I,timeRemaining:Number.isFinite(A)?Math.max(0,A-Date.now()):0,bidCount:v.bidCount,admissionGrant:v.admissionGrant};if(!this.canApplyAsyncResult(e,s,n,a))return G;const M=this.lastStatusCache.get(e),k=this.buildStatusTransition(e,G,b,{previousParticipation:R,previousStatus:M});return this.applyTransition(e,k),this.hasSuccessfulReadThisSession.set(e,!0),Number.isFinite(A)?this.lastSettleAtMs.set(e,A):this.lastSettleAtMs.delete(e),y(M?.status)?this.lastStatusCache.set(e,M):y(G.status)?this.lastStatusCache.set(e,G):k.stopWatching?this.lastStatusCache.delete(e):this.lastStatusCache.set(e,G),this.events.emit("auction:status-updated",{auctionId:e,status:G}),G}catch(o){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}var o}applyMissingStatus(t,i,e,s){const n={auctionId:t,status:"not_bid",highestBid:"0.00",timeRemaining:0};if(!this.canApplyAsyncResult(t,i,e,s))return n;const a=this.lastStatusCache.get(t);if(!y(a?.status)){const i=this.buildMissingTransition();this.applyTransition(t,i),this.lastStatusCache.delete(t)}return this.hasSuccessfulReadThisSession.set(t,!0),this.lastSettleAtMs.delete(t),this.events.emit("auction:status-updated",{auctionId:t,status:n}),n}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:d(t.amount),timestamp:t.timestamp,isWinning:t.isHighest,isYours:!0}));return this.bidHistoryCache.set(t,{bids:o,fetchedAt:Date.now()}),this.events.emit("auction:history-fetched",{auctionId:t,bidCount:o.length}),o}catch(i){throw this.logger.error("Failed to get bid history",{auctionId:t,error:i}),this.events.emit("auction:error",{auctionId:t,error:i}),i}}startWatching(t,i=5e3,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&&!y(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(u(i,"0")||u(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=d(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){y(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{B 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/auctions/types.d.ts
CHANGED
|
@@ -36,6 +36,12 @@ export interface BidResult {
|
|
|
36
36
|
bidCount: number;
|
|
37
37
|
admissionGrant?: string;
|
|
38
38
|
position?: number;
|
|
39
|
+
/**
|
|
40
|
+
* The bid committed but the server deferred a post-commit effect — a `won` with no
|
|
41
|
+
* `admissionGrant` here is a live standing whose credential the status lane delivers on a
|
|
42
|
+
* later read, not a settled outcome.
|
|
43
|
+
*/
|
|
44
|
+
degraded?: boolean;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
47
|
* Auction status from API
|
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,n)=>K.startMonitoring(e,t,r,n),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};
|
|
@@ -24,6 +24,11 @@ export interface DisplayError {
|
|
|
24
24
|
readonly retryAfter?: number;
|
|
25
25
|
/** Opaque support reference/confirmation codes — safe to show. */
|
|
26
26
|
readonly support?: ConsumerApiError["support"];
|
|
27
|
+
/**
|
|
28
|
+
* Whether offering the consumer a retry is honest. False where the refusal is
|
|
29
|
+
* settled for them, so an adapter must render no retry affordance.
|
|
30
|
+
*/
|
|
31
|
+
readonly allowRetry: boolean;
|
|
27
32
|
}
|
|
28
33
|
/** Structured debug payload for `onError` telemetry. NOT for rendering. */
|
|
29
34
|
export interface DebugErrorInfo {
|
|
@@ -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.DISTRIBUTION_DEACTIVATED]:"error.deactivated",[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"},i=/* @__PURE__ */new Set([o.DISTRIBUTION_DEACTIVATED,o.DISTRIBUTION_CLOSED,o.DISTRIBUTION_ORDER_LIMIT,o.SOLD_OUT,o.CHECKOUT_MISCONFIGURED]);function n(r){return"INLINE"===r||"STEP_RESET"===r?"error.inlineGeneric":"error.description"}function s(e,s){const{disposition:I}=r(e.code,e.status,e.action),d="number"==typeof e.retryAfter&&Number.isFinite(e.retryAfter)?e.retryAfter:void 0,E=function(r,e,i){return r===o.RATE_LIMITED?i?"error.rateLimited":"error.rateLimitedGeneric":r===o.OVERLOADED?i?"error.overloaded":"error.overloadedGeneric":t[r]??n(e)}(e.code,I,void 0!==d);let O=s(E,void 0!==d?{values:{seconds:d}}:void 0);O.includes("{{")&&(O=s(n(I)));const a="PANEL"===I?s("error.title"):void 0;return{code:e.code,title:a,description:O,disposition:I,retryAfter:e.retryAfter,support:e.support,allowRetry:!i.has(e.code)}}function I(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{I as ensureFanfareError,d as toDebug,s 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"},T=/* @__PURE__ */new Set([E.ENTRY_TOKEN_MISMATCH]);function N(E,N,R){const A=function(E,N){return"reroute"===N&&T.has(E)?"RESTART":I[E]??"PANEL"}(E,R);return{disposition:A,autoRetryOnce:false}}export{N as classify};
|
|
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_DEACTIVATED]:"PANEL",[E.DISTRIBUTION_FULL]:"PANEL",[E.DISTRIBUTION_ORDER_LIMIT]:"PANEL",[E.PROTOCOL_ERROR]:"PANEL",[E.NO_ACCESS]:"PANEL",[E.CHECKOUT_MISCONFIGURED]:"PANEL"},T=/* @__PURE__ */new Set([E.ENTRY_TOKEN_MISMATCH]);function N(E,N,R){const A=function(E,N){return"reroute"===N&&T.has(E)?"RESTART":I[E]??"PANEL"}(E,R);return{disposition:A,autoRetryOnce:false}}export{N 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";
|
|
@@ -44,8 +75,10 @@ export declare const ErrorCodes: {
|
|
|
44
75
|
readonly ADMISSION_GRANT_INVALID: "ADMISSION_GRANT_INVALID";
|
|
45
76
|
readonly ADMISSION_ORIGINAL_ENTRY_REQUIRED: "ADMISSION_ORIGINAL_ENTRY_REQUIRED";
|
|
46
77
|
readonly ADMISSION_KEY_MISMATCH: "ADMISSION_KEY_MISMATCH";
|
|
78
|
+
readonly ADMISSION_ACTIVE: "ADMISSION_ACTIVE";
|
|
47
79
|
readonly DISTRIBUTION_NOT_OPEN: "DISTRIBUTION_NOT_OPEN";
|
|
48
80
|
readonly DISTRIBUTION_CLOSED: "DISTRIBUTION_CLOSED";
|
|
81
|
+
readonly DISTRIBUTION_DEACTIVATED: "DISTRIBUTION_DEACTIVATED";
|
|
49
82
|
readonly DISTRIBUTION_FULL: "DISTRIBUTION_FULL";
|
|
50
83
|
readonly DISTRIBUTION_ORDER_LIMIT: "DISTRIBUTION_ORDER_LIMIT";
|
|
51
84
|
readonly FINGERPRINT_REQUIRED: "FINGERPRINT_REQUIRED";
|
|
@@ -83,7 +116,7 @@ export declare const ErrorCodes: {
|
|
|
83
116
|
* Codes the SDK recognizes. Open-ended via `(string & {})` so an unknown
|
|
84
117
|
* forward-compatible code from a newer server still type-checks at call sites.
|
|
85
118
|
*/
|
|
86
|
-
export type KnownErrorCode = ContractErrorCode | SdkClientErrorCode;
|
|
119
|
+
export type KnownErrorCode = ContractErrorCode | SdkClientErrorCode | CheckoutErrorCode;
|
|
87
120
|
export type ErrorCode = KnownErrorCode | (string & {});
|
|
88
121
|
/** Parses an RFC 7231 Retry-After header (delta-seconds or HTTP-date) into whole seconds, or undefined. */
|
|
89
122
|
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"},E=e,r={...t,...I,...E};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_DEACTIVATED,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.ADMISSION_ACTIVE,t.BOT_CHALLENGE_INVALID,t.BOT_CHALLENGE_EXPIRED,t.BOT_CHALLENGE_SUBJECT_MISMATCH]);class _ extends Error{constructor(e,t,I,E,r,o,N=!1,O,T){super(e),this.name="FanfareError",this.code=t,this.status=I,this.details=E,this.requestId=r,this.correlationId=o,this.retryable=N,this.retryAfter=O,this.issues=T?.issues,this.action=T?.action,this.support=T?.support,this.quietable=T?.quietable,Error.captureStackTrace&&Error.captureStackTrace(this,_)}static fromResponse(e,t,I){const E=e.headers.get("X-Request-Id")||void 0,r=e.headers.get("X-Correlation-Id")||I,n=N(e.headers.get("Retry-After")),{code:R,retryable:i}=T(e.status,t),s=o(t,"message"),A=(s&&e.status<500&&O.has(R)?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,R,e.status,a,E,r,i,n,{issues:D,action:c,support:L})}is(e){return this.code===e}}function T(e,t){const I=429===e||e>=500,E=o(t,"code");return E?{code:E,retryable:I}:429===e?{code:r.RATE_LIMITED,retryable:I}:401===e?{code:r.UNAUTHORIZED,retryable:I}:403===e?{code:r.FORBIDDEN,retryable:I}:413===e?{code:r.PAYLOAD_TOO_LARGE,retryable:I}:422===e||400===e?{code:r.VALIDATION_ERROR,retryable:I}:404===e?{code:r.NOT_FOUND,retryable:I}:423===e?{code:r.DISTRIBUTION_NOT_OPEN,retryable:I}:503===e?{code:r.SERVICE_UNAVAILABLE,retryable:I}:e>=500?{code:r.INTERNAL_ERROR,retryable:I}:{code:`HTTP_${e}`,retryable:I}}class n extends _{constructor(e,t,I,E){super(e,r.PROTOCOL_ERROR,void 0,I,E),this.name="ProtocolError",this.endpoint=t,this.issues=I}}function R(e){return e instanceof _}function i(e){return e instanceof n}const s=/* @__PURE__ */new Set([r.ENTRY_TOKEN_REQUIRED,r.ENTRY_TOKEN_INVALID,r.ENTRY_TOKEN_EXPIRED,r.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,r.NETWORK_ERROR,void 0,t),timeout:(e="Request timed out")=>new _(e,r.TIMEOUT),unauthorized:(e="Unauthorized")=>new _(e,r.UNAUTHORIZED,401),invalidSession:(e="Session is no longer valid")=>new _(e,r.INVALID_SESSION,401),notInitialized:(e="SDK not initialized")=>new _(e,r.NOT_INITIALIZED),invalidConfig:(e,t)=>new _(e,r.INVALID_CONFIG,void 0,t),validationError:(e,t)=>new _(e,r.VALIDATION_ERROR,void 0,t),internalError:(e,t)=>new _(e,r.INTERNAL_ERROR,void 0,t),cannotLeaveAfterBid:e=>new _(`Cannot leave auction ${e} after placing a bid — a bid is a binding commitment`,r.CANNOT_LEAVE_AFTER_BID,void 0,{auctionId:e})};export{E as CheckoutErrorCodes,r as ErrorCodes,_ as FanfareError,n as ProtocolError,I as SdkClientErrorCodes,T as classifyHttpError,L as createError,a as isCapabilityGrantError,c as isCapabilityGrantRerouteError,A as isCapabilityTokenError,D as isCapabilityTokenRerouteError,R 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 +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 +1 @@
|
|
|
1
|
-
import{createError as t,isFanfareError as e}from"../core/errors.js";import{getLogger as i}from"../core/logger.js";import{jitteredIntervalMs as
|
|
1
|
+
import{createError as t,isFanfareError as e}from"../core/errors.js";import{getLogger as i}from"../core/logger.js";import{jitteredIntervalMs as s}from"../core/utils.js";import{DistributionMonitorRuntime as r}from"../experiences/distribution-monitor.runtime.js";import{isEntryActionRequired as n}from"../payment/checkout-contract.js";import{getEventBus as a}from"../state/events.js";import{getSDKStore as o}from"../state/store.js";const h=2147483647;class c{constructor(t){this.logger=i(),this.events=a(),this.trackedParticipations=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.scheduledChecks=/* @__PURE__ */new Map,this.settlingTimers=/* @__PURE__ */new Map,this.monitorRuntime=new r,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.drawTimeMs=/* @__PURE__ */new Map,this.initialCheckBufferMs=5e3,this.retryCheckDelayMs=5e3,this.retryAfterCapMs=6e4,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}get store(){return o()}async get(t){try{return await this.http.get(`/draws/${t}`)}catch(e){throw this.logger.error("Failed to get draw",{drawId:t,error:e}),e}}async enter(e,i,s,r){try{const a=this.lifecycleGeneration;if(!this.store.session)throw t.unauthorized("Not authenticated. Call auth.guest() or auth.login() first");const o=this.bumpOperationGeneration(e);this.logger.info("Entering draw",{drawId:e});const h={metadata:i};r&&(h.paymentInput=r);const c=await this.http.post(`/draws/${e}/enter`,h,s);if(n(c))return c;const d=this.buildTransition(e,c,{source:"enter",enteredAt:/* @__PURE__ */(new Date).toISOString(),metadata:i});return"ENTERED"!==c.status&&"WON"!==c.status&&"COMPLETED"!==c.status||d.events.push({type:"experience:distribution-entered",payload:{distributionType:"draw",distributionId:e}}),this.canApplyAsyncResult(e,a,void 0,o)?(this.applyTransition(e,d),c):c}catch(a){throw this.logger.error("Failed to enter draw",{drawId:e,error:a}),this.events.emit("draw:error",{drawId:e,error:a}),a}}async leave(t,e){try{const i=this.lifecycleGeneration,s=this.bumpOperationGeneration(t);if(this.logger.info("Leaving draw",{drawId:t}),await this.http.post(`/draws/${t}/leave`,void 0,e),!this.canApplyAsyncResult(t,i,void 0,s))return;this.trackedParticipations.delete(t),this.stopMonitoring(t),this.events.emit("draw:left",{drawId:t})}catch(i){throw this.logger.error("Failed to leave draw",{drawId:t,error:i}),this.events.emit("draw:error",{drawId:t,error:i}),i}}async status(t){const e=this.inFlightRequests.get(t);if(e)return this.logger.debug("Returning existing in-flight request",{drawId:t}),e;const i=this.lifecycleGeneration,s=this.bumpOperationGeneration(t),r=this.doStatus(t,i,void 0,s).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,r),r}async doStatus(t,e,i,s){try{const r=await this.http.get(`/draws/${t}/status`);if(!this.canApplyAsyncResult(t,e,i,s))return r;const n=this.buildTransition(t,r,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,n),this.events.emit("draw:status-updated",{drawId:t,status:r.status}),r}catch(r){if(r&&"object"==typeof r&&"status"in r&&404===r.status){const r={status:"NOT_ENTERED"};if(!this.canApplyAsyncResult(t,e,i,s))return r;const n=this.buildTransition(t,r,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,n),this.events.emit("draw:status-updated",{drawId:t,status:r.status}),r}if(!this.canApplyAsyncResult(t,e,i,s))return this.trackedParticipations.has(t)?{status:"ENTERED"}:{status:"NOT_ENTERED"};throw this.logger.error("Failed to get draw status",{drawId:t,error:r}),this.events.emit("draw:error",{drawId:t,error:r}),r}}startMonitoring(t,e,i){this.monitorRuntime.start(t,e,i),this.bumpMonitorGeneration(t);const s=e?.drawTime;s?this.drawTimeMs.set(t,new Date(s).getTime()):this.drawTimeMs.delete(t);const r=this.monitorRuntime.getDisplayAtom(t);r&&r.set({type:"draw",drawTime:s,entryNumber:this.trackedParticipations.get(t)?.entryNumber}),s&&"settling"!==e?.phase?this.scheduleSettlingTransition(t,s):this.cancelSettlingTransition(t),"settling"===e?.phase?this.scheduleStatusCheck(t,0):s&&this.scheduleInitialStatusCheck(t,s)}stopMonitoring(t){this.bumpMonitorGeneration(t),this.cancelScheduledCheck(t),this.cancelSettlingTransition(t),this.drawTimeMs.delete(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.monitorRuntime.has(t)||this.scheduledChecks.has(t)||this.settlingTimers.has(t)}buildTransition(t,e,{source:i,previousParticipation:s,enteredAt:r,metadata:n}){switch(e.status){case"ENTERED":return{trackedParticipation:{drawId:t,enteredAt:s?.enteredAt??r??/* @__PURE__ */(new Date).toISOString(),entryNumber:s?.entryNumber,metadata:s?.metadata??n},events:"enter"===i?[{type:"draw:entered",payload:{drawId:t}}]:[],monitorUpdate:null,stopMonitoring:!1,retryStatusCheck:"status"===i};case"WON":return"reservation"in e?{trackedParticipation:null,events:[],monitorUpdate:"status"===i?{type:"reserved",token:e.reservation.token,expiresAt:new Date(e.reservation.expiresAt).getTime()}:null,stopMonitoring:"status"===i,retryStatusCheck:!1}:{trackedParticipation:null,events:[{type:"draw:won",payload:{drawId:t,result:{won:!0,drawTime:e.wonAt}}}],monitorUpdate:"status"===i?{type:"granted",token:e.admissionGrant,expiresAt:e.expiresAt?new Date(e.expiresAt).getTime():void 0}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"COMPLETED":return{trackedParticipation:null,events:[],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"completed"}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"DENIED":return{trackedParticipation:null,events:[{type:"draw:expired",payload:{drawId:t}}],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"denied",reason:e.reason}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1};case"NOT_ENTERED":return{trackedParticipation:null,events:s?[{type:"draw:lost",payload:{drawId:t,result:this.buildLostResult()}}]:[],monitorUpdate:"status"===i?{type:"ended",outcome:s?{type:"lost"}:{type:"closed",reason:"not_entered"}}:null,stopMonitoring:"status"===i,retryStatusCheck:!1}}}applyTransition(t,e){e.trackedParticipation?this.trackedParticipations.set(t,e.trackedParticipation):this.trackedParticipations.delete(t);for(const i of e.events)this.events.emit(i.type,i.payload);e.monitorUpdate&&this.monitorRuntime.notify(t,e.monitorUpdate),e.stopMonitoring?this.stopMonitoring(t):e.retryStatusCheck&&this.monitorRuntime.has(t)&&this.scheduleRetryStatusCheck(t)}scheduleInitialStatusCheck(t,e){const i=new Date(e).getTime(),s=Date.now(),r=i+this.initialCheckBufferMs,n=r<=s?s:r+Math.round(2e3*Math.random());this.scheduleStatusCheckAt(t,n)}scheduleSettlingTransition(t,e){const i=new Date(e).getTime(),s=Math.max(0,i-Date.now());this.cancelSettlingTransition(t);const r=this.monitorGenerations.get(t),n=setTimeout(()=>{this.settlingTimers.delete(t),void 0!==r&&this.monitorGenerations.get(t)===r&&(Date.now()<i?this.scheduleSettlingTransition(t,e):this.monitorRuntime.notify(t,{type:"settling",at:e},!1))},Math.min(s,h));this.settlingTimers.set(t,n)}cancelSettlingTransition(t){const e=this.settlingTimers.get(t);e&&clearTimeout(e),this.settlingTimers.delete(t)}scheduleRetryStatusCheck(t,i){const r=(e(i)&&"number"==typeof i.retryAfter&&Number.isFinite(i.retryAfter)?Math.min(1e3*i.retryAfter,this.retryAfterCapMs):void 0)??s(this.retryCheckDelayMs);this.scheduleStatusCheck(t,this.isInSettlingWindow(t)?Math.min(r,this.retryCheckDelayMs):r)}isInSettlingWindow(t){const e=this.drawTimeMs.get(t);return void 0!==e&&Date.now()>=e}scheduleStatusCheck(t,i){this.cancelScheduledCheck(t);const s=this.monitorGenerations.get(t),r=this.getOperationGeneration(t),n=setTimeout(async()=>{this.scheduledChecks.delete(t);try{if(void 0===s)return;await this.doStatus(t,this.lifecycleGeneration,s,r)}catch(i){if(!this.canApplyAsyncResult(t,this.lifecycleGeneration,s,r))return;if(e(i)&&i.retryable&&this.monitorRuntime.has(t))return void this.scheduleRetryStatusCheck(t,i);this.stopMonitoring(t)}},i);this.scheduledChecks.set(t,n)}scheduleStatusCheckAt(t,e){const i=Math.max(0,e-Date.now());if(i<=h)return void this.scheduleStatusCheck(t,i);this.cancelScheduledCheck(t);const s=this.monitorGenerations.get(t),r=setTimeout(()=>{this.scheduledChecks.delete(t),void 0!==s&&this.monitorGenerations.get(t)===s&&this.scheduleStatusCheckAt(t,e)},h);this.scheduledChecks.set(t,r)}cancelScheduledCheck(t){const e=this.scheduledChecks.get(t);e&&(clearTimeout(e),this.scheduledChecks.delete(t))}buildLostResult(){return{won:!1,drawTime:/* @__PURE__ */(new Date).toISOString()}}bumpMonitorGeneration(t){const e=(this.monitorGenerations.get(t)??0)+1;return this.monitorGenerations.set(t,e),e}getOperationGeneration(t){return this.operationGenerations.get(t)??0}bumpOperationGeneration(t){const e=this.getOperationGeneration(t)+1;return this.operationGenerations.set(t,e),e}canApplyAsyncResult(t,e,i,s){return!this.destroyed&&this.lifecycleGeneration===e&&((void 0===i||this.monitorGenerations.get(t)===i)&&(void 0===s||this.getOperationGeneration(t)===s))}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.scheduledChecks.forEach(t=>clearTimeout(t)),this.scheduledChecks.clear(),this.settlingTimers.forEach(t=>clearTimeout(t)),this.settlingTimers.clear(),this.inFlightRequests.clear(),this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.drawTimeMs.clear(),this.monitorRuntime.clear()}}export{c as DrawManagementModule};
|