UNPKG

@visionfi/server-sdk

Version:

Server-side SDK for VisionFI API access using Google Service Account authentication

141 lines (140 loc) 6.05 kB
/** * VisionFI Server SDK Client * The use cases for this SDK include backend services, server applications, and automated workflows that need to interact with the VisionFI platform. * The VisionFI Server SDK provides a client to interact with the VisionFI API using service-to-service authentication. * The SDK supports authentication via: * - Google Application Default Credentials (ADC) * - Service account JSON file * - Service account impersonation (for internal developers with IAM permissions) * It uses ID tokens for secure communication with the VisionFI API. * It includes clients for managing packages, authentication, and administrative tasks. * It handles token management, request signing, and error handling. * Copyright (c) 2024-2025 VisionFI. All Rights Reserved. */ import axios from 'axios'; import { GoogleAuth, Impersonated } from 'google-auth-library'; import { VisionFiError } from '@visionfi/core'; import { PackageClient } from './clients/package/PackageClient.js'; import { AdminClient } from './clients/admin/AdminClient.js'; import { AuthClient } from './clients/auth/AuthClient.js'; export class VisionFI { apiClient; auth; impersonatedClient; config; initPromise; packages; admin; authentication; constructor(config = {}) { this.config = { apiUrl: config.apiUrl || 'https://platform.visionfi.ai/api/v1', audience: config.audience || 'platform.visionfi.ai', ...config }; // Initialize Google Auth (without scopes for ID token) this.auth = config.authClient || new GoogleAuth(); // Initialize impersonation asynchronously if requested this.initPromise = this.initializeImpersonation(); // Create axios instance this.apiClient = axios.create({ baseURL: this.config.apiUrl, timeout: 60000, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }); // Add request interceptor for authentication this.apiClient.interceptors.request.use(async (config) => { try { // Set Host header if not already provided (needed for proper load balancer routing) if (!config.headers['Host'] && !config.headers['host']) { config.headers['Host'] = new URL(this.config.apiUrl).hostname; } // Ensure impersonation is initialized await this.initPromise; let token; // Use impersonated credentials if configured if (this.impersonatedClient) { // fetchIdToken returns the token string directly token = await this.impersonatedClient.fetchIdToken(this.config.audience); } else { // Use regular ID token client const client = await this.auth.getIdTokenClient(this.config.audience); const headers = await client.getRequestHeaders(); token = headers.Authorization?.replace('Bearer ', '') || ''; } // Add the Authorization header if (token) { config.headers.Authorization = `Bearer ${token}`; } // Add API key if provided if (this.config.apiKey) { config.headers['x-api-key'] = this.config.apiKey; } return config; } catch (error) { console.error('Authentication error:', error); throw new VisionFiError('Failed to authenticate request', 401, 'auth_error'); } }); // Add response interceptor for error handling this.apiClient.interceptors.response.use((response) => response, (error) => { if (error.response) { const { status, data } = error.response; const message = data?.message || error.message; const code = data?.code || 'api_error'; throw new VisionFiError(message, status, code); } else if (error.request) { throw new VisionFiError('No response from server', 0, 'network_error'); } else { throw new VisionFiError(error.message, 0, 'unknown_error'); } }); // Initialize clients this.packages = new PackageClient(this.apiClient); this.admin = new AdminClient(this.apiClient); this.authentication = new AuthClient(this.apiClient); } /** * Initialize impersonation if configured */ async initializeImpersonation() { if (this.config.impersonateServiceAccount) { try { // Get the authenticated source client from GoogleAuth // This will use ADC credentials const sourceClient = await this.auth.getClient(); // Create impersonated credentials this.impersonatedClient = new Impersonated({ sourceClient: sourceClient, targetPrincipal: this.config.impersonateServiceAccount, lifetime: this.config.impersonationLifetime || 3600, targetScopes: ['https://www.googleapis.com/auth/cloud-platform'] }); } catch (error) { console.error('Failed to initialize impersonation:', error); throw new VisionFiError(`Failed to initialize service account impersonation: ${error}`, 500, 'impersonation_error'); } } } /** * Test connection to the API */ async testConnection() { try { await this.admin.getProductTypes(); return true; } catch (error) { console.error('Connection test failed:', error); return false; } } }