UNPKG

tdpw

Version:

CLI tool for uploading Playwright test reports to TestDino platform with Azure storage support

208 lines 8.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SasTokenService = void 0; const types_1 = require("../types"); // Endpoint for SAS token const SAS_ENDPOINT = '/api/storage/token'; /** * Service to request and manage Azure SAS tokens from TestDino API */ class SasTokenService { config; baseUrl; apiKey; constructor(config) { this.config = config; this.baseUrl = config.apiUrl; this.apiKey = config.token; } /** * Headers to include on every SAS token request */ getHeaders() { return { 'Content-Type': 'application/json', 'User-Agent': 'tdpw/1.0.0', 'X-API-Key': this.apiKey, // Fixed: was 'X-API-key', now matches curl }; } /** * Request a SAS token from TestDino API using POST method */ async requestSasToken() { const url = `${this.baseUrl}${SAS_ENDPOINT}`; let response; try { // Fixed: Use POST method with proper headers as shown in curl response = await fetch(url, { method: 'POST', // Changed from GET to POST headers: this.getHeaders(), // Empty body for POST request body: JSON.stringify({}), }); } catch (error) { throw new types_1.NetworkError(`Network error requesting SAS token: ${error.message}`); } // Handle HTTP error responses if (!response.ok) { await this.handleSasError(response); } // Parse JSON response let data; try { data = await response.json(); } catch (error) { throw new types_1.NetworkError(`Failed to parse SAS token response: ${error.message}`); } // Validate and extract SAS token data return this.validateSasResponse(data); } /** * Handle HTTP error responses with detailed error messages */ async handleSasError(response) { let errorBody; try { errorBody = await response.text(); } catch { errorBody = 'Unable to read error response'; } switch (response.status) { case 401: throw new types_1.AuthenticationError('Invalid API key for SAS token request'); case 403: throw new types_1.AuthenticationError('API key does not have permission to request SAS tokens'); case 400: throw new types_1.NetworkError(`Bad SAS token request: ${errorBody}`); case 404: throw new types_1.NetworkError(`SAS token endpoint not found (${response.status}): ${errorBody}`); case 429: throw new types_1.NetworkError('Rate limit exceeded for SAS token requests'); case 500: case 502: case 503: case 504: throw new types_1.NetworkError(`Server error requesting SAS token (${response.status}) - please try again later`); default: throw new types_1.NetworkError(`SAS token request failed: HTTP ${response.status} - ${errorBody}`); } } /** * Validate and parse the SAS token response (updated for actual API format) */ validateSasResponse(data) { if (!data || typeof data !== 'object') { throw new types_1.NetworkError('Invalid SAS token response format - expected JSON object'); } const response = data; // Check for success wrapper format if (response.success !== true || response.data == null || typeof response.data !== 'object') { const message = typeof response.message === 'string' ? response.message : 'Unknown error'; throw new types_1.NetworkError(`SAS token request failed: ${message}`); } const sasData = response.data; // Validate required fields based on actual API response const requiredFields = ['tokenId', 'sasToken', 'containerUrl', 'blobPath', 'uniqueId', 'expiresAt', 'uploadInstructions']; const missingFields = requiredFields.filter(field => !(field in sasData)); if (missingFields.length > 0) { throw new types_1.NetworkError(`SAS token response missing required fields: ${missingFields.join(', ')}`); } // Validate field types if (typeof sasData.tokenId !== 'string') { throw new types_1.NetworkError('SAS token response: tokenId must be a string'); } if (typeof sasData.sasToken !== 'string') { throw new types_1.NetworkError('SAS token response: sasToken must be a string'); } if (typeof sasData.containerUrl !== 'string') { throw new types_1.NetworkError('SAS token response: containerUrl must be a string'); } if (typeof sasData.expiresAt !== 'string') { throw new types_1.NetworkError('SAS token response: expiresAt must be a string'); } // Validate container URL format try { new URL(sasData.containerUrl); } catch { throw new types_1.NetworkError(`Invalid containerUrl format: ${sasData.containerUrl}`); } // Validate upload instructions if (!sasData.uploadInstructions || typeof sasData.uploadInstructions !== 'object') { throw new types_1.NetworkError('SAS token response: uploadInstructions must be an object'); } const instructions = sasData.uploadInstructions; if (!instructions.baseUrl || !instructions.pathPrefix) { throw new types_1.NetworkError('SAS token response: uploadInstructions missing baseUrl or pathPrefix'); } // Prepare exampleUpload with proper type checks const exampleUploadObj = { method: 'PUT', url: '', headers: {}, }; if (instructions.exampleUpload && typeof instructions.exampleUpload === 'object') { const eu = instructions.exampleUpload; if (typeof eu.method === 'string') exampleUploadObj.method = eu.method; if (typeof eu.url === 'string') exampleUploadObj.url = eu.url; if (eu.headers && typeof eu.headers === 'object') { exampleUploadObj.headers = eu.headers; } } const result = { tokenId: sasData.tokenId, sasToken: sasData.sasToken, containerUrl: sasData.containerUrl, blobPath: sasData.blobPath, uniqueId: sasData.uniqueId, expiresAt: sasData.expiresAt, maxSize: typeof sasData.maxSize === 'number' ? sasData.maxSize : 2147483648, // 2GB default permissions: Array.isArray(sasData.permissions) ? sasData.permissions : [], uploadInstructions: { baseUrl: instructions.baseUrl, pathPrefix: instructions.pathPrefix, allowedFileTypes: Array.isArray(instructions.allowedFileTypes) ? instructions.allowedFileTypes : [], maxFileSize: typeof instructions.maxFileSize === 'number' ? instructions.maxFileSize : (typeof sasData.maxSize === 'number' ? sasData.maxSize : 2147483648), exampleUpload: exampleUploadObj } }; return result; } /** * Check if SAS token is expired (updated for actual API format) */ isTokenExpired(sasResponse) { try { const expirationDate = new Date(sasResponse.expiresAt); const now = new Date(); // Add small buffer (5 minutes) to account for clock skew const bufferMs = 5 * 60 * 1000; return (expirationDate.getTime() - bufferMs) <= now.getTime(); } catch { // If we can't parse the date, assume it's expired for safety return true; } } /** * Get time until SAS token expires (in minutes) */ getTimeUntilExpiry(sasResponse) { try { const expirationDate = new Date(sasResponse.expiresAt); const now = new Date(); const diffMs = expirationDate.getTime() - now.getTime(); return Math.floor(diffMs / (60 * 1000)); // Convert to minutes } catch { return 0; } } } exports.SasTokenService = SasTokenService; //# sourceMappingURL=sas.js.map