UNPKG

@visionfi/desktop-sdk

Version:

Desktop SDK for VisionFI Cloud Run services with Azure AD authentication

209 lines (208 loc) 7.26 kB
/** * VisionFI Desktop Client * Copyright (c) 2024-2025 VisionFI. All Rights Reserved. */ import { EventEmitter } from 'eventemitter3'; import axios from 'axios'; import { VisionFiError } from '@visionfi/core'; import { PackageClient, AdminClient } from './clients/index.js'; /** * Desktop client for VisionFI Cloud Run services * Provides Azure AD authentication and desktop-specific features */ export class VisionFIDesktop extends EventEmitter { config; apiClient; connectionStatus = 'offline'; // Client instances for nested API packages; admin; constructor(config) { super(); this.config = config; // Create axios instance this.apiClient = axios.create({ baseURL: config.tenantApiUrl, timeout: config.timeout || 30000, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }); // Add authentication interceptor this.apiClient.interceptors.request.use(async (request) => { try { const token = await this.config.getAccessToken(); request.headers.Authorization = `Bearer ${token}`; } catch (error) { this.emit('auth:error', error); throw new VisionFiError('Failed to get access token', 401, 'auth_error'); } return request; }); // Add response interceptor for error handling this.apiClient.interceptors.response.use((response) => response, async (error) => { if (error.response?.status === 401) { this.emit('auth:expired'); if (this.config.onTokenExpired) { this.config.onTokenExpired(); } } return Promise.reject(error); }); // Initialize client instances this.packages = new PackageClient(this.apiClient); this.admin = new AdminClient(this.apiClient); } /** * Connect to the Cloud Run service */ async connect() { this.connectionStatus = 'connecting'; this.emit('connection:connecting'); try { // Test connection with a simple request await this.apiClient.get('/health'); this.connectionStatus = 'online'; this.emit('connection:online'); } catch (error) { this.connectionStatus = 'offline'; this.emit('connection:offline', error); throw new VisionFiError('Failed to connect to Cloud Run service', 503, 'connection_error'); } } /** * Disconnect from the service */ async disconnect() { this.connectionStatus = 'offline'; this.emit('connection:offline'); } /** * Get current connection status */ getConnectionStatus() { return this.connectionStatus; } // Package Management Methods (delegated to managers for consistency) /** * Create a new package * @deprecated Use client.packages.create() instead */ async createPackage(options) { return this.packages.create(options); } /** * List packages with optional filtering * @deprecated Use client.packages.list() instead */ async listPackages(options) { return this.packages.list(options); } /** * Get a specific package by ID * @deprecated Use client.packages.get() instead */ async getPackage(packageId) { return this.packages.get(packageId); } // Document Analysis Methods /** * Analyze a document with upload progress tracking */ async analyzeDocument(fileBuffer, options, onProgress) { try { const fileBase64 = fileBuffer.toString('base64'); const totalBytes = fileBuffer.length; let bytesUploaded = 0; const startTime = Date.now(); // Simulate progress for demo (in real implementation, use axios upload progress) if (onProgress) { const progressInterval = setInterval(() => { bytesUploaded = Math.min(bytesUploaded + totalBytes / 10, totalBytes); const percentage = Math.round((bytesUploaded / totalBytes) * 100); const elapsedTime = (Date.now() - startTime) / 1000; const speed = bytesUploaded / elapsedTime; const remainingTime = (totalBytes - bytesUploaded) / speed; onProgress({ fileName: options.fileName, bytesUploaded, totalBytes, percentage, speed, remainingTime }); if (bytesUploaded >= totalBytes) { clearInterval(progressInterval); } }, 100); } const response = await this.apiClient.post('/analyze', { fileBase64, ...options }); return response.data; } catch (error) { throw this.handleError(error, 'Failed to analyze document'); } } /** * Get analysis results */ async getResults(jobUuid, pollInterval, maxAttempts) { if (!pollInterval) { try { const response = await this.apiClient.get(`/results/${jobUuid}`); return response.data; } catch (error) { throw this.handleError(error, 'Failed to get results'); } } // Polling implementation const interval = pollInterval || 3000; const attempts = maxAttempts || 20; return new Promise((resolve, reject) => { let attemptCount = 0; const poll = async () => { attemptCount++; try { const response = await this.apiClient.get(`/results/${jobUuid}`); const result = response.data; if (result.found === false && attemptCount < attempts) { setTimeout(poll, interval); return; } resolve(result); } catch (error) { reject(this.handleError(error, 'Error while polling for results')); } }; poll(); }); } // Administrative Methods /** * Get available product types * @deprecated Use client.admin.getProductTypes() instead */ async getProductTypes() { return this.admin.getProductTypes(); } /** * Handle errors consistently */ handleError(error, defaultMessage) { if (error instanceof VisionFiError) { return error; } const message = error.response?.data?.message || error.message || defaultMessage; const statusCode = error.response?.status; const code = error.response?.data?.code || 'unknown_error'; return new VisionFiError(message, statusCode, code); } }