UNPKG

@digital-passports/javascript-sdk

Version:

JavaScript SDK for interacting with the Digital Passport Hub REST API.

170 lines (158 loc) 5.94 kB
// Digital Passport Hub JavaScript SDK // See: https://digitalpassporthub.com/documentation // Use a static version string or replace at build time export const version = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'unknown'; /** * Custom error for Digital Passport SDK */ export class DigitalPassportError extends Error { /** * @param {string} message * @param {number} [status] * @param {any} [details] * @param {string} [code] */ constructor(message, status, details, code) { super(message); this.name = 'DigitalPassportError'; this.status = status; this.details = details; this.code = code; } } const BASE_URL = 'https://api.digitalpassporthub.com/v1/'; /** * @typedef {Object} DigitalPassportOptions * @property {string} [apiKey] - API key for authentication * @property {string} [environment] - Environment (default: 'production') * @property {Function} [fetch] - Custom fetch implementation */ /** * Digital Passport SDK */ export class DigitalPassport { /** * @param {DigitalPassportOptions} options */ constructor({ apiKey, environment = 'production', fetch } = {}) { this.apiKey = apiKey || (typeof process !== 'undefined' && process.env ? process.env.DIGITAL_PASSPORT_API_KEY : undefined); if (!this.apiKey) throw new DigitalPassportError('API key is required'); this.environment = environment; this._fetch = fetch || null; } async _getFetch() { if (this._fetch) return this._fetch; if (typeof fetch === 'function') return fetch.bind(globalThis); // Node.js: dynamically import node-fetch if (typeof process !== 'undefined' && process.versions && process.versions.node) { const mod = await import('node-fetch'); return mod.default || mod; } throw new DigitalPassportError('No fetch implementation found. Please provide a fetch function in the options.'); } async _request(path, options = {}) { const url = BASE_URL + path; const headers = { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', ...(options.headers || {}) }; try { const fetchImpl = await this._getFetch(); let response; try { response = await fetchImpl(url, { ...options, headers }); } catch (networkErr) { // Network error (e.g., DNS, offline, CORS) throw new DigitalPassportError( `Network error while fetching ${url}: ${networkErr.message || networkErr}`, undefined, { originalError: networkErr }, ErrorCode.NETWORK_ERROR ); } const contentType = response.headers.get('content-type'); let data; if (contentType && contentType.includes('application/json')) { data = await response.json(); } else { data = await response.text(); } if (!response.ok) { let code = ErrorCode.HTTP_ERROR; if (response.status === 401 || response.status === 403) code = ErrorCode.AUTH_ERROR; else if (response.status === 404) code = ErrorCode.NOT_FOUND; else if (response.status >= 500) code = ErrorCode.SERVER_ERROR; else if (response.status >= 400 && response.status < 500) code = ErrorCode.VALIDATION_ERROR; throw new DigitalPassportError( `HTTP ${response.status}: ${data && data.message ? data.message : data}`, response.status, data, code ); } return data; } catch (err) { if (err instanceof DigitalPassportError) throw err; throw new DigitalPassportError( `Unexpected error while fetching ${url}: ${err.message}`, undefined, { originalError: err }, ErrorCode.UNEXPECTED_ERROR ); } } /** * List all Digital Product Passports * @returns {Promise<object>} Paginated list of passports */ async listPassports() { return this._request('passports', { method: 'GET' }); } /** * Create a new Digital Product Passport * @param {object} data - Passport data (sku, name, etc.) * @returns {Promise<object>} Created passport object */ async createPassport(data) { // Enhanced validation if (!data || typeof data !== 'object') { throw new DigitalPassportError('Passport data must be an object', undefined, data, ErrorCode.VALIDATION_ERROR); } if (!data.sku || typeof data.sku !== 'string' || !data.sku.trim()) { throw new DigitalPassportError('Passport data must include a non-empty sku', undefined, data, ErrorCode.VALIDATION_ERROR); } if (!data.name || typeof data.name !== 'string' || !data.name.trim()) { throw new DigitalPassportError('Passport data must include a non-empty name', undefined, data, ErrorCode.VALIDATION_ERROR); } return this._request('passports', { method: 'POST', body: JSON.stringify(data) }); } /** * Generate a QR code for a specific passport * @param {string} passportId - ID of the passport * @param {object} options - QR code options (size, format) * @returns {Promise<object>} QR code data */ async generateQRCode(passportId, options = {}) { if (!passportId) throw new DigitalPassportError('passportId is required'); const params = new URLSearchParams(options).toString(); const path = `passports/${passportId}/qrcode${params ? '?' + params : ''}`; return this._request(path, { method: 'GET' }); } } export default DigitalPassport; export const ErrorCode = Object.freeze({ NETWORK_ERROR: 'NETWORK_ERROR', VALIDATION_ERROR: 'VALIDATION_ERROR', AUTH_ERROR: 'AUTH_ERROR', NOT_FOUND: 'NOT_FOUND', SERVER_ERROR: 'SERVER_ERROR', HTTP_ERROR: 'HTTP_ERROR', UNEXPECTED_ERROR: 'UNEXPECTED_ERROR', });