UNPKG

@explorins/pers-sdk

Version:

Platform-agnostic SDK for PERS (Phygital Experience Rewards System)

152 lines (134 loc) 4.14 kB
/** * PERS API Client - Core platform-agnostic client for PERS backend */ import { HttpClient, RequestOptions } from './abstractions/http-client'; import { PersConfig, buildApiRoot } from './pers-config'; export class PersApiClient { private readonly apiRoot: string; constructor( private httpClient: HttpClient, private config: PersConfig ) { // Build API root from environment and version this.apiRoot = buildApiRoot(config.environment, config.apiVersion); } /** * Get request headers including auth token and project key */ private async getHeaders(): Promise<Record<string, string>> { const headers: Record<string, string> = { 'Content-Type': 'application/json', }; // Add authentication token if (this.config.authProvider) { const token = await this.config.authProvider.getToken(); if (token) { headers['Authorization'] = `Bearer ${token}`; } } // Add project key if (this.config.authProvider) { const projectKey = await this.config.authProvider.getProjectKey(); if (projectKey) { headers['x-project-key'] = projectKey; } } else { // Fallback to config project key if no auth provider headers['x-project-key'] = this.config.apiProjectKey; } return headers; } /** * Make a request with proper headers, auth, and error handling */ private async request<T>( method: 'GET' | 'POST' | 'PUT' | 'DELETE', endpoint: string, body?: any, options?: { retryCount?: number } ): Promise<T> { const { retryCount = 0 } = options || {}; const url = `${this.apiRoot}${endpoint}`; const requestOptions: RequestOptions = { headers: await this.getHeaders(), timeout: this.config.timeout || 30000, }; try { switch (method) { case 'GET': return await this.httpClient.get<T>(url, requestOptions); case 'POST': return await this.httpClient.post<T>(url, body, requestOptions); case 'PUT': return await this.httpClient.put<T>(url, body, requestOptions); case 'DELETE': return await this.httpClient.delete<T>(url, requestOptions); default: throw new Error(`Unsupported HTTP method: ${method}`); } } catch (error: any) { // Handle 401 errors with automatic token refresh if (error.status === 401 && retryCount === 0 && this.config.authProvider?.onTokenExpired) { try { await this.config.authProvider.onTokenExpired(); // Retry once with refreshed token return this.request<T>(method, endpoint, body, { ...options, retryCount: 1 }); } catch (refreshError) { throw new PersApiError( `Authentication refresh failed: ${refreshError}`, endpoint, method, 401 ); } } throw new PersApiError( `PERS API request failed: ${error.message || error}`, endpoint, method, error.status ); } } /** * Generic GET request */ async get<T>(endpoint: string): Promise<T> { return this.request<T>('GET', endpoint); } /** * Generic POST request */ async post<T>(endpoint: string, body?: any): Promise<T> { return this.request<T>('POST', endpoint, body); } /** * Generic PUT request */ async put<T>(endpoint: string, body?: any): Promise<T> { return this.request<T>('PUT', endpoint, body); } /** * Generic DELETE request */ async delete<T>(endpoint: string): Promise<T> { return this.request<T>('DELETE', endpoint); } /** * Get current configuration */ getConfig(): PersConfig { return this.config; } } export class PersApiError extends Error { constructor( message: string, public endpoint: string, public method: string, public statusCode?: number ) { super(message); this.name = 'PersApiError'; } }