UNPKG

@dollardonationclub/impactapi-js

Version:

JavaScript SDK for integrating the DDC Impact Widget into any website

298 lines (294 loc) 9.39 kB
type SessionType = "add_on" | "portion_of_sales_choice" | "portion_of_sales"; interface ImpactMetric { identifier: string; name: string; description: string; verbSingular: string; verbPlural: string; verbPast: string; verbProgressive: string; verbImperative: string; impactObjectSingular: string; impactObjectPlural: string; unit: string; emoji: string; icon: string; impactToDollarRatio: number; } interface Campaign { id: string; slug: string; organizationName: string; impactMetrics: ImpactMetric[]; presetAmounts?: number[]; color?: string; } interface AllocationInput { campaignIdentifier: string; amount: number; } interface Allocation extends AllocationInput { campaign: Campaign; } interface SessionDataBase { id: string; campaigns: Campaign[]; expiresAt: string; allocations: Allocation[]; } interface SessionDataAddOn extends SessionDataBase { type: "add_on"; amount: number; } interface SessionDataPortionOfSalesChoice extends SessionDataBase { type: "portion_of_sales_choice"; amount: number; } interface SessionDataPortionOfSales extends SessionDataBase { type: "portion_of_sales"; } type SessionData = SessionDataAddOn | SessionDataPortionOfSalesChoice | SessionDataPortionOfSales; interface ImpactCalculation { campaignIdentifier: string; amount: number; impactText: string; } type WidgetStyleMode = "gradient" | "solid" | "light" | "dark"; interface WidgetConfig { sessionId: string; secret: string; styleMode?: WidgetStyleMode; } interface PreviewWidgetConfigBase { styleMode?: WidgetStyleMode; } interface PreviewWidgetConfigAddOn extends PreviewWidgetConfigBase { type: "add_on"; amount: number; available_campaigns: string[]; } interface PreviewWidgetConfigPortionOfSalesChoice extends PreviewWidgetConfigBase { type: "portion_of_sales_choice"; amount: number; available_campaigns: string[]; } interface PreviewWidgetConfigPortionOfSales extends PreviewWidgetConfigBase { type: "portion_of_sales"; allocations: Array<{ campaign_identifier: string; amount: number; }>; } type PreviewWidgetConfig = PreviewWidgetConfigAddOn | PreviewWidgetConfigPortionOfSalesChoice | PreviewWidgetConfigPortionOfSales; interface BaseWidgetEventMap { ready: ReadyEventData; error: ErrorEventData; resize: ResizeEventData; destroyed: DestroyedEventData; "session-updated": SessionData; } interface WidgetEventMap extends BaseWidgetEventMap { "allocations-updated": AllocationsUpdatedEventData; } interface ReadyEventData { sessionId: string; version: string; type: SessionType; } interface ErrorEventData { message: string; stack?: string; } interface AllocationsUpdatedEventData { allocations: Allocation[]; totalAmount: number; } interface ResizeEventData { width?: number; height?: number; } interface DestroyedEventData { sessionId: string; } /** * Error codes for widget operations */ declare enum WidgetErrorCode { MOUNT_FAILED = "MOUNT_FAILED", CONTAINER_NOT_FOUND = "CONTAINER_NOT_FOUND", ALREADY_MOUNTED = "ALREADY_MOUNTED", INITIALIZATION_FAILED = "INITIALIZATION_FAILED", INVALID_CONFIG = "INVALID_CONFIG", MISSING_WIDGET_ID = "MISSING_WIDGET_ID", MISSING_SECRET = "MISSING_SECRET", API_ERROR = "API_ERROR", NETWORK_ERROR = "NETWORK_ERROR", UNAUTHORIZED = "UNAUTHORIZED", SESSION_EXPIRED = "SESSION_EXPIRED", MESSAGE_TIMEOUT = "MESSAGE_TIMEOUT", INVALID_MESSAGE = "INVALID_MESSAGE", WIDGET_NOT_READY = "WIDGET_NOT_READY", OPERATION_FAILED = "OPERATION_FAILED", UNKNOWN_ERROR = "UNKNOWN_ERROR" } /** * Structured error class for widget operations */ declare class WidgetError extends Error { readonly code: WidgetErrorCode; readonly recoverable: boolean; readonly details?: unknown; readonly cause?: Error; constructor(code: WidgetErrorCode, message: string, options?: { recoverable?: boolean; details?: unknown; cause?: Error; }); /** * Converts the error to a plain object for serialization */ toJSON(): { name: string; code: WidgetErrorCode; message: string; recoverable: boolean; details: unknown; stack: string | undefined; }; } /** * Helper functions for creating common errors */ declare const WidgetErrors: { mountFailed: (message: string, cause?: Error) => WidgetError; containerNotFound: (selector: string) => WidgetError; invalidConfig: (message: string, details?: unknown) => WidgetError; apiError: (message: string, details?: unknown, cause?: Error) => WidgetError; unauthorized: (message?: string) => WidgetError; timeout: (operation: string, timeout: number) => WidgetError; notReady: (operation: string) => WidgetError; }; /** * Base widget interface with common functionality shared across all widget types */ interface BaseWidget<TEventMap> { /** * Mounts the widget iframe into the specified container * @param container - DOM element or CSS selector string * @returns Promise that resolves when widget is ready */ mount(_container: HTMLElement | string): Promise<void>; /** * Destroys the widget and cleans up all resources */ destroy(): void; /** * Checks if the widget has finished loading and is ready to use */ isReady(): boolean; /** * Gets the unique session ID */ getSessionId(): string; /** * Gets the current session data including campaigns and configuration */ getSessionData(): SessionData | null; /** * Registers an event listener (chainable) * @param event - Event name * @param handler - Event handler function */ on<K extends keyof TEventMap>(_event: K, _handler: (_data: TEventMap[K]) => void): this; /** * Removes an event listener (chainable) * @param event - Event name * @param handler - Specific handler to remove, or omit to remove all */ off<K extends keyof TEventMap>(_event: K, _handler?: (_data: TEventMap[K]) => void): this; /** * Waits for a specific event to fire (promise-based) * @param event - Event name * @param options - Optional timeout configuration * @returns Promise that resolves with event data */ waitFor<K extends keyof TEventMap>(_event: K, _options?: { timeout?: number; }): Promise<TEventMap[K]>; } /** * Widget interface * * Unified widget that adapts its behavior based on the session type: * - portion_of_sales: Non-interactive - displays allocations from the API (set on backend) * - portion_of_sales_choice: Interactive - vendor pays, customer chooses allocations * - add_on: Interactive - customer pays and chooses allocations */ interface Widget extends BaseWidget<WidgetEventMap> { /** * Gets the current allocations */ getAllocations(): Allocation[]; /** * Refetches session data from the API * Useful for getting updated allocations or campaign data */ refresh(): Promise<void>; /** * Gets the session type of this widget * Returns the actual session type: "portion_of_sales", "portion_of_sales_choice", or "add_on" */ getType(): SessionType | null; } /** * Creates a widget instance * * The widget will fetch session data and adapt its behavior based on the session type: * - portion_of_sales: Non-interactive - displays allocations from the API (set on backend) * - portion_of_sales_choice: Interactive - vendor pays, customer chooses allocations * - add_on: Interactive - customer pays and chooses allocations * * @example * ```ts * const widget = createWidget({ * sessionId: 'session-123', * secret: 'secret-key', * debug: true * }) * * await widget.mount('#widget-container') * * // Check session type * const type = widget.getType() // "portion_of_sales", "portion_of_sales_choice", or "add_on" * * // Listen to allocation updates (for all types): * widget.on('allocations-updated', (data) => { * console.log('Allocations:', data.allocations) * console.log('Total:', data.totalAmount) * }) * * // Refresh session data at any time: * await widget.refresh() * ``` */ declare function createWidget(config: WidgetConfig): Widget; /** * Creates a preview widget instance for demonstrating widget appearance and behavior * * Preview widgets use mock session data and do not persist changes to the API. * All interactions are client-side only. * * @example * ```ts * const previewWidget = createPreviewWidget({ * type: 'add_on', * amount: 500, * available_campaigns: ['yellow-rooms', 'trees-for-the-future'], * styleMode: 'light' * }) * * await previewWidget.mount('#preview-container') * ``` */ declare function createPreviewWidget(config: PreviewWidgetConfig): Widget; export { type Allocation, type AllocationsUpdatedEventData, type Campaign, type DestroyedEventData, type ErrorEventData, type ImpactCalculation, type PreviewWidgetConfig, type ReadyEventData, type ResizeEventData, type SessionData, type SessionType, type Widget, type WidgetConfig, WidgetError, WidgetErrorCode, WidgetErrors, type WidgetEventMap, type WidgetStyleMode, createPreviewWidget, createWidget };