@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
JavaScript
;
/**
* 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;