UNPKG

@vizzly-testing/cli

Version:

Visual review platform for UI developers and designers

146 lines (137 loc) 5 kB
/** * Cloud Proxy Router * Proxies requests to Vizzly cloud API with OAuth authentication * * This router transparently handles OAuth token management: * - Reads tokens from ~/.vizzly/config.json via authService * - Adds Authorization header to outgoing requests * - Handles token refresh on 401 responses * - Returns proxied response to React app */ import { parseJsonBody } from '../middleware/json-parser.js'; import { sendSuccess, sendError, sendServiceUnavailable } from '../middleware/response.js'; import * as output from '../../utils/output.js'; /** * Create cloud proxy router * @param {Object} context - Router context * @param {Object} context.authService - Auth service for token management * @param {string} context.apiUrl - Base API URL (default: https://app.vizzly.dev) * @returns {Function} Route handler */ export function createCloudProxyRouter({ authService, apiUrl: _apiUrl = 'https://app.vizzly.dev' }) { /** * Make an authenticated request to the cloud API * @param {string} endpoint - API endpoint (e.g., /api/cli/projects) * @param {Object} options - Fetch options * @returns {Promise<Object>} Response data */ async function proxyRequest(endpoint, options = {}) { if (!authService) { throw new Error('Auth service not available'); } // Use authService.authenticatedRequest which handles token refresh return authService.authenticatedRequest(endpoint, options); } return async function handleCloudProxyRoute(req, res, pathname, parsedUrl) { // Only handle /api/cloud/* routes if (!pathname.startsWith('/api/cloud')) { return false; } // Check auth service availability if (!authService) { sendServiceUnavailable(res, 'Auth service'); return true; } // Route: GET /api/cloud/projects - List user's projects if (req.method === 'GET' && pathname === '/api/cloud/projects') { try { let response = await proxyRequest('/api/cli/projects', { method: 'GET' }); sendSuccess(res, { projects: response.projects || [] }); return true; } catch (error) { output.error('Error fetching projects from cloud:', error); // Return empty array instead of error for better UX when not logged in if (error.message?.includes('not authenticated') || error.code === 'AUTH_ERROR') { sendSuccess(res, { projects: [], authenticated: false }); } else { sendError(res, 500, error.message); } return true; } } // Route: GET /api/cloud/organizations/:org/projects/:project/builds let buildsMatch = pathname.match(/^\/api\/cloud\/organizations\/([^/]+)\/projects\/([^/]+)\/builds$/); if (req.method === 'GET' && buildsMatch) { try { let organizationSlug = decodeURIComponent(buildsMatch[1]); let projectSlug = decodeURIComponent(buildsMatch[2]); let limit = parsedUrl.searchParams.get('limit') || '20'; let branch = parsedUrl.searchParams.get('branch'); let queryParams = new URLSearchParams(); if (limit) queryParams.append('limit', limit); if (branch) queryParams.append('branch', branch); let query = queryParams.toString(); let endpoint = `/api/cli/organizations/${organizationSlug}/projects/${projectSlug}/builds${query ? `?${query}` : ''}`; let response = await proxyRequest(endpoint, { method: 'GET' }); sendSuccess(res, { builds: response.builds || [] }); return true; } catch (error) { output.error('Error fetching builds from cloud:', error); sendError(res, 500, error.message); return true; } } // Route: POST /api/cloud/baselines/download - Download baselines from build if (req.method === 'POST' && pathname === '/api/cloud/baselines/download') { try { let body = await parseJsonBody(req); let { buildId, screenshotNames } = body; if (!buildId) { sendError(res, 400, 'buildId is required'); return true; } // Download baselines from the specified build let response = await proxyRequest('/api/cli/baselines/download', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ buildId, screenshotNames }) }); sendSuccess(res, { success: true, message: `Baselines downloaded from build ${buildId}`, ...response }); return true; } catch (error) { output.error('Error downloading baselines from cloud:', error); sendError(res, 500, error.message); return true; } } // Unknown cloud route sendError(res, 404, 'Cloud API endpoint not found'); return true; }; }