Skip to main content

Error Handling

The Fanfare SDK uses a structured error system with specific error codes and types. This page documents all error types and provides patterns for handling them effectively.

FanfareError

All SDK errors extend the FanfareError base class:
class FanfareError extends Error {
  code: ErrorCode;
  statusCode?: number;
  details?: Record<string, unknown>;
  retryable: boolean;

  constructor(options: FanfareErrorOptions);
}

interface FanfareErrorOptions {
  code: ErrorCode;
  message: string;
  statusCode?: number;
  details?: Record<string, unknown>;
  retryable?: boolean;
  cause?: Error;
}

Error Codes

ErrorCodes Constant

const ErrorCodes = {
  // Configuration errors
  INVALID_CONFIG: "INVALID_CONFIG",
  MISSING_ORGANIZATION_ID: "MISSING_ORGANIZATION_ID",
  MISSING_PUBLISHABLE_KEY: "MISSING_PUBLISHABLE_KEY",

  // Authentication errors
  UNAUTHORIZED: "UNAUTHORIZED",
  INVALID_OTP: "INVALID_OTP",
  SESSION_EXPIRED: "SESSION_EXPIRED",
  AUTHENTICATION_REQUIRED: "AUTHENTICATION_REQUIRED",
  INVALID_TOKEN: "INVALID_TOKEN",

  // Network errors
  NETWORK_ERROR: "NETWORK_ERROR",
  TIMEOUT: "TIMEOUT",
  SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",

  // Queue errors
  QUEUE_NOT_FOUND: "QUEUE_NOT_FOUND",
  QUEUE_CLOSED: "QUEUE_CLOSED",
  QUEUE_FULL: "QUEUE_FULL",
  ALREADY_IN_QUEUE: "ALREADY_IN_QUEUE",
  NOT_IN_QUEUE: "NOT_IN_QUEUE",

  // Draw errors
  DRAW_NOT_FOUND: "DRAW_NOT_FOUND",
  DRAW_CLOSED: "DRAW_CLOSED",
  DRAW_FULL: "DRAW_FULL",
  ALREADY_IN_DRAW: "ALREADY_IN_DRAW",
  NOT_IN_DRAW: "NOT_IN_DRAW",
  DRAW_NOT_STARTED: "DRAW_NOT_STARTED",

  // Auction errors
  AUCTION_NOT_FOUND: "AUCTION_NOT_FOUND",
  AUCTION_CLOSED: "AUCTION_CLOSED",
  AUCTION_NOT_STARTED: "AUCTION_NOT_STARTED",
  BID_TOO_LOW: "BID_TOO_LOW",
  INVALID_BID_AMOUNT: "INVALID_BID_AMOUNT",
  RESERVE_NOT_MET: "RESERVE_NOT_MET",

  // Experience errors
  EXPERIENCE_NOT_FOUND: "EXPERIENCE_NOT_FOUND",
  SEQUENCE_NOT_FOUND: "SEQUENCE_NOT_FOUND",
  ACCESS_CODE_REQUIRED: "ACCESS_CODE_REQUIRED",
  INVALID_ACCESS_CODE: "INVALID_ACCESS_CODE",
  NO_SEQUENCE_AVAILABLE: "NO_SEQUENCE_AVAILABLE",

  // Waitlist errors
  WAITLIST_NOT_FOUND: "WAITLIST_NOT_FOUND",
  WAITLIST_CLOSED: "WAITLIST_CLOSED",

  // Timed release errors
  TIMED_RELEASE_NOT_FOUND: "TIMED_RELEASE_NOT_FOUND",
  TIMED_RELEASE_NOT_STARTED: "TIMED_RELEASE_NOT_STARTED",
  TIMED_RELEASE_ENDED: "TIMED_RELEASE_ENDED",

  // General errors
  VALIDATION_ERROR: "VALIDATION_ERROR",
  RATE_LIMITED: "RATE_LIMITED",
  INTERNAL_ERROR: "INTERNAL_ERROR",
  UNKNOWN_ERROR: "UNKNOWN_ERROR",
} as const;

type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];

Deny Reasons

Queue Deny Reasons

When a consumer is denied entry to a queue:
const QueueDenyReason = {
  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",
} as const;

Draw Deny Reasons

When a consumer is denied entry to a draw:
const DrawDenyReason = {
  ORDER_LIMIT_EXCEEDED: "ORDER_LIMIT_EXCEEDED",
  CAPACITY_EXCEEDED: "CAPACITY_EXCEEDED",
  VALIDATION_FAILED: "VALIDATION_FAILED",
  AUDIENCE_MISMATCH: "AUDIENCE_MISMATCH",
  DRAW_CLOSED: "DRAW_CLOSED",
  NOT_SELECTED: "NOT_SELECTED",
} as const;

Error Handling Patterns

Basic Error Handling

import { FanfareError, ErrorCodes } from "@waitify-io/fanfare-sdk-core";

try {
  await fanfare.queues.enter("queue_123");
} catch (error) {
  if (error instanceof FanfareError) {
    console.error("Fanfare error:", error.code, error.message);
  } else {
    console.error("Unexpected error:", error);
  }
}

Switch on Error Code

try {
  await fanfare.queues.enter("queue_123");
} catch (error) {
  if (error instanceof FanfareError) {
    switch (error.code) {
      case ErrorCodes.QUEUE_CLOSED:
        showMessage("This queue is currently closed.");
        break;
      case ErrorCodes.QUEUE_FULL:
        showMessage("This queue is full. Please try again later.");
        break;
      case ErrorCodes.ALREADY_IN_QUEUE:
        showMessage("You are already in this queue.");
        break;
      case ErrorCodes.AUTHENTICATION_REQUIRED:
        showLoginModal();
        break;
      default:
        showMessage(`Error: ${error.message}`);
    }
  }
}

Authentication Errors

try {
  await fanfare.auth.verifyOtp({
    email: "[email protected]",
    code: userInputCode,
  });
} catch (error) {
  if (error instanceof FanfareError) {
    switch (error.code) {
      case ErrorCodes.INVALID_OTP:
        showMessage("Invalid code. Please check and try again.");
        break;
      case ErrorCodes.SESSION_EXPIRED:
        showMessage("Code expired. Please request a new one.");
        await fanfare.auth.requestOtp({ email: "[email protected]" });
        break;
      default:
        showMessage("Authentication failed. Please try again.");
    }
  }
}

Network Error Handling

try {
  await fanfare.queues.enter("queue_123");
} catch (error) {
  if (error instanceof FanfareError) {
    if (error.code === ErrorCodes.NETWORK_ERROR || error.code === ErrorCodes.TIMEOUT) {
      // Retry with exponential backoff
      if (error.retryable) {
        await retryWithBackoff(() => fanfare.queues.enter("queue_123"));
      }
    }
  }
}

async function retryWithBackoff(fn: () => Promise<unknown>, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise((r) => setTimeout(r, Math.pow(2, i) * 1000));
    }
  }
}

Auction Bid Errors

try {
  await fanfare.auctions.placeBid("auction_123", "150.00");
} catch (error) {
  if (error instanceof FanfareError) {
    switch (error.code) {
      case ErrorCodes.BID_TOO_LOW:
        const minBid = error.details?.minBid as string;
        showMessage(`Bid must be at least ${minBid}`);
        break;
      case ErrorCodes.AUCTION_CLOSED:
        showMessage("This auction has ended.");
        break;
      case ErrorCodes.RESERVE_NOT_MET:
        showMessage("Reserve price not met.");
        break;
      default:
        showMessage("Failed to place bid. Please try again.");
    }
  }
}

Experience Journey Errors

try {
  await journey.start({ accessCode: userAccessCode });
} catch (error) {
  if (error instanceof FanfareError) {
    switch (error.code) {
      case ErrorCodes.INVALID_ACCESS_CODE:
        showMessage("Invalid access code. Please check and try again.");
        break;
      case ErrorCodes.ACCESS_CODE_REQUIRED:
        showAccessCodeInput();
        break;
      case ErrorCodes.NO_SEQUENCE_AVAILABLE:
        showMessage("No available access for this experience.");
        break;
      case ErrorCodes.EXPERIENCE_NOT_FOUND:
        showMessage("Experience not found.");
        break;
      default:
        showMessage("Failed to start experience.");
    }
  }
}

Event-Based Error Handling

Subscribe to error events for async error handling:
// Queue errors
fanfare.on("queue:error", ({ queueId, error }) => {
  if (error instanceof FanfareError && error.code === ErrorCodes.QUEUE_CLOSED) {
    showNotification("The queue has closed.");
    updateUI("closed");
  }
});

// Draw errors
fanfare.on("draw:error", ({ drawId, error }) => {
  showNotification(`Draw error: ${error.message}`);
});

// Auction errors
fanfare.on("auction:error", ({ auctionId, error }) => {
  showNotification(`Auction error: ${error.message}`);
});

Custom Error Handling Utility

import { FanfareError, ErrorCodes } from "@waitify-io/fanfare-sdk-core";

type ErrorMessages = Partial<Record<ErrorCode, string>>;

function handleFanfareError(error: unknown, customMessages?: ErrorMessages): string {
  if (!(error instanceof FanfareError)) {
    return "An unexpected error occurred. Please try again.";
  }

  // Check for custom message first
  if (customMessages?.[error.code]) {
    return customMessages[error.code]!;
  }

  // Default messages
  const defaultMessages: ErrorMessages = {
    [ErrorCodes.NETWORK_ERROR]: "Network error. Please check your connection.",
    [ErrorCodes.TIMEOUT]: "Request timed out. Please try again.",
    [ErrorCodes.UNAUTHORIZED]: "Please log in to continue.",
    [ErrorCodes.RATE_LIMITED]: "Too many requests. Please wait a moment.",
    [ErrorCodes.SERVICE_UNAVAILABLE]: "Service temporarily unavailable.",
  };

  return defaultMessages[error.code] || error.message;
}

// Usage
try {
  await fanfare.queues.enter("queue_123");
} catch (error) {
  const message = handleFanfareError(error, {
    [ErrorCodes.QUEUE_FULL]: "This queue is currently full. Check back soon!",
  });
  showToast(message);
}

Error Details

Some errors include additional details:
try {
  await fanfare.auctions.placeBid("auction_123", "50.00");
} catch (error) {
  if (error instanceof FanfareError) {
    console.log("Code:", error.code);
    console.log("Message:", error.message);
    console.log("Status:", error.statusCode); // HTTP status if applicable
    console.log("Details:", error.details);
    // details might include: { minBid: "100.00", currentHighest: "95.00" }
    console.log("Retryable:", error.retryable);
  }
}

Typed Error Checking

import { FanfareError, ErrorCodes, type ErrorCode } from "@waitify-io/fanfare-sdk-core";

function isAuthError(error: unknown): error is FanfareError {
  const authCodes: ErrorCode[] = [
    ErrorCodes.UNAUTHORIZED,
    ErrorCodes.SESSION_EXPIRED,
    ErrorCodes.AUTHENTICATION_REQUIRED,
    ErrorCodes.INVALID_TOKEN,
  ];

  return error instanceof FanfareError && authCodes.includes(error.code);
}

try {
  await fanfare.queues.enter("queue_123");
} catch (error) {
  if (isAuthError(error)) {
    redirectToLogin();
  }
}