UNPKG

recoder-shared

Version:

Shared types, utilities, and configurations for Recoder

406 lines 13.7 kB
"use strict"; /** * Unified Authentication Client for Recoder.xyz * Supports all platforms: CLI, Web, Mobile, Desktop, Extension */ Object.defineProperty(exports, "__esModule", { value: true }); exports.authClient = exports.AuthClient = void 0; const tslib_1 = require("tslib"); const axios_1 = tslib_1.__importDefault(require("axios")); const events_1 = require("events"); class AuthClient extends events_1.EventEmitter { constructor(baseURL = 'http://localhost:3001') { super(); this.currentUser = null; this.tokens = null; this.deviceInfo = null; this.refreshPromise = null; this.baseURL = baseURL; this.api = axios_1.default.create({ baseURL: `${baseURL}/api`, timeout: 10000, headers: { 'Content-Type': 'application/json', 'User-Agent': this.getUserAgent() } }); this.setupInterceptors(); this.loadFromStorage(); } getUserAgent() { if (typeof window !== 'undefined' && window) { return `Recoder-Web/${this.getVersion()}`; } else if (typeof process !== 'undefined' && process) { return `Recoder-CLI/${this.getVersion()} (${process.platform})`; } return `Recoder-Client/${this.getVersion()}`; } getVersion() { try { // Try to get version from package.json return '1.0.0'; // Fallback version } catch { return '1.0.0'; } } setupInterceptors() { // Request interceptor to add auth token this.api.interceptors.request.use((config) => { if (this.tokens?.accessToken) { config.headers.Authorization = `Bearer ${this.tokens.accessToken}`; } return config; }); // Response interceptor to handle token refresh this.api.interceptors.response.use((response) => response, async (error) => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; const refreshed = await this.refreshAccessToken(); if (refreshed) { originalRequest.headers.Authorization = `Bearer ${this.tokens.accessToken}`; return this.api(originalRequest); } else { this.logout(); } } return Promise.reject(error); }); } // Authentication Methods async register(email, password, name, organization) { try { const response = await this.api.post('/auth/register', { email, password, name, organization }); await this.handleAuthSuccess(response.data); return response.data; } catch (error) { throw this.handleAuthError(error); } } async login(email, password) { try { const response = await this.api.post('/auth/login', { email, password }); await this.handleAuthSuccess(response.data); return response.data; } catch (error) { throw this.handleAuthError(error); } } async loginWithGoogle(authCode, redirectUri) { try { const response = await this.api.post('/oauth/google', { code: authCode, redirectUri }); await this.handleAuthSuccess(response.data); return response.data; } catch (error) { throw this.handleAuthError(error); } } async loginWithGitHub(authCode, state) { try { const response = await this.api.post('/oauth/github', { code: authCode, state }); await this.handleAuthSuccess(response.data); return response.data; } catch (error) { throw this.handleAuthError(error); } } async refreshAccessToken() { if (this.refreshPromise) { return this.refreshPromise; } if (!this.tokens?.refreshToken) { return false; } this.refreshPromise = this._performRefresh(); const result = await this.refreshPromise; this.refreshPromise = null; return result; } async _performRefresh() { try { const response = await this.api.post('/auth/refresh', { refreshToken: this.tokens.refreshToken }); this.tokens = { accessToken: response.data.data.accessToken, refreshToken: response.data.data.refreshToken }; this.saveToStorage(); this.emit('tokenRefreshed', this.tokens); return true; } catch (error) { console.error('Token refresh failed:', error); return false; } } async logout() { try { if (this.tokens?.accessToken) { await this.api.post('/auth/logout'); } } catch (error) { console.error('Logout request failed:', error); } finally { this.clearAuth(); this.emit('logout'); } } // Device Management async registerDevice(deviceInfo) { const deviceId = await this.generateDeviceId(deviceInfo.deviceType, deviceInfo.platform); const fullDeviceInfo = { ...deviceInfo, deviceId }; try { await this.api.post('/devices/register', fullDeviceInfo); this.deviceInfo = fullDeviceInfo; this.saveToStorage(); this.emit('deviceRegistered', fullDeviceInfo); return fullDeviceInfo; } catch (error) { throw this.handleAuthError(error); } } async getDevices() { try { const response = await this.api.get('/devices'); return response.data.data; } catch (error) { throw this.handleAuthError(error); } } async sendHeartbeat(metadata) { if (!this.deviceInfo) return; try { await this.api.post(`/devices/${this.deviceInfo.deviceId}/heartbeat`, { metadata }); } catch (error) { console.error('Heartbeat failed:', error); } } // OAuth Account Management async getOAuthAccounts() { try { const response = await this.api.get('/oauth/accounts'); return response.data.data; } catch (error) { throw this.handleAuthError(error); } } async disconnectOAuthProvider(provider) { try { await this.api.delete(`/oauth/disconnect/${provider}`); this.emit('oauthDisconnected', provider); } catch (error) { throw this.handleAuthError(error); } } // User Profile Management async getCurrentUser() { try { const response = await this.api.get('/auth/me'); this.currentUser = response.data.data; return response.data.data; } catch (error) { throw this.handleAuthError(error); } } async updateProfile(updates) { try { const response = await this.api.put('/auth/update-profile', updates); this.currentUser = response.data.data; this.emit('profileUpdated', response.data.data); return response.data.data; } catch (error) { throw this.handleAuthError(error); } } async changePassword(currentPassword, newPassword) { try { await this.api.put('/auth/change-password', { currentPassword, newPassword }); this.emit('passwordChanged'); } catch (error) { throw this.handleAuthError(error); } } // Utility Methods isAuthenticated() { return !!(this.tokens?.accessToken && this.currentUser); } getUser() { return this.currentUser; } getTokens() { return this.tokens; } getDeviceInfo() { return this.deviceInfo; } // Private Helper Methods async handleAuthSuccess(response) { this.currentUser = response.data.user; this.tokens = { accessToken: response.data.accessToken, refreshToken: response.data.refreshToken }; this.saveToStorage(); this.emit('authenticated', { user: this.currentUser, tokens: this.tokens }); // Auto-register device if not already registered if (!this.deviceInfo && typeof window !== 'undefined' && window && typeof navigator !== 'undefined' && navigator) { // Web platform await this.registerDevice({ name: `${navigator.userAgent.includes('Chrome') ? 'Chrome' : 'Browser'} - ${new Date().toLocaleDateString()}`, deviceType: 'web', platform: 'browser' }); } } handleAuthError(error) { const message = error.response?.data?.error?.message || error.message || 'Authentication failed'; const authError = new Error(message); this.emit('authError', authError); return authError; } clearAuth() { this.currentUser = null; this.tokens = null; this.removeFromStorage(); } async generateDeviceId(deviceType, platform) { try { const response = await this.api.post('/devices/generate-id', { deviceType, platform }); return response.data.data.deviceId; } catch (error) { // Fallback to client-side generation const timestamp = Date.now().toString(36); const random = Math.random().toString(36).substr(2, 8); return `${deviceType}-${platform}-${timestamp}-${random}`; } } // Storage Methods (Platform-specific implementations should override these) saveToStorage() { const data = { user: this.currentUser, tokens: this.tokens, device: this.deviceInfo }; if (typeof window !== 'undefined' && window && window.localStorage && typeof localStorage !== 'undefined') { // Web platform localStorage.setItem('recoder-auth', JSON.stringify(data)); } else if (typeof process !== 'undefined' && process) { // Node.js platform (CLI) const fs = require('fs'); const path = require('path'); const os = require('os'); try { const configDir = path.join(os.homedir(), '.recoder'); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } const authFile = path.join(configDir, 'auth.json'); fs.writeFileSync(authFile, JSON.stringify(data, null, 2)); } catch (error) { console.error('Failed to save auth data:', error); } } } loadFromStorage() { try { let data = null; if (typeof window !== 'undefined' && window && window.localStorage && typeof localStorage !== 'undefined') { // Web platform const stored = localStorage.getItem('recoder-auth'); if (stored) { data = JSON.parse(stored); } } else if (typeof process !== 'undefined' && process) { // Node.js platform (CLI) const fs = require('fs'); const path = require('path'); const os = require('os'); const authFile = path.join(os.homedir(), '.recoder', 'auth.json'); if (fs.existsSync(authFile)) { const content = fs.readFileSync(authFile, 'utf-8'); data = JSON.parse(content); } } if (data) { this.currentUser = data.user; this.tokens = data.tokens; this.deviceInfo = data.device; } } catch (error) { console.error('Failed to load auth data:', error); this.clearAuth(); } } removeFromStorage() { if (typeof window !== 'undefined' && window && window.localStorage && typeof localStorage !== 'undefined') { localStorage.removeItem('recoder-auth'); } else if (typeof process !== 'undefined' && process) { const fs = require('fs'); const path = require('path'); const os = require('os'); try { const authFile = path.join(os.homedir(), '.recoder', 'auth.json'); if (fs.existsSync(authFile)) { fs.unlinkSync(authFile); } } catch (error) { console.error('Failed to remove auth data:', error); } } } } exports.AuthClient = AuthClient; // Export singleton instance for convenience exports.authClient = new AuthClient(); // Export for custom instances exports.default = AuthClient; //# sourceMappingURL=auth-client.js.map