@digital-passports/javascript-sdk
Version:
JavaScript SDK for interacting with the Digital Passport Hub REST API.
170 lines (158 loc) • 5.94 kB
JavaScript
// 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',
});