UNPKG

addtowallet

Version:

Create and manage Google Wallet and Apple Wallet passes with an easy-to-use API client for Node.js and browsers.

260 lines (259 loc) 9.52 kB
const DEFAULT_BASE_URL = 'https://app.addtowallet.co'; function buildQuery(params) { if (!params) return ''; const usp = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value === undefined || value === null) return; usp.append(key, String(value)); }); const qs = usp.toString(); return qs ? `?${qs}` : ''; } /** * AddToWallet API client for creating and managing wallet passes * * @example * ```ts * const client = new AddToWalletClient({ * apiKey: process.env.ADDTOWALLET_API_KEY! * }); * * const { cardId, passUrl } = await client.createPass({ * logoUrl: 'https://s3.amazonaws.com/i.addtowallet.co/assets/realestatelogo.png', * cardTitle: 'Your Business Name', * header: 'Amy Jane', * textModulesData: [ * { id: 'r1start', header: 'Phone', body: '+1 8888888888' }, * { id: 'r1end', header: 'Email', body: 'amy@gmail.com' } * ], * linksModuleData: [ * { id: 'r1', description: 'Call Us', uri: 'tel:+1 8287489293' }, * { id: 'r2', description: 'Email Us', uri: 'mailto:support@addtowallet.co' }, * { id: 'r3', description: 'Visit our website', uri: 'https://app.addtowallet.co' }, * { id: 'r4', description: 'Visit our office', uri: 'geo:https://maps.google.com/?q=123+Main+Street,+Anytown,+CA' } * ], * barcodeType: 'QR_CODE', * barcodeValue: '', * barcodeAltText: '', * hexBackgroundColor: '#141f31', * appleFontColor: '#FFFFFF', * heroImage: 'https://s3.amazonaws.com/i.addtowallet.co/assets/realestatehero.png' * }); * * console.log(cardId, passUrl); * ``` */ export class AddToWalletClient { /** * Creates a new AddToWallet client instance * * @param options - Configuration options for the client * @param options.apiKey - Secret API key from AddToWallet dashboard (server-side only) * @param options.getAuthToken - Function that returns a short-lived auth token (for browser use) * @param options.baseUrl - Base URL for the AddToWallet API (defaults to https://app.addtowallet.co) * @param options.fetchFn - Custom fetch implementation (for Node < 18 or custom behavior) * @param options.timeoutMs - Request timeout in milliseconds */ constructor(options) { this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, ''); this.apiKey = options.apiKey; this.getAuthToken = options.getAuthToken; this.fetchFn = options.fetchFn ?? fetch; this.timeoutMs = options.timeoutMs; this.defaultTracking = options.defaultTracking; } async resolveToken() { if (this.getAuthToken) { const token = await this.getAuthToken(); if (!token) throw new Error('getAuthToken returned empty token'); return token; } if (this.apiKey) return this.apiKey; throw new Error('No authentication configured. Please provide an API key or getAuthToken function.\n\n' + 'To get your API key:\n' + '1. Sign up at https://app.addtowallet.co\n' + '2. Navigate to the API Docs section\n' + '3. Copy your API key from the sidebar\n\n' + 'For more information, visit: https://app.addtowallet.co/api-docs'); } /** * Merges default tracking parameters with request-specific tracking parameters * Request-specific parameters override default ones */ mergeTrackingParams(requestTracking) { if (!this.defaultTracking && !requestTracking) return undefined; const merged = { ...this.defaultTracking, ...requestTracking, }; // Remove undefined values return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined)); } async request(options) { const controller = new AbortController(); const timeout = this.timeoutMs ? setTimeout(() => controller.abort(), this.timeoutMs) : undefined; const url = `${this.baseUrl}${options.path}${buildQuery(options.query)}`; const token = await this.resolveToken(); const res = await this.fetchFn(url, { method: options.method, headers: { 'Content-Type': 'application/json', 'apikey': token, }, body: options.body ? JSON.stringify(options.body) : undefined, signal: options.signal ?? controller.signal, }).catch((err) => { if (timeout) clearTimeout(timeout); throw err; }); if (timeout) clearTimeout(timeout); if (!res.ok) { const text = await res.text().catch(() => ''); const err = new Error(`AddToWallet API error ${res.status}: ${res.statusText} ${text}`.trim()); throw err; } const contentType = res.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return (await res.json()); } // Fallback to text payloads (e.g., signed JWT) return { url: await res.text() }; } /** * Creates a new wallet pass * * @param payload - The pass creation request * @returns Promise resolving to the created pass information, including `cardId` and `passUrl` * * @example * ```ts * const { cardId, passUrl } = await client.createPass({ * logoUrl: 'https://s3.amazonaws.com/i.addtowallet.co/assets/realestatelogo.png', * cardTitle: 'Your Business Name', * header: 'Amy Jane', * textModulesData: [ * { id: 'r1start', header: 'Phone', body: '+1 8888888888' }, * { id: 'r1end', header: 'Email', body: 'amy@gmail.com' } * ], * linksModuleData: [ * { id: 'r1', description: 'Call Us', uri: 'tel:+1 8287489293' }, * { id: 'r2', description: 'Email Us', uri: 'mailto:support@addtowallet.co' }, * { id: 'r3', description: 'Visit our website', uri: 'https://app.addtowallet.co' }, * { id: 'r4', description: 'Visit our office', uri: 'geo:https://maps.google.com/?q=123+Main+Street,+Anytown,+CA' } * ], * barcodeType: 'QR_CODE', * barcodeValue: '', * barcodeAltText: '', * hexBackgroundColor: '#141f31', * appleFontColor: '#FFFFFF', * heroImage: 'https://s3.amazonaws.com/i.addtowallet.co/assets/realestatehero.png', * }); * * console.log(cardId, passUrl); * ``` */ async createPass(payload) { // Merge default tracking with request-specific tracking const mergedTracking = this.mergeTrackingParams(payload.tracking); // Create the request payload with merged tracking const requestPayload = { ...payload, tracking: mergedTracking, }; const res = await this.request({ method: 'POST', path: '/api/card/create', body: requestPayload, }); if (res && res.cardId && !res.passUrl) { const passUrl = `${this.baseUrl}/card/${encodeURIComponent(res.cardId)}`; return { ...res, passUrl, tracking: mergedTracking }; } return { ...res, tracking: mergedTracking }; } /** * Updates an existing wallet pass * * @param cardId - The ID of the pass to update * @param payload - The pass update request * @returns Promise resolving to the updated pass information * * @example * ```ts * await client.updatePass('66b6005bb7bddce8f05a3392', { * cardTitle: 'Updated Card Title', * header: 'Updated Header', * hexBackgroundColor: '#33FF57' * }); * ``` */ async updatePass(cardId, payload) { return this.request({ method: 'PUT', path: `/api/card/edit/${encodeURIComponent(cardId)}`, body: payload, }); } /** * Deletes a wallet pass by ID * * @param cardId - The ID of the pass to delete * @returns Promise resolving to deletion confirmation * * @example * ```ts * const { msg } = await client.deletePass('66b6005bb7bddce8f05a3392'); * console.log('Pass deleted:', msg); * ``` */ async deletePass(cardId) { return this.request({ method: 'DELETE', path: `/api/card/delete/${encodeURIComponent(cardId)}`, }); } /** * Gets current credit information * * @returns Promise resolving to credit information * * @example * ```ts * const { premiumCredits, freeCredits } = await client.getCredits(); * console.log(`Premium credits: ${premiumCredits}, Free credits: ${freeCredits}`); * ``` */ async getCredits() { return this.request({ method: 'GET', path: '/api/getCredits', }); } /** * Gets a specific pass by ID * * @param cardId - The ID of the pass to retrieve * @returns Promise resolving to pass details * * @example * ```ts * const pass = await client.getPass('66b6005bb7bddce8f05a3392'); * console.log('Pass title:', pass.cardTitle); * ``` */ async getPass(cardId) { return this.request({ method: 'GET', path: `/api/card/${encodeURIComponent(cardId)}`, }); } }