@fanfare-io/fanfare-sdk-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +44 -0
- package/dist/adapters/google-analytics.d.ts +12 -0
- package/dist/adapters/google-analytics.js +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/types.d.ts +9 -0
- package/dist/appointments/appointment.module.d.ts +32 -0
- package/dist/appointments/appointment.module.js +1 -0
- package/dist/appointments/index.d.ts +2 -0
- package/dist/appointments/public.d.ts +1 -0
- package/dist/appointments/public.js +1 -0
- package/dist/appointments/types.d.ts +47 -0
- package/dist/auctions/auction.module.d.ts +58 -0
- package/dist/auctions/auction.module.js +1 -0
- package/dist/auctions/index.d.ts +5 -0
- package/dist/auctions/public.d.ts +5 -0
- package/dist/auctions/public.js +1 -0
- package/dist/auctions/types.d.ts +97 -0
- package/dist/auth/auth.module.d.ts +71 -0
- package/dist/auth/auth.module.js +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/types.d.ts +112 -0
- package/dist/beacon/batching.d.ts +52 -0
- package/dist/beacon/batching.js +1 -0
- package/dist/beacon/beacon.module.d.ts +58 -0
- package/dist/beacon/beacon.module.js +1 -0
- package/dist/beacon/enrichment.d.ts +16 -0
- package/dist/beacon/enrichment.js +1 -0
- package/dist/beacon/index.d.ts +12 -0
- package/dist/beacon/public.d.ts +9 -0
- package/dist/beacon/public.js +1 -0
- package/dist/beacon/types.d.ts +103 -0
- package/dist/beacon/validation.d.ts +24 -0
- package/dist/beacon/validation.js +1 -0
- package/dist/challenges/challenge.module.d.ts +9 -0
- package/dist/challenges/challenge.module.js +1 -0
- package/dist/challenges/index.d.ts +3 -0
- package/dist/challenges/public.d.ts +9 -0
- package/dist/challenges/public.js +1 -0
- package/dist/challenges/types.d.ts +33 -0
- package/dist/config/index.d.ts +44 -0
- package/dist/config/index.js +1 -0
- package/dist/core/client.d.ts +15 -0
- package/dist/core/client.js +2 -0
- package/dist/core/errors.d.ts +175 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/http.d.ts +87 -0
- package/dist/core/http.js +1 -0
- package/dist/core/logger.d.ts +46 -0
- package/dist/core/logger.js +1 -0
- package/dist/core/money.d.ts +10 -0
- package/dist/core/money.js +1 -0
- package/dist/core/parse-response.d.ts +62 -0
- package/dist/core/parse-response.js +1 -0
- package/dist/core/utils.d.ts +67 -0
- package/dist/core/utils.js +1 -0
- package/dist/draws/draw.module.d.ts +40 -0
- package/dist/draws/draw.module.js +1 -0
- package/dist/draws/index.d.ts +5 -0
- package/dist/draws/public.d.ts +6 -0
- package/dist/draws/public.js +1 -0
- package/dist/draws/types.d.ts +90 -0
- package/dist/draws/types.js +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +1 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.js +1 -0
- package/dist/experiences/distribution-monitor.runtime.d.ts +12 -0
- package/dist/experiences/distribution-monitor.runtime.js +1 -0
- package/dist/experiences/distribution-monitor.types.d.ts +62 -0
- package/dist/experiences/experience.module.d.ts +88 -0
- package/dist/experiences/experience.module.js +1 -0
- package/dist/experiences/index.d.ts +3 -0
- package/dist/experiences/index.js +1 -0
- package/dist/experiences/journey-contract.fixtures.d.ts +9 -0
- package/dist/experiences/journey-view.d.ts +22 -0
- package/dist/experiences/journey-view.js +1 -0
- package/dist/experiences/journey.d.ts +89 -0
- package/dist/experiences/journey.js +1 -0
- package/dist/experiences/journey.machine.d.ts +79 -0
- package/dist/experiences/journey.machine.js +1 -0
- package/dist/experiences/journey.types.d.ts +395 -0
- package/dist/experiences/public.d.ts +13 -0
- package/dist/experiences/public.js +1 -0
- package/dist/experiences/types.d.ts +161 -0
- package/dist/handoff/handoff.module.d.ts +184 -0
- package/dist/handoff/handoff.module.js +1 -0
- package/dist/handoff/index.d.ts +5 -0
- package/dist/handoff/index.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1 -0
- package/dist/internals.d.ts +11 -0
- package/dist/internals.js +1 -0
- package/dist/queues/index.d.ts +5 -0
- package/dist/queues/index.js +1 -0
- package/dist/queues/qualitative-bucket.d.ts +12 -0
- package/dist/queues/qualitative-bucket.js +1 -0
- package/dist/queues/queue.module.d.ts +42 -0
- package/dist/queues/queue.module.js +1 -0
- package/dist/queues/types.d.ts +112 -0
- package/dist/queues/types.js +1 -0
- package/dist/security/admission-proof.d.ts +15 -0
- package/dist/security/admission-proof.js +1 -0
- package/dist/state/capability-token-registry.d.ts +44 -0
- package/dist/state/capability-token-registry.js +1 -0
- package/dist/state/events.d.ts +342 -0
- package/dist/state/events.js +1 -0
- package/dist/state/store.d.ts +48 -0
- package/dist/state/store.js +1 -0
- package/dist/sync/broadcast-transport.d.ts +19 -0
- package/dist/sync/broadcast-transport.js +1 -0
- package/dist/sync/cross-tab-coordinator.d.ts +44 -0
- package/dist/sync/cross-tab-coordinator.js +1 -0
- package/dist/sync/index.d.ts +8 -0
- package/dist/sync/localstorage-transport.d.ts +32 -0
- package/dist/sync/localstorage-transport.js +1 -0
- package/dist/sync/protocol-transport.d.ts +51 -0
- package/dist/sync/protocol-transport.js +1 -0
- package/dist/sync/protocol.d.ts +254 -0
- package/dist/sync/protocol.js +1 -0
- package/dist/sync/recovery.d.ts +68 -0
- package/dist/sync/transport-factory.d.ts +16 -0
- package/dist/sync/transport-factory.js +1 -0
- package/dist/sync/transport-interface.d.ts +33 -0
- package/dist/sync/transport.d.ts +49 -0
- package/dist/sync/transport.js +1 -0
- package/dist/test-utils/harness-scenarios.d.ts +71 -0
- package/dist/test-utils/harness-scenarios.js +1 -0
- package/dist/test-utils/index.d.ts +12 -0
- package/dist/test-utils/index.js +1 -0
- package/dist/test-utils/mock-journey.d.ts +63 -0
- package/dist/test-utils/mock-journey.js +1 -0
- package/dist/test-utils/mock-sdk-vitest.d.ts +80 -0
- package/dist/test-utils/mock-sdk-vitest.js +1 -0
- package/dist/test-utils/mock-sdk.d.ts +22 -0
- package/dist/test-utils/mock-sdk.js +1 -0
- package/dist/test-utils/mock-server.d.ts +105 -0
- package/dist/test-utils/mock-server.js +1 -0
- package/dist/test-utils/sdk-factory.d.ts +3 -0
- package/dist/test-utils/sdk-factory.js +1 -0
- package/dist/test-utils/verification-scenarios.d.ts +43 -0
- package/dist/test-utils/verification-scenarios.js +1 -0
- package/dist/test-utils/verification-state.d.ts +1 -0
- package/dist/test-utils/verification-state.js +1 -0
- package/dist/theme.d.ts +8 -0
- package/dist/theme.js +1 -0
- package/dist/timed-releases/index.d.ts +6 -0
- package/dist/timed-releases/public.d.ts +6 -0
- package/dist/timed-releases/public.js +1 -0
- package/dist/timed-releases/timed-release.module.d.ts +31 -0
- package/dist/timed-releases/timed-release.module.js +1 -0
- package/dist/timed-releases/types.d.ts +70 -0
- package/dist/timed-releases/types.js +1 -0
- package/dist/types/distribution-type.d.ts +45 -0
- package/dist/types/index.d.ts +178 -0
- package/dist/utils/fingerprint.module.d.ts +27 -0
- package/dist/utils/fingerprint.module.js +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +1 -0
- package/dist/waitlists/index.d.ts +2 -0
- package/dist/waitlists/public.d.ts +6 -0
- package/dist/waitlists/public.js +1 -0
- package/dist/waitlists/types.d.ts +50 -0
- package/dist/waitlists/types.js +1 -0
- package/dist/waitlists/waitlist.module.d.ts +17 -0
- package/dist/waitlists/waitlist.module.js +1 -0
- package/package.json +205 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { HttpClient } from '../core/http';
|
|
2
|
+
import { AdmissionProofHeaders } from '../security/admission-proof';
|
|
3
|
+
import { SdkHandoffDistributionType } from '../types/distribution-type';
|
|
4
|
+
/**
|
|
5
|
+
* Parameters for minting a handoff token. consumerId/organizationId/experience
|
|
6
|
+
* are NOT accepted from the caller — the server derives them from the verified
|
|
7
|
+
* capability grant so a client cannot mint a token for another consumer, org,
|
|
8
|
+
* or experience. The grant is the consumer's proof that it holds admission to
|
|
9
|
+
* the experience; it lives in journey routing state
|
|
10
|
+
* (`routing.selected.capabilityGrant`).
|
|
11
|
+
*/
|
|
12
|
+
export interface CreateHandoffTokenParams {
|
|
13
|
+
capabilityGrant: string;
|
|
14
|
+
expiresIn?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parameters for building a handoff URL
|
|
18
|
+
*/
|
|
19
|
+
export interface BuildHandoffUrlParams {
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
token: string;
|
|
22
|
+
additionalParams?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Admission context returned by the server on redemption
|
|
26
|
+
*/
|
|
27
|
+
export interface AdmissionContext {
|
|
28
|
+
consumerId: string;
|
|
29
|
+
organizationId: string;
|
|
30
|
+
experienceId: string;
|
|
31
|
+
experienceType: SdkHandoffDistributionType;
|
|
32
|
+
/**
|
|
33
|
+
* Origin of the merchant page that minted the handoff, attested by the
|
|
34
|
+
* server (signed into the token, captured from the mint request's `Origin`
|
|
35
|
+
* header). Used as the `postMessage` target origin when the checkout iframe
|
|
36
|
+
* reports completion to the merchant parent. Absent when the server captured
|
|
37
|
+
* no origin at mint time.
|
|
38
|
+
*/
|
|
39
|
+
sourceOrigin?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Stored admission data in sessionStorage
|
|
43
|
+
*/
|
|
44
|
+
export interface StoredAdmission {
|
|
45
|
+
context: AdmissionContext;
|
|
46
|
+
storedAt: string;
|
|
47
|
+
/**
|
|
48
|
+
* ISO-8601 timestamp echoing the server-verified handoff token expiry. The
|
|
49
|
+
* admission is only valid until this instant; past it, the stored admission
|
|
50
|
+
* is cleared and checkout is no longer permitted.
|
|
51
|
+
*/
|
|
52
|
+
expiresAt: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validation result for stored admission
|
|
56
|
+
*/
|
|
57
|
+
export interface AdmissionValidationResult {
|
|
58
|
+
valid: boolean;
|
|
59
|
+
reason?: string;
|
|
60
|
+
admission?: StoredAdmission;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Result of redeeming a handoff token
|
|
64
|
+
*/
|
|
65
|
+
export interface ProcessHandoffResult {
|
|
66
|
+
context: AdmissionContext;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parameters for completing checkout
|
|
70
|
+
*/
|
|
71
|
+
export interface CompleteCheckoutParams {
|
|
72
|
+
success: boolean;
|
|
73
|
+
orderId?: string;
|
|
74
|
+
reason?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Checkout status information
|
|
78
|
+
*/
|
|
79
|
+
export interface CheckoutStatus {
|
|
80
|
+
hasAdmission: boolean;
|
|
81
|
+
admission?: StoredAdmission;
|
|
82
|
+
canCheckout: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parameters for building checkout URL with admission
|
|
86
|
+
*/
|
|
87
|
+
export interface BuildCheckoutUrlParams {
|
|
88
|
+
baseUrl: string;
|
|
89
|
+
admission: StoredAdmission;
|
|
90
|
+
additionalParams?: Record<string, string>;
|
|
91
|
+
}
|
|
92
|
+
export interface BuildAdmissionProofHeadersParams {
|
|
93
|
+
url: string;
|
|
94
|
+
method?: string;
|
|
95
|
+
nonce?: string;
|
|
96
|
+
}
|
|
97
|
+
export interface HandoffModuleOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Merchant origins allowed to receive embedded checkout postMessage events.
|
|
100
|
+
* When provided, embedded messages to any other source origin are dropped.
|
|
101
|
+
* Values must be exact origins like "https://shop.example.com";
|
|
102
|
+
* an empty array intentionally denies all source origins.
|
|
103
|
+
*/
|
|
104
|
+
allowedSourceOrigins?: string[];
|
|
105
|
+
}
|
|
106
|
+
export type { AdmissionProofHeaders } from '../security/admission-proof';
|
|
107
|
+
/**
|
|
108
|
+
* Message sent to parent window in embedded mode
|
|
109
|
+
*/
|
|
110
|
+
export interface EmbeddedMessage {
|
|
111
|
+
type: string;
|
|
112
|
+
payload: unknown;
|
|
113
|
+
source: "fanfare-sdk";
|
|
114
|
+
timestamp: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Handoff module for managing server-minted handoff tokens
|
|
118
|
+
*/
|
|
119
|
+
export declare class HandoffModule {
|
|
120
|
+
private logger;
|
|
121
|
+
private readonly http;
|
|
122
|
+
private readonly allowedSourceOrigins?;
|
|
123
|
+
private readonly ADMISSION_KEY;
|
|
124
|
+
constructor(http: HttpClient, options?: HandoffModuleOptions);
|
|
125
|
+
/**
|
|
126
|
+
* Request a server-minted handoff token.
|
|
127
|
+
*
|
|
128
|
+
* @param params Mint parameters
|
|
129
|
+
* @returns Opaque, signed, short-TTL token string
|
|
130
|
+
*/
|
|
131
|
+
createHandoffToken(params: CreateHandoffTokenParams): Promise<string>;
|
|
132
|
+
/**
|
|
133
|
+
* Build a handoff URL with the token and additional parameters
|
|
134
|
+
*/
|
|
135
|
+
buildHandoffUrl(params: BuildHandoffUrlParams): string;
|
|
136
|
+
/**
|
|
137
|
+
* Redeem a handoff token via the server and store the admission context.
|
|
138
|
+
*
|
|
139
|
+
* The server verifies the signature, enforces expiry, and enforces
|
|
140
|
+
* single-use (replay rejection). This method does not inspect the token.
|
|
141
|
+
*
|
|
142
|
+
* @param token Opaque handoff token
|
|
143
|
+
* @returns Admission context
|
|
144
|
+
* @throws Error if the server rejects the token (invalid, expired, replayed)
|
|
145
|
+
*/
|
|
146
|
+
processHandoffToken(token: string): Promise<ProcessHandoffResult>;
|
|
147
|
+
/**
|
|
148
|
+
* Validate stored admission
|
|
149
|
+
*/
|
|
150
|
+
validateAdmission(): Promise<AdmissionValidationResult>;
|
|
151
|
+
getAdmissionKeyThumbprint(): Promise<string | null>;
|
|
152
|
+
buildAdmissionProofHeaders(params: BuildAdmissionProofHeadersParams): Promise<AdmissionProofHeaders>;
|
|
153
|
+
buildAdmissionGrantValidationHeaders(params: BuildAdmissionProofHeadersParams): Promise<AdmissionProofHeaders>;
|
|
154
|
+
private storeAdmission;
|
|
155
|
+
private getStoredAdmission;
|
|
156
|
+
private clearAdmission;
|
|
157
|
+
/**
|
|
158
|
+
* Clear all handoff-related data from sessionStorage
|
|
159
|
+
*/
|
|
160
|
+
clearHandoffData(): void;
|
|
161
|
+
/**
|
|
162
|
+
* Complete the checkout process
|
|
163
|
+
*/
|
|
164
|
+
completeCheckout(params: CompleteCheckoutParams): Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
* Get the current checkout status
|
|
167
|
+
*/
|
|
168
|
+
getCheckoutStatus(): Promise<CheckoutStatus>;
|
|
169
|
+
/**
|
|
170
|
+
* Build a checkout URL with admission data
|
|
171
|
+
*/
|
|
172
|
+
buildCheckoutUrlWithAdmission(params: BuildCheckoutUrlParams): string;
|
|
173
|
+
/**
|
|
174
|
+
* Send a message to parent window if in embedded mode
|
|
175
|
+
*/
|
|
176
|
+
private sendEmbeddedMessage;
|
|
177
|
+
private resolveEmbeddedTargetOrigin;
|
|
178
|
+
private requireAllowedSourceOrigin;
|
|
179
|
+
private requireValidOrigin;
|
|
180
|
+
/**
|
|
181
|
+
* Check if running in embedded mode (iframe)
|
|
182
|
+
*/
|
|
183
|
+
isEmbedded(): boolean;
|
|
184
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getLogger as e}from"../core/logger.js";import{isBrowser as r}from"../core/utils.js";import{getAdmissionKeyThumbprint as o,buildAdmissionProofHeaders as t}from"../security/admission-proof.js";const n="X-Sequence-Capability-Grant";class i{constructor(r,o={}){this.logger=e(),this.ADMISSION_KEY="fanfare_admission_grant",this.http=r,o.allowedSourceOrigins&&(this.allowedSourceOrigins=new Set(o.allowedSourceOrigins.map(e=>this.requireValidOrigin(e))))}async createHandoffToken(e){const{capabilityGrant:r,expiresIn:o}=e;this.logger.debug("Requesting handoff token",{expiresIn:o});try{const e=await this.http.post("/handoff/mint",void 0!==o?{expiresInSeconds:o}:{},{headers:{[n]:r}});return this.logger.info("Handoff token minted",{expiresAt:e.expiresAt,tokenLength:e.token.length}),e.token}catch(t){const e=t instanceof Error?t:new Error(String(t));throw this.logger.error("Failed to mint handoff token",e),e}}buildHandoffUrl(e){const{baseUrl:r,token:o,additionalParams:t={}}=e;try{const e=new URL(r);e.searchParams.set("fanfare_token",o);for(const[r,o]of Object.entries(t))e.searchParams.set(r,o);return e.toString()}catch(n){const e=n instanceof Error?n:new Error(String(n));throw this.logger.error("Failed to build handoff URL",e),e}}async processHandoffToken(e){if(!r())throw new Error("Handoff tokens can only be processed in browser environment");let o;this.logger.debug("Redeeming handoff token",{tokenLength:e.length});try{o=await this.http.post("/handoff/redeem",{token:e})}catch(i){const e=i instanceof Error?i:new Error(String(i));throw this.logger.error("Failed to redeem handoff token",e),e}const t={consumerId:o.consumerId,organizationId:o.organizationId,experienceId:o.experienceId,experienceType:o.experienceType,...o.sourceOrigin?{sourceOrigin:o.sourceOrigin}:{}},n={context:t,storedAt:/* @__PURE__ */(new Date).toISOString(),expiresAt:o.expiresAt};return this.storeAdmission(n),this.logger.info("Handoff token redeemed",{consumerId:t.consumerId,organizationId:t.organizationId,experienceId:t.experienceId}),{context:t}}async validateAdmission(){if(!r())return{valid:!1,reason:"Not in browser environment"};try{const e=this.getStoredAdmission();if(!e)return{valid:!1,reason:"No admission found"};const r=Date.parse(e.expiresAt);if(Number.isNaN(r)||Date.now()>r)return this.clearAdmission(),{valid:!1,reason:"Admission has expired"};return await o()?{valid:!0,admission:e}:(this.logger.warn("Admission proof key is unavailable for stored admission"),this.clearAdmission(),{valid:!1,reason:"Admission proof is unavailable"})}catch(e){const r=e instanceof Error?e:new Error(String(e));return this.logger.error("Failed to validate admission",r),{valid:!1,reason:"Validation error: "+r.message}}}async getAdmissionKeyThumbprint(){return await o()}async buildAdmissionProofHeaders(e){const r=await t(e);if(!r)throw new Error("DPoP proof generation requires browser crypto support");return r}async buildAdmissionGrantValidationHeaders(e){return await this.buildAdmissionProofHeaders(e)}storeAdmission(e){try{if("undefined"==typeof sessionStorage)return void this.logger.warn("SessionStorage not available");sessionStorage.setItem(this.ADMISSION_KEY,JSON.stringify(e))}catch(r){this.logger.error("Failed to store admission",r instanceof Error?r:new Error(String(r)))}}getStoredAdmission(){try{if("undefined"==typeof sessionStorage)return null;const e=sessionStorage.getItem(this.ADMISSION_KEY);return e?JSON.parse(e):null}catch(e){return this.logger.error("Failed to get stored admission",e instanceof Error?e:new Error(String(e))),null}}clearAdmission(){try{if("undefined"==typeof sessionStorage)return;sessionStorage.removeItem(this.ADMISSION_KEY)}catch(e){this.logger.error("Failed to clear admission",e instanceof Error?e:new Error(String(e)))}}clearHandoffData(){try{if("undefined"==typeof sessionStorage)return;sessionStorage.removeItem(this.ADMISSION_KEY)}catch(e){this.logger.error("Failed to clear handoff data",e instanceof Error?e:new Error(String(e)))}}async completeCheckout(e){const{success:r,orderId:o,reason:t}=e;try{const e=this.getStoredAdmission();if(!e)return void this.logger.warn("No admission found when completing checkout");this.logger.info("Checkout completion",{success:r,orderId:o,reason:t,consumerId:e.context.consumerId,organizationId:e.context.organizationId,experienceId:e.context.experienceId}),r?(this.sendEmbeddedMessage({type:"checkout-success",payload:{orderId:o,consumerId:e.context.consumerId,organizationId:e.context.organizationId,experienceId:e.context.experienceId}}),this.clearAdmission()):this.sendEmbeddedMessage({type:"checkout-failure",payload:{reason:t,canRetry:!0,consumerId:e.context.consumerId,organizationId:e.context.organizationId,experienceId:e.context.experienceId}})}catch(n){const e=n instanceof Error?n:new Error(String(n));throw this.logger.error("Failed to complete checkout",e),e}}async getCheckoutStatus(){try{const e=await this.validateAdmission();return e.valid&&e.admission?{hasAdmission:!0,admission:e.admission,canCheckout:!0}:{hasAdmission:!1,canCheckout:!1}}catch(e){const r=e instanceof Error?e:new Error(String(e));return this.logger.error("Failed to get checkout status",r),{hasAdmission:!1,canCheckout:!1}}}buildCheckoutUrlWithAdmission(e){const{baseUrl:r,admission:o,additionalParams:t={}}=e;try{const e=new URL(r);e.searchParams.set("fanfare_consumer",o.context.consumerId),e.searchParams.set("fanfare_org",o.context.organizationId),e.searchParams.set("fanfare_experience",o.context.experienceId),e.searchParams.set("fanfare_type",o.context.experienceType),e.searchParams.set("fanfare_expires",o.expiresAt);for(const[r,o]of Object.entries(t))e.searchParams.set(r,o);return e.toString()}catch(n){const e=n instanceof Error?n:new Error(String(n));throw this.logger.error("Failed to build checkout URL with admission",e),e}}sendEmbeddedMessage(e){try{if(!r()||window.parent===window)return;const o={...e,source:"fanfare-sdk",timestamp:/* @__PURE__ */(new Date).toISOString()},t=this.resolveEmbeddedTargetOrigin();if(!t)return;window.parent.postMessage(o,t)}catch(o){this.logger.error("Failed to send embedded message",o instanceof Error?o:new Error(String(o)))}}resolveEmbeddedTargetOrigin(){if(!r())return null;const e=this.getStoredAdmission()?.context.sourceOrigin;if(!e)return this.logger.warn("No source origin on stored admission; cannot address embedded checkout message"),null;try{return this.requireAllowedSourceOrigin(e)}catch(o){return this.logger.warn("Blocked embedded message to untrusted source origin",{reason:o instanceof Error?o.message:String(o)}),null}}requireAllowedSourceOrigin(e){const r=this.requireValidOrigin(e);if(this.allowedSourceOrigins&&!this.allowedSourceOrigins.has(r))throw new Error("Source origin is not allowlisted");return r}requireValidOrigin(e){let r;try{r=new URL(e)}catch{throw new Error("Source origin is invalid")}if("https:"!==r.protocol&&"http:"!==r.protocol||r.origin!==e)throw new Error("Source origin must be an absolute HTTP(S) origin");return r.origin}isEmbedded(){if(!r())return!1;try{return window.parent!==window}catch{return!1}}}export{i as HandoffModule};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{HandoffModule as o}from"./handoff.module.js";export{o as HandoffModule};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fanfare Browser SDK
|
|
3
|
+
*
|
|
4
|
+
* Official SDK for integrating Fanfare's queue, draw, auction,
|
|
5
|
+
* and appointment experiences into your website.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
export { init as default, init } from './core/client';
|
|
10
|
+
export type { FanfareConfig, FanfareSDK } from './types';
|
|
11
|
+
export { version } from './version';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{init as i,init as o}from"./core/client.js";import{version as r}from"./version.js";export{i as default,o as init,r as version};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal adapter surface. Exists for the first-party framework adapters
|
|
3
|
+
* (fanfare-sdk-react/-solid) and internal tooling; it is NOT customer API and
|
|
4
|
+
* carries no stability guarantee — symbols here may change or disappear in any
|
|
5
|
+
* release. Customer-facing API lives on the domain subpaths (see
|
|
6
|
+
* docs/PUBLIC_SURFACE.md).
|
|
7
|
+
*/
|
|
8
|
+
export type { AppointmentDisplayState, AuctionDisplayState, DistributionDisplayState, DistributionMonitor, DrawDisplayState, MonitorUpdate, QueueDisplayState, TimedReleaseDisplayState, } from './experiences/distribution-monitor.types';
|
|
9
|
+
export { SnapshotInvariantError, deriveSequenceState, getAdmissionGrant, getAdmissionGrantExpiresAt, getDistribution, getGates, getOffers, getParticipation, getSelected, getSequenceId, getSequencePhase, getSequenceState, isGateState, isRoutedState, } from './experiences/journey.machine';
|
|
10
|
+
export { generateFingerprint, generateFingerprintSync } from './utils/fingerprint.module';
|
|
11
|
+
export type { BrowserFingerprint } from './utils/fingerprint.module';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{SnapshotInvariantError as e,deriveSequenceState as r,getAdmissionGrant as i,getAdmissionGrantExpiresAt as o,getDistribution as m,getGates as n,getOffers as p,getParticipation as t,getSelected as s,getSequenceId as f,getSequencePhase as j,getSequenceState as u,isGateState as c,isRoutedState as l}from"./experiences/journey.machine.js";import{generateFingerprint as x,generateFingerprintSync as a}from"./utils/fingerprint.module.js";export{e as SnapshotInvariantError,r as deriveSequenceState,x as generateFingerprint,a as generateFingerprintSync,i as getAdmissionGrant,o as getAdmissionGrantExpiresAt,m as getDistribution,n as getGates,p as getOffers,t as getParticipation,s as getSelected,f as getSequenceId,j as getSequencePhase,u as getSequenceState,c as isGateState,l as isRoutedState};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{bucketFromEtaSeconds as t,bucketI18nKey as o}from"./qualitative-bucket.js";import{QueueDenyReason as r}from"./types.js";export{r as QueueDenyReason,t as bucketFromEtaSeconds,o as bucketI18nKey};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type QualitativeBucket = "upNext" | "almostThere" | "nearTheFront" | "fewAhead" | "inTheLine";
|
|
2
|
+
declare const I18N_KEYS: {
|
|
3
|
+
readonly upNext: "queue.bucket.upNext";
|
|
4
|
+
readonly almostThere: "queue.bucket.almostThere";
|
|
5
|
+
readonly nearTheFront: "queue.bucket.nearTheFront";
|
|
6
|
+
readonly fewAhead: "queue.bucket.fewAhead";
|
|
7
|
+
readonly inTheLine: "queue.bucket.inTheLine";
|
|
8
|
+
};
|
|
9
|
+
export type BucketI18nKey = (typeof I18N_KEYS)[QualitativeBucket];
|
|
10
|
+
export declare function bucketFromEtaSeconds(etaSeconds: number): QualitativeBucket;
|
|
11
|
+
export declare function bucketI18nKey(bucket: QualitativeBucket): BucketI18nKey;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={upNext:"queue.bucket.upNext",almostThere:"queue.bucket.almostThere",nearTheFront:"queue.bucket.nearTheFront",fewAhead:"queue.bucket.fewAhead",inTheLine:"queue.bucket.inTheLine"};function u(e){return e<30?"upNext":e<120?"almostThere":e<300?"nearTheFront":e<900?"fewAhead":"inTheLine"}function t(u){return e[u]}export{u as bucketFromEtaSeconds,t as bucketI18nKey};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { HttpClient, RequestOptions } from '../core/http';
|
|
2
|
+
import { MonitorUpdate } from '../experiences/distribution-monitor.types';
|
|
3
|
+
import { Queue, QueueConsumerState, QueueModule } from './types';
|
|
4
|
+
export declare class QueueManagementModule implements QueueModule {
|
|
5
|
+
private http;
|
|
6
|
+
private logger;
|
|
7
|
+
private store;
|
|
8
|
+
private events;
|
|
9
|
+
private trackedParticipations;
|
|
10
|
+
private pollingIntervals;
|
|
11
|
+
private inFlightRequests;
|
|
12
|
+
private monitorRuntime;
|
|
13
|
+
private monitorGenerations;
|
|
14
|
+
private readonly operationGenerations;
|
|
15
|
+
private destroyed;
|
|
16
|
+
private lifecycleGeneration;
|
|
17
|
+
constructor(http: HttpClient);
|
|
18
|
+
/**
|
|
19
|
+
* Get queue details
|
|
20
|
+
*/
|
|
21
|
+
get(queueId: string): Promise<Queue>;
|
|
22
|
+
/**
|
|
23
|
+
* Enter a queue
|
|
24
|
+
*/
|
|
25
|
+
enter(queueId: string, metadata?: Record<string, unknown>, options?: RequestOptions): Promise<QueueConsumerState>;
|
|
26
|
+
leave(queueId: string, options?: RequestOptions): Promise<void>;
|
|
27
|
+
status(queueId: string): Promise<QueueConsumerState>;
|
|
28
|
+
private _doStatus;
|
|
29
|
+
startMonitoring(id: string, context?: Record<string, unknown>, onUpdate?: (update: MonitorUpdate) => void): void;
|
|
30
|
+
stopMonitoring(id: string): void;
|
|
31
|
+
isMonitoring(id: string): boolean;
|
|
32
|
+
private startStatusPolling;
|
|
33
|
+
private stopStatusPolling;
|
|
34
|
+
private isStatusPolling;
|
|
35
|
+
private bumpMonitorGeneration;
|
|
36
|
+
private getOperationGeneration;
|
|
37
|
+
private bumpOperationGeneration;
|
|
38
|
+
private canApplyAsyncResult;
|
|
39
|
+
private buildTransition;
|
|
40
|
+
private applyTransition;
|
|
41
|
+
destroy(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createError as t}from"../core/errors.js";import{getLogger as e}from"../core/logger.js";import{DistributionMonitorRuntime as i}from"../experiences/distribution-monitor.runtime.js";import{getEventBus as s}from"../state/events.js";import{getSDKStore as n}from"../state/store.js";class o{constructor(t){this.logger=e(),this.store=n(),this.events=s(),this.trackedParticipations=/* @__PURE__ */new Map,this.pollingIntervals=/* @__PURE__ */new Map,this.inFlightRequests=/* @__PURE__ */new Map,this.monitorRuntime=new i,this.monitorGenerations=/* @__PURE__ */new Map,this.operationGenerations=/* @__PURE__ */new Map,this.destroyed=!1,this.lifecycleGeneration=0,this.http=t}async get(t){try{return await this.http.get(`/queues/${t}`)}catch(e){throw this.logger.error("Failed to get queue",{queueId:t,error:e}),e}}async enter(e,i,s){try{const n=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 queue",{queueId:e});const r=await this.http.post(`/queues/${e}/enter`,{metadata:i},s),a=this.buildTransition(e,r,{source:"enter",enteredAt:/* @__PURE__ */(new Date).toISOString(),metadata:i});return this.canApplyAsyncResult(e,n,void 0,o)?(this.applyTransition(e,a),r):r}catch(n){throw this.logger.error("Failed to enter queue",{queueId:e,error:n}),this.events.emit("queue:error",{queueId:e,error:n}),n}}async leave(t,e){try{const i=this.lifecycleGeneration,s=this.bumpOperationGeneration(t);if(this.logger.info("Leaving queue",{queueId:t}),await this.http.post(`/queues/${t}/leave`,void 0,e),!this.canApplyAsyncResult(t,i,void 0,s))return;this.trackedParticipations.delete(t),this.stopStatusPolling(t),this.events.emit("queue:left",{queueId:t,reason:"user"})}catch(i){throw this.logger.error("Failed to leave queue",{queueId:t,error:i}),this.events.emit("queue:error",{queueId:t,error:i}),i}}async status(t){const e=this.inFlightRequests.get(t);if(e)return this.logger.debug("Returning existing in-flight request",{queueId:t}),e;const i=this.lifecycleGeneration,s=this.bumpOperationGeneration(t),n=this._doStatus(t,i,void 0,s).finally(()=>{this.inFlightRequests.delete(t)});return this.inFlightRequests.set(t,n),n}async _doStatus(t,e,i,s){try{const n=await this.http.get(`/queues/${t}/status`);if(!this.canApplyAsyncResult(t,e,i,s))return n;const o=this.buildTransition(t,n,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,o),this.events.emit("queue:status-updated",{queueId:t,status:n}),n}catch(n){if(n&&"object"==typeof n&&"status"in n&&404===n.status){const n={status:"NOT_QUEUED"};if(!this.canApplyAsyncResult(t,e,i,s))return n;const o=this.buildTransition(t,n,{source:"status",previousParticipation:this.trackedParticipations.get(t)});return this.applyTransition(t,o),this.events.emit("queue:status-updated",{queueId:t,status:n}),n}if(!this.canApplyAsyncResult(t,e,i,s))return{status:"NOT_QUEUED"};throw this.logger.error("Failed to get queue status",{queueId:t,error:n}),this.events.emit("queue:error",{queueId:t,error:n}),n}}startMonitoring(t,e,i){this.monitorRuntime.start(t,e,i),this.startStatusPolling(t)}stopMonitoring(t){this.stopStatusPolling(t),this.monitorRuntime.stop(t)}isMonitoring(t){return this.isStatusPolling(t)||this.monitorRuntime.has(t)}startStatusPolling(t,e=1e4){this.stopStatusPolling(t);const i=this.bumpMonitorGeneration(t);this.logger.debug("Starting queue polling",{queueId:t,intervalMs:e});const s=async()=>{const e=this.getOperationGeneration(t);try{await this._doStatus(t,this.lifecycleGeneration,i,e)}catch(s){if(!this.canApplyAsyncResult(t,this.lifecycleGeneration,i,e))return;this.logger.error("Polling error",{queueId:t,error:s})}};s(),this.pollingIntervals.set(t,setInterval(s,e))}stopStatusPolling(t){this.bumpMonitorGeneration(t);const e=this.pollingIntervals.get(t);e&&(clearInterval(e),this.pollingIntervals.delete(t),this.logger.debug("Stopped queue polling",{queueId:t}))}isStatusPolling(t){return this.pollingIntervals.has(t)}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))}buildTransition(t,e,{source:i,previousParticipation:s,enteredAt:n,metadata:o}){switch(e.status){case"QUEUED":return{trackedParticipation:{queueId:t,enteredAt:s?.enteredAt??n??/* @__PURE__ */(new Date).toISOString(),position:e.position,estimatedWaitTime:e.estimatedWaitTimeInSeconds,positionDisplayMode:e.positionDisplayMode,metadata:s?.metadata??o},displayState:{position:e.position,estimatedWaitTime:e.estimatedWaitTimeInSeconds,positionDisplayMode:e.positionDisplayMode},events:"enter"===i?[{type:"queue:entered",payload:{queueId:t,position:e.position,estimatedWaitTime:e.estimatedWaitTimeInSeconds}}]:void 0!==s?.position&&s.position!==e.position?[{type:"queue:position-changed",payload:{queueId:t,position:e.position,previousPosition:s.position}}]:[],monitorUpdate:null,stopMonitoring:!1};case"GRANTED":case"COMPLETED":return{trackedParticipation:null,events:[{type:"queue:granted",payload:{queueId:t,grant:e.admissionGrant}}],monitorUpdate:"status"===i?{type:"granted",token:e.admissionGrant,expiresAt:"expiresAt"in e&&e.expiresAt?new Date(e.expiresAt).getTime():void 0}:null,stopMonitoring:"status"===i};case"EXPIRED":return{trackedParticipation:null,events:[{type:"queue:expired",payload:{queueId:t}}],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"expired"}}:null,stopMonitoring:"status"===i};case"DENIED":return{trackedParticipation:null,events:[],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"denied",reason:e.reason}}:null,stopMonitoring:"status"===i};case"LEFT":return{trackedParticipation:null,events:[],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"left"}}:null,stopMonitoring:"status"===i};case"NOT_QUEUED":return{trackedParticipation:null,events:[],monitorUpdate:"status"===i?{type:"ended",outcome:{type:"closed",reason:"not_queued"}}:null,stopMonitoring:"status"===i}}}applyTransition(t,e){if(e.trackedParticipation?this.trackedParticipations.set(t,e.trackedParticipation):this.trackedParticipations.delete(t),e.displayState){const i=this.monitorRuntime.getDisplayAtom(t);i&&i.set({type:"queue",...e.displayState})}for(const i of e.events)this.events.emit(i.type,i.payload);e.monitorUpdate&&this.monitorRuntime.notify(t,e.monitorUpdate),e.stopMonitoring&&this.stopStatusPolling(t)}destroy(){this.destroyed=!0,this.lifecycleGeneration+=1,this.pollingIntervals.forEach(t=>clearInterval(t)),this.pollingIntervals.clear(),this.inFlightRequests.clear(),this.trackedParticipations.clear(),this.monitorGenerations.clear(),this.operationGenerations.clear(),this.monitorRuntime.clear()}}export{o as QueueManagementModule};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { RequestOptions } from '../core/http';
|
|
2
|
+
/**
|
|
3
|
+
* Known queue deny reasons.
|
|
4
|
+
*
|
|
5
|
+
* When a consumer is denied entry to a queue, the `reason` field
|
|
6
|
+
* will contain one of these values (or a custom reason).
|
|
7
|
+
*/
|
|
8
|
+
export declare const QueueDenyReason: {
|
|
9
|
+
/** Consumer has exceeded their order limit for this experience */
|
|
10
|
+
readonly ORDER_LIMIT_EXCEEDED: "ORDER_LIMIT_EXCEEDED";
|
|
11
|
+
/** Queue has reached maximum capacity */
|
|
12
|
+
readonly CAPACITY_EXCEEDED: "CAPACITY_EXCEEDED";
|
|
13
|
+
/** Consumer validation failed */
|
|
14
|
+
readonly VALIDATION_FAILED: "VALIDATION_FAILED";
|
|
15
|
+
/** Consumer is not part of the required audience */
|
|
16
|
+
readonly AUDIENCE_MISMATCH: "AUDIENCE_MISMATCH";
|
|
17
|
+
/** Queue is closed */
|
|
18
|
+
readonly QUEUE_CLOSED: "QUEUE_CLOSED";
|
|
19
|
+
/** Admission token has expired */
|
|
20
|
+
readonly ADMISSION_EXPIRED: "ADMISSION_EXPIRED";
|
|
21
|
+
};
|
|
22
|
+
export type QueueDenyReason = (typeof QueueDenyReason)[keyof typeof QueueDenyReason];
|
|
23
|
+
/**
|
|
24
|
+
* Queue module interface
|
|
25
|
+
*/
|
|
26
|
+
export interface QueueModule {
|
|
27
|
+
/**
|
|
28
|
+
* Get queue details
|
|
29
|
+
*/
|
|
30
|
+
get(queueId: string): Promise<Queue>;
|
|
31
|
+
/**
|
|
32
|
+
* Enter a queue (requires authentication)
|
|
33
|
+
*/
|
|
34
|
+
enter(queueId: string, metadata?: Record<string, unknown>, options?: RequestOptions): Promise<QueueConsumerState>;
|
|
35
|
+
/**
|
|
36
|
+
* Leave a queue
|
|
37
|
+
*/
|
|
38
|
+
leave(queueId: string, options?: RequestOptions): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Get current status in queue
|
|
41
|
+
*/
|
|
42
|
+
status(queueId: string): Promise<QueueConsumerState>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Queue information
|
|
46
|
+
*/
|
|
47
|
+
export interface Queue {
|
|
48
|
+
id: string;
|
|
49
|
+
experienceId: string;
|
|
50
|
+
name: string;
|
|
51
|
+
status: "pending" | "open" | "closed" | "paused";
|
|
52
|
+
capacity?: number;
|
|
53
|
+
currentSize: number;
|
|
54
|
+
estimatedWaitTime?: number;
|
|
55
|
+
openAt?: string;
|
|
56
|
+
closeAt?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Consumer state in queue
|
|
60
|
+
*/
|
|
61
|
+
export type QueueConsumerState = QueuedConsumerState | GrantedConsumerState | CompletedConsumerState | LeftConsumerState | DeniedConsumerState | NotQueuedConsumerState | ExpiredConsumerState;
|
|
62
|
+
export interface QueuedConsumerState {
|
|
63
|
+
status: "QUEUED";
|
|
64
|
+
position: number;
|
|
65
|
+
estimatedWaitTimeInSeconds: number;
|
|
66
|
+
positionDisplayMode?: "precise" | "qualitative";
|
|
67
|
+
}
|
|
68
|
+
export interface GrantedConsumerState {
|
|
69
|
+
status: "GRANTED";
|
|
70
|
+
admissionGrant: string;
|
|
71
|
+
position?: number;
|
|
72
|
+
dequeuedAt?: string;
|
|
73
|
+
expiresAt?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface CompletedConsumerState {
|
|
76
|
+
status: "COMPLETED";
|
|
77
|
+
admissionGrant: string;
|
|
78
|
+
completedAt?: string;
|
|
79
|
+
position?: number;
|
|
80
|
+
}
|
|
81
|
+
export interface LeftConsumerState {
|
|
82
|
+
status: "LEFT";
|
|
83
|
+
dequeuedAt?: string;
|
|
84
|
+
position?: number;
|
|
85
|
+
}
|
|
86
|
+
export interface DeniedConsumerState {
|
|
87
|
+
status: "DENIED";
|
|
88
|
+
reason: string;
|
|
89
|
+
dequeuedAt?: string;
|
|
90
|
+
position?: number;
|
|
91
|
+
}
|
|
92
|
+
export interface NotQueuedConsumerState {
|
|
93
|
+
status: "NOT_QUEUED";
|
|
94
|
+
dequeuedAt?: string;
|
|
95
|
+
position?: number;
|
|
96
|
+
}
|
|
97
|
+
export interface ExpiredConsumerState {
|
|
98
|
+
status: "EXPIRED";
|
|
99
|
+
expiredAt?: string;
|
|
100
|
+
position?: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Runtime-tracked queue participation
|
|
104
|
+
*/
|
|
105
|
+
export interface QueueParticipation {
|
|
106
|
+
queueId: string;
|
|
107
|
+
enteredAt: string;
|
|
108
|
+
position?: number;
|
|
109
|
+
estimatedWaitTime?: number;
|
|
110
|
+
positionDisplayMode?: "precise" | "qualitative";
|
|
111
|
+
metadata?: Record<string, unknown>;
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const E={ORDER_LIMIT_EXCEEDED:"ORDER_LIMIT_EXCEEDED",CAPACITY_EXCEEDED:"CAPACITY_EXCEEDED",VALIDATION_FAILED:"VALIDATION_FAILED",AUDIENCE_MISMATCH:"AUDIENCE_MISMATCH",QUEUE_CLOSED:"QUEUE_CLOSED",ADMISSION_EXPIRED:"ADMISSION_EXPIRED"};export{E as QueueDenyReason};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const ADMISSION_KEY_THUMBPRINT_HEADER = "X-Admission-Key-Thumbprint";
|
|
2
|
+
export declare const DPOP_HEADER = "DPoP";
|
|
3
|
+
export declare const DPOP_NONCE_HEADER = "DPoP-Nonce";
|
|
4
|
+
export interface AdmissionProofHeaders {
|
|
5
|
+
[ADMISSION_KEY_THUMBPRINT_HEADER]: string;
|
|
6
|
+
[DPOP_HEADER]: string;
|
|
7
|
+
[DPOP_NONCE_HEADER]?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function canonicalizeAdmissionProofUrl(url: string): string;
|
|
10
|
+
export declare function getAdmissionKeyThumbprint(): Promise<string | null>;
|
|
11
|
+
export declare function buildAdmissionProofHeaders(params: {
|
|
12
|
+
url: string;
|
|
13
|
+
method?: string;
|
|
14
|
+
nonce?: string;
|
|
15
|
+
}): Promise<AdmissionProofHeaders | null>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{generateId as n}from"../core/utils.js";const t="X-Admission-Key-Thumbprint",e="DPoP",r="DPoP-Nonce",o="fanfare-admission-proof",c="keys",i="current";let a=null,u=null;function l(n){const t=new URL(n);return t.search="",t.hash="",t.toString()}async function s(){const n=await p();return n?.thumbprint??null}async function y(o){const c=await p();if(!c)return null;const i=o.method?.toUpperCase()??"POST",a={typ:"dpop+jwt",alg:"ES256",jwk:c.publicJwk},u={htm:i,htu:l(o.url),iat:Math.floor(Date.now()/1e3),jti:n(),...o.nonce?{nonce:o.nonce}:{}},s=`${d(a)}.${d(u)}`,y=await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},c.privateKey,(new TextEncoder).encode(s));return{[t]:c.thumbprint,[e]:`${s}.${b(new Uint8Array(y))}`,...o.nonce?{[r]:o.nonce}:{}}}async function p(){if(u)return u;if(a)return await a;a=(async()=>{if("undefined"==typeof window||"undefined"==typeof document||!globalThis.crypto?.subtle)return null;const n=await async function(){if(!("indexedDB"in globalThis))return null;return await new Promise(n=>{try{const t=indexedDB.open(o,1);t.onupgradeneeded=()=>{const n=t.result;n.objectStoreNames.contains(c)||n.createObjectStore(c)},t.onsuccess=()=>n(t.result),t.onerror=()=>n(null),t.onblocked=()=>n(null)}catch{n(null)}})}();if(!n)return null;try{const t=await async function(n){try{const t=await f(n.transaction(c).objectStore(c).get(i));return t?.privateKey&&t.publicKey&&t.publicJwk&&t.thumbprint?t:null}catch{return null}}(n);if(t)return u=t,t;const e=await async function(){const n=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!1,["sign","verify"]),t=await crypto.subtle.exportKey("jwk",n.publicKey);return{privateKey:n.privateKey,publicKey:n.publicKey,publicJwk:t,thumbprint:await w(t)}}(),r=await async function(n,t){try{return await f(n.transaction(c,"readwrite").objectStore(c).put(t,i)),!0}catch{return!1}}(n,e);return r?(u=e,e):null}finally{n.close()}})();try{return await a}finally{a=null}}function f(n){return new Promise((t,e)=>{n.onsuccess=()=>t(n.result),n.onerror=()=>e(n.error??new Error("IndexedDB request failed"))})}async function w(n){const t=JSON.stringify({crv:n.crv,kty:n.kty,x:n.x,y:n.y}),e=await crypto.subtle.digest("SHA-256",(new TextEncoder).encode(t));return b(new Uint8Array(e))}function d(n){return b((new TextEncoder).encode(JSON.stringify(n)))}function b(n){let t="";for(const e of n)t+=String.fromCharCode(e);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}export{t as ADMISSION_KEY_THUMBPRINT_HEADER,e as DPOP_HEADER,r as DPOP_NONCE_HEADER,y as buildAdmissionProofHeaders,l as canonicalizeAdmissionProofUrl,s as getAdmissionKeyThumbprint};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { RequestOptions } from '../core/http';
|
|
2
|
+
import { DistributionSummary } from '../experiences/journey.types';
|
|
3
|
+
import { SdkDistributionType } from '../types/distribution-type';
|
|
4
|
+
export type CapabilitySequenceKey = `${string}:${string}`;
|
|
5
|
+
export interface CapabilityTokenRecord {
|
|
6
|
+
experienceId: string;
|
|
7
|
+
sequenceId: string;
|
|
8
|
+
token: string;
|
|
9
|
+
expiresAt?: string;
|
|
10
|
+
expiresAtMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface DistributionCapabilityBinding {
|
|
13
|
+
distributionId: string;
|
|
14
|
+
distributionType: SdkDistributionType;
|
|
15
|
+
experienceId: string;
|
|
16
|
+
sequenceId: string;
|
|
17
|
+
sequenceKey: CapabilitySequenceKey;
|
|
18
|
+
}
|
|
19
|
+
export declare function makeSequenceKey(experienceId: string, sequenceId: string): CapabilitySequenceKey;
|
|
20
|
+
export declare function upsertSequenceToken(input: {
|
|
21
|
+
experienceId: string;
|
|
22
|
+
sequenceId: string;
|
|
23
|
+
token: string;
|
|
24
|
+
expiresAt?: string;
|
|
25
|
+
}): void;
|
|
26
|
+
export declare function bindDistribution(input: {
|
|
27
|
+
distributionId: string;
|
|
28
|
+
distributionType: SdkDistributionType;
|
|
29
|
+
experienceId: string;
|
|
30
|
+
sequenceId: string;
|
|
31
|
+
}): void;
|
|
32
|
+
export declare function bindDistributionContext(input: {
|
|
33
|
+
experienceId: string;
|
|
34
|
+
sequenceId: string;
|
|
35
|
+
distributionContext: DistributionSummary;
|
|
36
|
+
}): void;
|
|
37
|
+
export declare function resolveCapabilityOptions(lookup: {
|
|
38
|
+
distributionId?: string;
|
|
39
|
+
sequenceId?: string;
|
|
40
|
+
experienceId?: string;
|
|
41
|
+
}, existingOptions?: RequestOptions, nowMs?: number): RequestOptions | undefined;
|
|
42
|
+
export declare function clearSequenceContext(experienceId: string, sequenceId: string): void;
|
|
43
|
+
export declare function clearExperienceContext(experienceId: string): void;
|
|
44
|
+
export declare function clearExpired(nowMs?: number): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getSDKStore as e}from"./store.js";function i(e,i){return`${e}:${i}`}function t(t){const n=i(t.experienceId,t.sequenceId),r=t.expiresAt?new Date(t.expiresAt).getTime():void 0;e().upsertCapabilityToken(n,{experienceId:t.experienceId,sequenceId:t.sequenceId,token:t.token,expiresAt:t.expiresAt,expiresAtMs:r})}function n(t){const n=i(t.experienceId,t.sequenceId);e().bindDistribution(t.distributionId,{distributionId:t.distributionId,distributionType:t.distributionType,experienceId:t.experienceId,sequenceId:t.sequenceId,sequenceKey:n})}function r(e){const{experienceId:i,sequenceId:t,distributionContext:r}=e;n({distributionId:r.id,distributionType:r.type,experienceId:i,sequenceId:t}),r.waitlistId&&n({distributionId:r.waitlistId,distributionType:"waitlist",experienceId:i,sequenceId:t})}function s(t,n,r){if(n?.headers?.["X-Sequence-Capability-Grant"])return n;const s=e(),d=Date.now();let c;if(t.distributionId){const e=s.distributionCapabilityIndex[t.distributionId];e&&(c=e.sequenceKey)}if(!c&&t.experienceId&&t.sequenceId&&(c=i(t.experienceId,t.sequenceId)),!c)return n;const u=s.capabilityTokens[c];if(!u)return n;if(u.expiresAtMs&&u.expiresAtMs<=d)return n;const o={...n?.headers,"X-Sequence-Capability-Grant":u.token};return{...n,headers:o}}function d(i,t){e().clearCapabilityContext(i,t)}function c(i){e().clearExperienceCapabilityContext(i)}export{n as bindDistribution,r as bindDistributionContext,c as clearExperienceContext,d as clearSequenceContext,i as makeSequenceKey,s as resolveCapabilityOptions,t as upsertSequenceToken};
|