UNPKG

@oxyhq/services

Version:

Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀

287 lines (258 loc) • 8.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OxyServicesBase = void 0; var _jwtDecode = require("jwt-decode"); var _errorUtils = require("../utils/errorUtils"); var _HttpService = require("./HttpService"); var _OxyServices = require("./OxyServices.errors"); /** * OxyServices Base Class * * Contains core infrastructure, HTTP client, request management, and error handling */ /** * Base class for OxyServices with core infrastructure */ class OxyServicesBase { constructor(...args) { const config = args[0]; if (!config || typeof config !== 'object') { throw new Error('OxyConfig is required'); } this.config = config; this.cloudURL = config.cloudURL || 'https://cloud.oxy.so'; // Initialize unified HTTP service (handles auth, caching, deduplication, queuing, retry) this.httpService = new _HttpService.HttpService(config); } // Test-only utility to reset global tokens between jest tests static __resetTokensForTests() { _HttpService.HttpService.__resetTokensForTests(); } /** * Make a request with all performance optimizations * This is the main method for all API calls - ensures authentication and performance features */ async makeRequest(method, url, data, options = {}) { return this.httpService.request({ method, url, data: method !== 'GET' ? data : undefined, params: method === 'GET' ? data : undefined, ...options }); } // ============================================================================ // CORE METHODS (HTTP Client, Token Management, Error Handling) // ============================================================================ /** * Get the configured Oxy API base URL */ getBaseURL() { return this.httpService.getBaseURL(); } /** * Get the HTTP service instance * Useful for advanced use cases where direct access to the HTTP service is needed */ getClient() { return this.httpService; } /** * Get performance metrics */ getMetrics() { return this.httpService.getMetrics(); } /** * Clear request cache */ clearCache() { this.httpService.clearCache(); } /** * Clear specific cache entry */ clearCacheEntry(key) { this.httpService.clearCacheEntry(key); } /** * Get cache statistics */ getCacheStats() { return this.httpService.getCacheStats(); } /** * Get the configured Oxy Cloud (file storage/CDN) URL */ getCloudURL() { return this.cloudURL; } /** * Set authentication tokens */ setTokens(accessToken, refreshToken = '') { this.httpService.setTokens(accessToken, refreshToken); } /** * Clear stored authentication tokens */ clearTokens() { this.httpService.clearTokens(); } /** * Get the current user ID from the access token */ getCurrentUserId() { const accessToken = this.httpService.getAccessToken(); if (!accessToken) { return null; } try { const decoded = (0, _jwtDecode.jwtDecode)(accessToken); return decoded.userId || decoded.id || null; } catch (error) { return null; } } /** * Check if the client has a valid access token (public method) */ hasValidToken() { return this.httpService.hasAccessToken(); } /** * Get the raw access token (for constructing anchor URLs when needed) */ getAccessToken() { return this.httpService.getAccessToken(); } /** * Wait for authentication to be ready * * Optimized for high-scale usage with immediate synchronous check and adaptive polling. * Returns immediately if token is already available (0ms delay), otherwise uses * adaptive polling that starts fast (50ms) and gradually increases to reduce CPU usage. * * @param timeoutMs Maximum time to wait in milliseconds (default: 5000ms) * @returns Promise that resolves to true if authentication is ready, false if timeout * * @example * ```typescript * const isReady = await oxyServices.waitForAuth(3000); * if (isReady) { * // Proceed with authenticated operations * } * ``` */ async waitForAuth(timeoutMs = 5000) { // Immediate synchronous check - no delay if token is ready if (this.httpService.hasAccessToken()) { return true; } const startTime = performance.now(); const maxTime = startTime + timeoutMs; // Adaptive polling: start fast, then slow down to reduce CPU usage let pollInterval = 50; // Start with 50ms while (performance.now() < maxTime) { await new Promise(resolve => setTimeout(resolve, pollInterval)); if (this.httpService.hasAccessToken()) { return true; } // Increase interval after first few checks (adaptive polling) // This reduces CPU usage for long waits while maintaining responsiveness if (pollInterval < 200) { pollInterval = Math.min(pollInterval * 1.5, 200); } } return false; } /** * Execute a function with automatic authentication retry logic * This handles the common case where API calls are made before authentication completes */ async withAuthRetry(operation, operationName, options = {}) { const { maxRetries = 2, retryDelay = 1000, authTimeoutMs = 5000 } = options; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { // First attempt: check if we have a token if (!this.httpService.hasAccessToken()) { if (attempt === 0) { // On first attempt, wait briefly for authentication to complete const authReady = await this.waitForAuth(authTimeoutMs); if (!authReady) { throw new _OxyServices.OxyAuthenticationTimeoutError(operationName, authTimeoutMs); } } else { // On retry attempts, fail immediately if no token throw new _OxyServices.OxyAuthenticationError(`Authentication required: ${operationName} requires a valid access token.`, 'AUTH_REQUIRED'); } } // Execute the operation return await operation(); } catch (error) { const isLastAttempt = attempt === maxRetries; const errorObj = error && typeof error === 'object' ? error : null; const isAuthError = errorObj?.response?.status === 401 || errorObj?.code === 'MISSING_TOKEN' || errorObj?.message?.includes('Authentication') || error instanceof _OxyServices.OxyAuthenticationError; if (isAuthError && !isLastAttempt && !(error instanceof _OxyServices.OxyAuthenticationTimeoutError)) { await new Promise(resolve => setTimeout(resolve, retryDelay)); continue; } // If it's not an auth error, or it's the last attempt, throw the error if (error instanceof _OxyServices.OxyAuthenticationError) { throw error; } throw this.handleError(error); } } // This should never be reached, but TypeScript requires it throw new _OxyServices.OxyAuthenticationError(`${operationName} failed after ${maxRetries + 1} attempts`); } /** * Validate the current access token with the server */ async validate() { if (!this.hasValidToken()) { return false; } try { const res = await this.makeRequest('GET', '/api/auth/validate', undefined, { cache: false, retry: false }); return res.valid === true; } catch (error) { return false; } } /** * Centralized error handling */ handleError(error) { const api = (0, _errorUtils.handleHttpError)(error); const err = new Error(api.message); err.code = api.code; err.status = api.status; err.details = api.details; return err; } /** * Health check endpoint */ async healthCheck() { try { return await this.makeRequest('GET', '/health', undefined, { cache: false }); } catch (error) { throw this.handleError(error); } } } exports.OxyServicesBase = OxyServicesBase; //# sourceMappingURL=OxyServices.base.js.map