Documentation Index
Fetch the complete documentation index at: https://docs.fanfare.io/llms.txt
Use this file to discover all available pages before exploring further.
Experience Journey
The Experience Journey system provides a state machine for orchestrating complex consumer journeys through multiple stages of an experience. It handles routing, authentication requirements, sequence selection, and distribution participation.
Overview
An “Experience” in Fanfare is a container that can include multiple sequences and distribution types (queues, draws, auctions, timed releases). The ExperienceJourney class manages the consumer’s progression through these stages.
┌─────────────────────────────────────────────────────────────┐
│ Experience │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Sequences │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ General │ │ VIP │ │ Early │ │ │
│ │ │ Access │ │ Access │ │ Access │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Distributions │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ │
│ │ │ Queue │ │ Draw │ │ Auction │ │ Timed │ │ │
│ │ │ │ │ │ │ │ │ Release │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Creating a Journey
const journey = fanfare.experiences.createJourney("exp_123");
The journey is created idempotently - calling createJourney with the same experience ID returns the existing journey instance.
Journey Stages
JourneyStage
High-level stages of the journey:
| Stage | Description |
|---|
not_started | Journey has not begun |
entering | Entering the experience |
needs_auth | Authentication is required |
needs_access_code | Access code is required |
routing | Finding the appropriate sequence |
routed | Successfully routed to a sequence |
SequenceStage
Stages within a routed sequence:
| Stage | Description |
|---|
none | No sequence stage (pre-routing) |
upcoming | Distribution is scheduled but not active |
waitlist_entered | Consumer is on the waitlist |
active_enterable | Active distribution, consumer can enter |
participating | Actively participating in distribution |
admitted | Admitted with valid token |
admission_expired | Admission token has expired |
ended | Distribution has ended |
State Snapshot
The journey state is accessible via the state atom:
interface JourneySnapshot {
revision: number;
updatedAt: number;
journeyStage: JourneyStage;
sequenceStage: SequenceStage;
requirements: Requirement[];
availableActions: {
journey: JourneyAction[];
sequence: SequenceAction[];
};
context: JourneyContext;
events: JourneyEvent[];
lastSeenEventId?: string;
}
Subscribing to State Changes
import { computed } from "nanostores";
// Subscribe to full state
journey.state.listen((snapshot) => {
console.log("Journey stage:", snapshot.journeyStage);
console.log("Sequence stage:", snapshot.sequenceStage);
});
// Or use computed stores for specific values
journey.journeyStage$.listen((stage) => {
console.log("Journey stage changed:", stage);
});
journey.sequenceStage$.listen((stage) => {
console.log("Sequence stage changed:", stage);
});
journey.requirements$.listen((requirements) => {
console.log("Requirements:", requirements);
});
Lifecycle Methods
start()
Begins the journey. Optionally accepts an access code.
await journey.start();
// Or with access code
await journey.start({ accessCode: "VIP2024" });
// Or with reset
await journey.start({ reset: true });
destroy()
Removes the journey and clears state.
Requirement Handling
Requirements
The journey may require certain conditions to be met:
interface Requirement {
type: "authentication" | "access_code" | "bot_check";
required: boolean;
reason?: string;
metadata?: Record<string, unknown>;
}
authenticate()
Call after the consumer has authenticated:
// After successful authentication
await journey.authenticate();
provideAccessCode()
Provide an access code:
await journey.provideAccessCode("VIP2024");
skipAccessCode()
Skip the access code requirement if optional:
const req = journey.requirements$.get().find((r) => r.type === "access_code");
if (!req?.required) {
await journey.skipAccessCode();
}
completeBotCheck()
Mark bot check as complete:
await journey.completeBotCheck();
Distribution Participation
refreshDistribution()
Refresh the current distribution context:
await journey.refreshDistribution();
startPolling() / stopPolling()
Poll for distribution updates:
// Start polling every 5 seconds
journey.startPolling(5000);
// Stop polling
journey.stopPolling();
enterQueue()
Enter the active queue:
if (snapshot.availableActions.sequence.includes("enter_queue")) {
await journey.enterQueue();
}
enterDraw()
Enter the active draw:
if (snapshot.availableActions.sequence.includes("enter_draw")) {
await journey.enterDraw();
}
enterAuction()
Enter the active auction:
if (snapshot.availableActions.sequence.includes("enter_auction")) {
await journey.enterAuction();
}
enterTimedRelease()
Enter the active timed release:
if (snapshot.availableActions.sequence.includes("enter_timed_release")) {
await journey.enterTimedRelease();
}
completeTimedRelease()
Complete a timed release (after successful purchase):
if (snapshot.availableActions.sequence.includes("complete_timed_release")) {
await journey.completeTimedRelease();
}
enterWaitlist()
Join the waitlist for an upcoming distribution:
if (snapshot.availableActions.sequence.includes("enter_waitlist")) {
await journey.enterWaitlist();
}
leaveWaitlist()
Leave the waitlist:
if (snapshot.availableActions.sequence.includes("leave_waitlist")) {
await journey.leaveWaitlist();
}
leaveParticipation()
Leave the current distribution:
if (snapshot.availableActions.sequence.includes("leave_participation")) {
await journey.leaveParticipation();
}
markAdmitted()
Mark the consumer as admitted with a token:
journey.markAdmitted("token_xxx", Date.now() + 3600000); // expires in 1 hour
Actions API
Execute any available action by name:
await journey.perform("enter_queue");
await journey.perform("provide_access_code", "VIP2024");
Available Actions
Journey actions:
start - Begin the journey
authenticate - Mark authentication complete
provide_access_code - Provide an access code
skip_access_code - Skip optional access code
request_reroute - Request re-routing
retry - Retry current operation
complete_bot_check - Mark bot check complete
refresh_distribution - Refresh distribution data
Sequence actions:
enter_waitlist - Join waitlist
leave_waitlist - Leave waitlist
enter_queue - Enter queue
enter_draw - Enter draw
enter_auction - Enter auction
enter_timed_release - Enter timed release
complete_timed_release - Complete timed release
leave_participation - Leave current distribution
Events
Journey Events
interface JourneyEvent {
id: string;
ts: number;
kind: "reroute" | "sequence_change" | "distribution_change" | "requirement" | "error" | "info";
severity: "info" | "success" | "warning" | "error";
audience: "user" | "system" | "analytics";
message: string;
detail?: Record<string, unknown>;
}
Subscribing to Events
// Get the latest unacknowledged event
journey.latestEvent$.listen((event) => {
if (event) {
console.log(event.message);
journey.ackEvent(event.id);
}
});
// Or acknowledge all events
journey.ackAllEvents();
Resume Flow
resumeFromMe()
Resume journey state from server data:
// Fetch consumer data
const me = await fanfare.experiences.getMe();
// Resume the journey
const resumed = await journey.resumeFromMe(me);
if (resumed) {
console.log("Journey resumed successfully");
}
resumeFromServer()
Resume with minimal server data:
await journey.resumeFromServer({
sequenceId: "seq_123",
waitlist: {
waitlistId: "wl_456",
enteredAt: "2024-01-15T12:00:00Z",
},
});
Complete Example
import Fanfare from "@waitify-io/fanfare-sdk-core";
async function runExperience() {
const fanfare = await Fanfare.init({
organizationId: "org_xxx",
publishableKey: "pk_live_xxx",
});
// Restore session
await fanfare.restore();
// Create or get journey
const journey = fanfare.experiences.createJourney("exp_123");
// Subscribe to state changes
journey.state.listen((snapshot) => {
console.log("Stage:", snapshot.journeyStage, snapshot.sequenceStage);
// Handle requirements
if (snapshot.journeyStage === "needs_auth") {
showAuthModal();
}
// Handle available actions
if (snapshot.availableActions.sequence.includes("enter_queue")) {
showEnterButton();
}
// Handle admission
if (snapshot.sequenceStage === "admitted") {
const token = snapshot.context.admittanceToken;
redirectToCheckout(token);
}
});
// Start the journey
await journey.start();
// Start polling for updates
journey.startPolling(5000);
}
runExperience();
Context Data
The journey context contains all relevant data:
interface JourneyContext {
experienceId: string;
experience?: ExperienceSession;
sequenceId?: string;
distribution?: DistributionSummary;
participation?: {
id: string;
type: "queue" | "draw" | "auction" | "waitlist" | "timed_release";
};
accessCode?: string;
admittanceToken?: string;
admittanceExpiresAt?: number;
}
Access context data:
const snapshot = journey.state.get();
console.log("Experience:", snapshot.context.experienceId);
console.log("Sequence:", snapshot.context.sequenceId);
console.log("Participation:", snapshot.context.participation?.type);
console.log("Admission token:", snapshot.context.admittanceToken);