@visionfi/server-sdk
Version:
Server-side SDK for VisionFI API access using Google Service Account authentication
141 lines (140 loc) • 6.05 kB
JavaScript
/**
* 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;
}
}
}