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.
State Management
The Fanfare SDK uses a reactive state system built on nanostores to manage participation state, sessions, and experience journeys. This page explains how state is managed internally and how you can interact with it.
State Architecture
┌─────────────────────────────────────────────────────────────┐
│ State Store │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ session: Session | null │ │
│ │ refreshToken: string | null │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ activeQueues: Record<string, QueueParticipation> │ │
│ │ activeDraws: Record<string, DrawParticipation> │ │
│ │ activeAuctions: Record<string, AuctionParticipation> │ │
│ │ activeWaitlists: Record<string, WaitlistParticipation> │ │
│ │ activeTimedReleases: Record<string, TimedReleasePart...> │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ activeJourneys: Record<string, JourneySnapshot> │ │
│ └─────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Persistence Layer │
│ localStorage / sessionStorage │
├─────────────────────────────────────────────────────────────┤
│ Tab Sync Layer │
│ BroadcastChannel │
└─────────────────────────────────────────────────────────────┘
Participation State
Each experience type tracks participation state:
Queue Participation
interface QueueParticipation {
queueId: string;
enteredAt: string;
status: "QUEUED" | "ADMITTED" | "COMPLETED" | "LEFT" | "DENIED" | "NOT_QUEUED" | "EXPIRED";
position?: number;
estimatedWaitTime?: number;
metadata?: Record<string, unknown>;
admissionToken?: string;
admittedAt?: string;
expiresAt?: string;
}
Draw Participation
interface DrawParticipation {
drawId: string;
enteredAt: string;
status: "entered" | "drawn" | "won" | "lost" | "expired";
entryNumber?: string;
metadata?: Record<string, unknown>;
result?: DrawResult;
checkedAt?: string;
fingerprint?: string;
}
Auction Participation
interface AuctionParticipation {
auctionId: string;
enteredAt: string;
status: "watching" | "bidding" | "winning" | "outbid" | "won" | "lost";
currentBid?: string;
highestBid?: string;
bidCount?: number;
lastBidAt?: string;
metadata?: Record<string, unknown>;
fingerprint?: string;
}
Waitlist Participation
interface WaitlistParticipation {
id: string;
waitlistId: string;
sequenceId: string;
isEntered: boolean;
enteredAt?: string;
}
Timed Release Participation
interface TimedReleaseParticipation {
timedReleaseId: string;
enteredAt: string;
status: "entered" | "completed" | "left";
selectedVariantId?: string;
metadata?: Record<string, unknown>;
}
Accessing State
Active Participation Queries
Each module provides methods to query active participations:
// Get all active queues
const queues = fanfare.queues.getActiveQueues();
for (const [queueId, participation] of Object.entries(queues)) {
console.log(queueId, participation.status, participation.position);
}
// Get all active draws
const draws = fanfare.draws.getActiveDraws();
// Get all active auctions
const auctions = fanfare.auctions.getActiveAuctions();
// Get all entered waitlists
const waitlists = fanfare.waitlists.getEnteredWaitlists();
// Get all active timed releases
const timedReleases = fanfare.timedReleases.getActiveTimedReleases();
Specific State Queries
// Check if in a specific queue
const isInQueue = fanfare.queues.getActiveQueues()["queue_123"] !== undefined;
// Check if entered a specific draw
const isEntered = fanfare.draws.isEntered("draw_123");
// Check if participating in auction
const isParticipating = fanfare.auctions.isParticipating("auction_123");
// Check if on waitlist
const isOnWaitlist = fanfare.waitlists.isOnWaitlist("waitlist_123");
// Check if in timed release
const inTimedRelease = fanfare.timedReleases.isEntered("tr_123");
State Persistence
State is automatically persisted to browser storage.
Configuration
const fanfare = await Fanfare.init({
organizationId: "org_xxx",
publishableKey: "pk_live_xxx",
auth: {
persistSession: true, // Persist session to localStorage
},
});
Storage Keys
The SDK uses the following localStorage keys (prefixed with organization ID):
| Key | Contents |
|---|
fanfare:{orgId}:session | Current session |
fanfare:{orgId}:refresh_token | Refresh token |
fanfare:{orgId}:state | Participation state |
fanfare:{orgId}:journeys | Journey snapshots |
Tab Synchronization
State is synchronized across browser tabs using BroadcastChannel.
How It Works
- When state changes in one tab, it broadcasts the change
- Other tabs receive the broadcast and update their local state
- This ensures consumers don’t lose their place when switching tabs
Configuration
const fanfare = await Fanfare.init({
organizationId: "org_xxx",
publishableKey: "pk_live_xxx",
sync: {
enabled: true, // Enable tab sync (default)
syncKeys: ["session", "activeQueues", "activeDraws"], // Keys to sync
channelName: "fanfare-sync", // Custom channel name
},
});
Disabling Sync
const fanfare = await Fanfare.init({
organizationId: "org_xxx",
publishableKey: "pk_live_xxx",
sync: false, // Disable completely
});
Journey State
Experience journeys maintain their own reactive state:
const journey = fanfare.experiences.createJourney("exp_123");
// Get current snapshot
const snapshot = journey.state.get();
console.log(snapshot.journeyStage);
console.log(snapshot.sequenceStage);
// Subscribe to changes
journey.state.listen((snapshot) => {
console.log("Journey updated:", snapshot.revision);
});
// Use computed stores for specific values
journey.journeyStage$.listen((stage) => {
console.log("Journey stage:", stage);
});
journey.requirements$.listen((requirements) => {
console.log("Requirements:", requirements);
});
State Restoration
State is restored on SDK initialization:
const fanfare = await Fanfare.init({
organizationId: "org_xxx",
publishableKey: "pk_live_xxx",
});
// Restore saved state
const { session, experiences } = await fanfare.restore();
if (session) {
console.log("Restored session:", session.consumerId);
}
// Check restored participations
if (Object.keys(experiences.queues).length > 0) {
console.log("Has active queue participation");
}
// Resume operations (starts polling, etc.)
await fanfare.resume();
State Cleanup
Clearing State
The SDK provides cleanup methods for expired/ended experiences:
// Clear expired draws
fanfare.draws.clearExpiredDraws();
// Clear ended auctions
fanfare.auctions.clearEndedAuctions();
Manual State Management
For advanced use cases, you can interact with state directly:
// The SDK doesn't expose raw state stores, but modules
// provide methods to manage state
// Leave all waitlists
await fanfare.waitlists.leaveAll();
// Stop all auction watching
fanfare.auctions.stopAllWatching();
State and Server Synchronization
The SDK automatically syncs with the server:
// Fetch latest status from server
const status = await fanfare.queues.status("queue_123");
// This also updates local state
const participation = fanfare.queues.getActiveQueues()["queue_123"];
console.log(participation.status === status.status); // true
Refreshing State
// Refresh a journey's distribution state
const journey = fanfare.experiences.createJourney("exp_123");
await journey.refreshDistribution();
// The journey snapshot is automatically updated
const snapshot = journey.state.get();
Best Practices
1. Always Restore on Load
async function initApp() {
const fanfare = await Fanfare.init(config);
const { session } = await fanfare.restore();
if (session) {
await fanfare.resume();
}
return fanfare;
}
2. Subscribe to Events, Not State
Prefer subscribing to events over polling state:
// Good: React to events
fanfare.on("queue:admitted", ({ token }) => {
handleAdmission(token);
});
// Avoid: Polling state
setInterval(() => {
const queues = fanfare.queues.getActiveQueues();
// Check for changes...
}, 1000);
3. Clean Up on Unmount
useEffect(() => {
const unsubscribes = [fanfare.on("queue:admitted", handleAdmitted), journey.state.listen(handleJourneyChange)];
return () => {
unsubscribes.forEach((unsub) => unsub());
};
}, []);
4. Handle Tab Visibility
The SDK handles tab visibility automatically, but you can optimize:
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
// Tab hidden - SDK reduces polling
} else {
// Tab visible - SDK resumes normal polling
}
});