UNPKG

@bugspotter/sdk

Version:

Professional bug reporting SDK with screenshots, session replay, and automatic error capture for web applications

186 lines (185 loc) 6.5 kB
"use strict"; /** * DirectUploader * Handles direct client-to-storage uploads using presigned URLs */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DirectUploader = void 0; /** * DirectUploader handles uploading files directly to storage using presigned URLs * This bypasses the API server for file data, reducing memory usage and improving performance */ class DirectUploader { constructor(config) { this.config = config; } /** * Upload a screenshot file directly to storage * @param file - Screenshot file or Blob * @param onProgress - Optional progress callback * @returns Upload result with storage key */ async uploadScreenshot(file, onProgress) { return this.uploadFile(file, 'screenshot', 'screenshot.png', onProgress); } /** * Upload a compressed session replay directly to storage * @param compressedData - Gzip-compressed replay data * @param onProgress - Optional progress callback * @returns Upload result with storage key */ async uploadReplay(compressedData, onProgress) { return this.uploadFile(compressedData, 'replay', 'replay.gz', onProgress); } /** * Upload an attachment file directly to storage * @param file - Attachment file * @param onProgress - Optional progress callback * @returns Upload result with storage key */ async uploadAttachment(file, onProgress) { return this.uploadFile(file, 'attachment', file.name, onProgress); } /** * Generic file upload method * 1. Request presigned URL from API * 2. Upload file directly to storage using presigned URL * 3. Confirm upload with API */ async uploadFile(file, fileType, filename, onProgress) { try { // Step 1: Get presigned upload URL const presignedUrlResponse = await this.requestPresignedUrl(fileType, filename); if (!presignedUrlResponse.success) { return { success: false, error: presignedUrlResponse.error || 'Failed to get presigned URL', }; } const { uploadUrl, storageKey } = presignedUrlResponse.data; // Step 2: Upload file to storage using presigned URL const uploadSuccess = await this.uploadToStorage(uploadUrl, file, onProgress); if (!uploadSuccess) { return { success: false, error: 'Failed to upload file to storage', }; } // Step 3: Confirm upload with API const confirmSuccess = await this.confirmUpload(fileType); if (!confirmSuccess) { return { success: false, error: 'Failed to confirm upload', }; } return { success: true, storageKey, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error', }; } } /** * Request a presigned URL from the API */ async requestPresignedUrl(fileType, filename) { try { const response = await fetch(`${this.config.apiEndpoint}/api/v1/uploads/presigned-url`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.config.apiKey, }, body: JSON.stringify({ projectId: this.config.projectId, bugId: this.config.bugId, fileType, filename, }), }); if (!response.ok) { const errorText = await response.text(); return { success: false, error: `HTTP ${response.status}: ${errorText}`, }; } const result = await response.json(); return { success: result.success, data: result.data, error: result.error, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Network error', }; } } /** * Upload file to storage using presigned URL * Uses XMLHttpRequest for progress tracking */ uploadToStorage(uploadUrl, file, onProgress) { return new Promise((resolve) => { const xhr = new XMLHttpRequest(); // Track upload progress if (onProgress) { xhr.upload.addEventListener('progress', (event) => { if (event.lengthComputable) { onProgress({ loaded: event.loaded, total: event.total, percentage: Math.round((event.loaded / event.total) * 100), }); } }); } // Handle completion xhr.addEventListener('load', () => { resolve(xhr.status >= 200 && xhr.status < 300); }); // Handle errors xhr.addEventListener('error', () => { resolve(false); }); xhr.addEventListener('abort', () => { resolve(false); }); // Send file xhr.open('PUT', uploadUrl); xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream'); xhr.send(file); }); } /** * Confirm successful upload with the API */ async confirmUpload(fileType) { try { const response = await fetch(`${this.config.apiEndpoint}/api/v1/reports/${this.config.bugId}/confirm-upload`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.config.apiKey, }, body: JSON.stringify({ fileType, }), }); return response.ok; } catch (_a) { return false; } } } exports.DirectUploader = DirectUploader;