@bugspotter/sdk 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/LICENSE +21 -0
  3. package/README.md +639 -0
  4. package/dist/bugspotter.min.js +2 -0
  5. package/dist/bugspotter.min.js.LICENSE.txt +14 -0
  6. package/dist/capture/base-capture.d.ts +34 -0
  7. package/dist/capture/base-capture.js +23 -0
  8. package/dist/capture/capture-lifecycle.d.ts +24 -0
  9. package/dist/capture/capture-lifecycle.js +2 -0
  10. package/dist/capture/console.d.ts +29 -0
  11. package/dist/capture/console.js +107 -0
  12. package/dist/capture/metadata.d.ts +21 -0
  13. package/dist/capture/metadata.js +76 -0
  14. package/dist/capture/network.d.ts +32 -0
  15. package/dist/capture/network.js +135 -0
  16. package/dist/capture/screenshot.d.ts +19 -0
  17. package/dist/capture/screenshot.js +52 -0
  18. package/dist/collectors/dom.d.ts +67 -0
  19. package/dist/collectors/dom.js +164 -0
  20. package/dist/collectors/index.d.ts +2 -0
  21. package/dist/collectors/index.js +5 -0
  22. package/dist/core/buffer.d.ts +50 -0
  23. package/dist/core/buffer.js +88 -0
  24. package/dist/core/circular-buffer.d.ts +42 -0
  25. package/dist/core/circular-buffer.js +77 -0
  26. package/dist/core/compress.d.ts +49 -0
  27. package/dist/core/compress.js +245 -0
  28. package/dist/core/offline-queue.d.ts +76 -0
  29. package/dist/core/offline-queue.js +301 -0
  30. package/dist/core/transport.d.ts +73 -0
  31. package/dist/core/transport.js +352 -0
  32. package/dist/core/upload-helpers.d.ts +32 -0
  33. package/dist/core/upload-helpers.js +79 -0
  34. package/dist/core/uploader.d.ts +70 -0
  35. package/dist/core/uploader.js +185 -0
  36. package/dist/index.d.ts +140 -0
  37. package/dist/index.esm.js +205 -0
  38. package/dist/index.js +244 -0
  39. package/dist/utils/logger.d.ts +28 -0
  40. package/dist/utils/logger.js +84 -0
  41. package/dist/utils/sanitize-patterns.d.ts +103 -0
  42. package/dist/utils/sanitize-patterns.js +282 -0
  43. package/dist/utils/sanitize.d.ts +73 -0
  44. package/dist/utils/sanitize.js +254 -0
  45. package/dist/widget/button.d.ts +33 -0
  46. package/dist/widget/button.js +143 -0
  47. package/dist/widget/components/dom-element-cache.d.ts +62 -0
  48. package/dist/widget/components/dom-element-cache.js +105 -0
  49. package/dist/widget/components/form-validator.d.ts +66 -0
  50. package/dist/widget/components/form-validator.js +115 -0
  51. package/dist/widget/components/pii-detection-display.d.ts +64 -0
  52. package/dist/widget/components/pii-detection-display.js +142 -0
  53. package/dist/widget/components/redaction-canvas.d.ts +95 -0
  54. package/dist/widget/components/redaction-canvas.js +230 -0
  55. package/dist/widget/components/screenshot-processor.d.ts +44 -0
  56. package/dist/widget/components/screenshot-processor.js +191 -0
  57. package/dist/widget/components/style-manager.d.ts +37 -0
  58. package/dist/widget/components/style-manager.js +296 -0
  59. package/dist/widget/components/template-manager.d.ts +66 -0
  60. package/dist/widget/components/template-manager.js +198 -0
  61. package/dist/widget/modal.d.ts +62 -0
  62. package/dist/widget/modal.js +299 -0
  63. package/docs/CDN.md +213 -0
  64. package/docs/FRAMEWORK_INTEGRATION.md +1104 -0
  65. package/docs/PUBLISHING.md +550 -0
  66. package/docs/SESSION_REPLAY.md +381 -0
  67. package/package.json +90 -0
@@ -0,0 +1,352 @@
1
+ "use strict";
2
+ /**
3
+ * Transport layer for bug report submission with flexible authentication,
4
+ * exponential backoff retry, and offline queue support
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.clearOfflineQueue = exports.TokenRefreshError = exports.TransportError = void 0;
8
+ exports.getAuthHeaders = getAuthHeaders;
9
+ exports.submitWithAuth = submitWithAuth;
10
+ const logger_1 = require("../utils/logger");
11
+ const offline_queue_1 = require("./offline-queue");
12
+ // ============================================================================
13
+ // CONSTANTS
14
+ // ============================================================================
15
+ const TOKEN_REFRESH_STATUS = 401;
16
+ const JITTER_PERCENTAGE = 0.1;
17
+ const DEFAULT_ENABLE_RETRY = true;
18
+ // ============================================================================
19
+ // CUSTOM ERROR TYPES
20
+ // ============================================================================
21
+ class TransportError extends Error {
22
+ constructor(message, endpoint, cause) {
23
+ super(message);
24
+ this.endpoint = endpoint;
25
+ this.cause = cause;
26
+ this.name = 'TransportError';
27
+ }
28
+ }
29
+ exports.TransportError = TransportError;
30
+ class TokenRefreshError extends TransportError {
31
+ constructor(endpoint, cause) {
32
+ super('Failed to refresh authentication token', endpoint, cause);
33
+ this.name = 'TokenRefreshError';
34
+ }
35
+ }
36
+ exports.TokenRefreshError = TokenRefreshError;
37
+ // Default configurations
38
+ const DEFAULT_RETRY_CONFIG = {
39
+ maxRetries: 3,
40
+ baseDelay: 1000,
41
+ maxDelay: 30000,
42
+ retryOn: [502, 503, 504, 429],
43
+ };
44
+ const DEFAULT_OFFLINE_CONFIG = {
45
+ enabled: false,
46
+ maxQueueSize: 10,
47
+ };
48
+ const authStrategies = {
49
+ 'api-key': (config) => {
50
+ const apiKey = config.apiKey;
51
+ return apiKey ? { 'X-API-Key': apiKey } : {};
52
+ },
53
+ jwt: (config) => {
54
+ const token = config.token;
55
+ return token ? { Authorization: `Bearer ${token}` } : {};
56
+ },
57
+ bearer: (config) => {
58
+ const token = config.token;
59
+ return token ? { Authorization: `Bearer ${token}` } : {};
60
+ },
61
+ custom: (config) => {
62
+ const customHeader = config.customHeader;
63
+ if (!customHeader) {
64
+ return {};
65
+ }
66
+ const { name, value } = customHeader;
67
+ return name && value ? { [name]: value } : {};
68
+ },
69
+ none: () => {
70
+ return {};
71
+ },
72
+ };
73
+ // ============================================================================
74
+ // RETRY HANDLER - Exponential Backoff Logic
75
+ // ============================================================================
76
+ class RetryHandler {
77
+ constructor(config, logger) {
78
+ this.config = config;
79
+ this.logger = logger;
80
+ }
81
+ /**
82
+ * Execute operation with exponential backoff retry
83
+ */
84
+ async executeWithRetry(operation, shouldRetryStatus) {
85
+ let lastError = null;
86
+ for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
87
+ try {
88
+ const response = await operation();
89
+ // Check if we should retry based on status code
90
+ if (shouldRetryStatus(response.status) && attempt < this.config.maxRetries) {
91
+ const delay = this.calculateDelay(attempt, response);
92
+ this.logger.warn(`Request failed with status ${response.status}, retrying in ${delay}ms (attempt ${attempt + 1}/${this.config.maxRetries})`);
93
+ await sleep(delay);
94
+ continue;
95
+ }
96
+ // Success or non-retryable status
97
+ return response;
98
+ }
99
+ catch (error) {
100
+ lastError = error;
101
+ // Retry on network errors
102
+ if (attempt < this.config.maxRetries) {
103
+ const delay = this.calculateDelay(attempt);
104
+ this.logger.warn(`Network error, retrying in ${delay}ms (attempt ${attempt + 1}/${this.config.maxRetries}):`, error);
105
+ await sleep(delay);
106
+ continue;
107
+ }
108
+ }
109
+ }
110
+ // All retries exhausted
111
+ throw lastError || new Error('Request failed after all retry attempts');
112
+ }
113
+ /**
114
+ * Calculate retry delay with exponential backoff and jitter
115
+ */
116
+ calculateDelay(attempt, response) {
117
+ var _a, _b;
118
+ // Check for Retry-After header
119
+ if ((_b = (_a = response === null || response === void 0 ? void 0 : response.headers) === null || _a === void 0 ? void 0 : _a.has) === null || _b === void 0 ? void 0 : _b.call(_a, 'Retry-After')) {
120
+ const retryAfter = response.headers.get('Retry-After');
121
+ const retryAfterSeconds = parseInt(retryAfter, 10);
122
+ if (!isNaN(retryAfterSeconds)) {
123
+ return Math.min(retryAfterSeconds * 1000, this.config.maxDelay);
124
+ }
125
+ }
126
+ // Exponential backoff: baseDelay * 2^attempt
127
+ const exponentialDelay = this.config.baseDelay * Math.pow(2, attempt);
128
+ // Add jitter: ±10% randomization
129
+ const jitter = exponentialDelay * JITTER_PERCENTAGE * (Math.random() * 2 - 1);
130
+ const delayWithJitter = exponentialDelay + jitter;
131
+ // Cap at maxDelay
132
+ return Math.min(delayWithJitter, this.config.maxDelay);
133
+ }
134
+ }
135
+ /**
136
+ * Type guard to check if parameter is TransportOptions
137
+ *
138
+ * Strategy: Check for properties that ONLY exist in TransportOptions, not in AuthConfig.
139
+ * AuthConfig has: type, apiKey?, token?, onTokenExpired?, customHeader?
140
+ * TransportOptions has: auth?, logger?, enableRetry?, retry?, offline?
141
+ *
142
+ * Key distinction: AuthConfig always has 'type' property, TransportOptions never does.
143
+ */
144
+ function isTransportOptions(obj) {
145
+ if (typeof obj !== 'object' || obj === null) {
146
+ return false;
147
+ }
148
+ const record = obj;
149
+ // If it has 'type' property, it's an AuthConfig, not TransportOptions
150
+ if ('type' in record) {
151
+ return false;
152
+ }
153
+ // Key insight: If object has TransportOptions-specific keys (even if undefined),
154
+ // it's likely TransportOptions since AuthConfig never has these keys
155
+ const hasTransportOptionsKeys = 'auth' in record ||
156
+ 'retry' in record ||
157
+ 'offline' in record ||
158
+ 'logger' in record ||
159
+ 'enableRetry' in record;
160
+ // Return true if it has any TransportOptions-specific keys
161
+ return hasTransportOptionsKeys;
162
+ }
163
+ /**
164
+ * Parse transport parameters, supporting both legacy and new API signatures
165
+ */
166
+ function parseTransportParams(authOrOptions) {
167
+ var _a;
168
+ if (isTransportOptions(authOrOptions)) {
169
+ // Type guard ensures authOrOptions is TransportOptions
170
+ return {
171
+ auth: authOrOptions.auth,
172
+ logger: authOrOptions.logger || (0, logger_1.getLogger)(),
173
+ enableRetry: (_a = authOrOptions.enableRetry) !== null && _a !== void 0 ? _a : DEFAULT_ENABLE_RETRY,
174
+ retryConfig: Object.assign(Object.assign({}, DEFAULT_RETRY_CONFIG), authOrOptions.retry),
175
+ offlineConfig: Object.assign(Object.assign({}, DEFAULT_OFFLINE_CONFIG), authOrOptions.offline),
176
+ };
177
+ }
178
+ return {
179
+ auth: authOrOptions,
180
+ logger: (0, logger_1.getLogger)(),
181
+ enableRetry: DEFAULT_ENABLE_RETRY,
182
+ retryConfig: DEFAULT_RETRY_CONFIG,
183
+ offlineConfig: DEFAULT_OFFLINE_CONFIG,
184
+ };
185
+ }
186
+ // ============================================================================
187
+ // HELPER FUNCTIONS
188
+ // ============================================================================
189
+ /**
190
+ * Process offline queue in background
191
+ */
192
+ async function processQueueInBackground(offlineConfig, retryConfig, logger) {
193
+ if (!offlineConfig.enabled) {
194
+ return;
195
+ }
196
+ const queue = new offline_queue_1.OfflineQueue(offlineConfig, logger);
197
+ queue.process(retryConfig.retryOn).catch((error) => {
198
+ logger.warn('Failed to process offline queue:', error);
199
+ });
200
+ }
201
+ /**
202
+ * Handle offline failure by queueing request
203
+ */
204
+ async function handleOfflineFailure(error, endpoint, body, contentHeaders, auth, offlineConfig, logger) {
205
+ if (!offlineConfig.enabled || !isNetworkError(error)) {
206
+ return;
207
+ }
208
+ logger.warn('Network error detected, queueing request for offline retry');
209
+ const queue = new offline_queue_1.OfflineQueue(offlineConfig, logger);
210
+ const authHeaders = getAuthHeaders(auth);
211
+ await queue.enqueue(endpoint, body, Object.assign(Object.assign({}, contentHeaders), authHeaders));
212
+ }
213
+ // ============================================================================
214
+ // PUBLIC API
215
+ // ============================================================================
216
+ /**
217
+ * Get authentication headers based on configuration
218
+ * @param auth - Authentication configuration
219
+ * @returns HTTP headers for authentication
220
+ */
221
+ function getAuthHeaders(auth) {
222
+ // No auth
223
+ if (!auth) {
224
+ return {};
225
+ }
226
+ // Apply strategy
227
+ const strategy = authStrategies[auth.type];
228
+ return strategy ? strategy(auth) : {};
229
+ }
230
+ /**
231
+ * Submit request with authentication, exponential backoff retry, and offline queue support
232
+ *
233
+ * @param endpoint - API endpoint URL
234
+ * @param body - Request body (must be serializable for retry)
235
+ * @param contentHeaders - Content-related headers (Content-Type, etc.)
236
+ * @param authOrOptions - Auth config or TransportOptions
237
+ * @returns Response from the server
238
+ */
239
+ async function submitWithAuth(endpoint, body, contentHeaders, authOrOptions) {
240
+ // Parse options (support both old signature and new options-based API)
241
+ const { auth, logger, enableRetry, retryConfig, offlineConfig } = parseTransportParams(authOrOptions);
242
+ // Process offline queue on each request (run in background without awaiting)
243
+ processQueueInBackground(offlineConfig, retryConfig, logger);
244
+ try {
245
+ // Send with retry logic
246
+ const response = await sendWithRetry(endpoint, body, contentHeaders, auth, retryConfig, logger, enableRetry);
247
+ return response;
248
+ }
249
+ catch (error) {
250
+ // Queue for offline retry if enabled
251
+ await handleOfflineFailure(error, endpoint, body, contentHeaders, auth, offlineConfig, logger);
252
+ throw error;
253
+ }
254
+ }
255
+ /**
256
+ * Check if auth config supports token refresh
257
+ */
258
+ function shouldRetryWithRefresh(auth) {
259
+ return (typeof auth === 'object' &&
260
+ (auth.type === 'jwt' || auth.type === 'bearer') &&
261
+ typeof auth.onTokenExpired === 'function');
262
+ }
263
+ /**
264
+ * Make HTTP request with auth headers
265
+ */
266
+ async function makeRequest(endpoint, body, contentHeaders, auth) {
267
+ const authHeaders = getAuthHeaders(auth);
268
+ const headers = Object.assign(Object.assign({}, contentHeaders), authHeaders);
269
+ return fetch(endpoint, {
270
+ method: 'POST',
271
+ headers,
272
+ body,
273
+ });
274
+ }
275
+ /**
276
+ * Send request with exponential backoff retry
277
+ */
278
+ async function sendWithRetry(endpoint, body, contentHeaders, auth, retryConfig, logger, enableTokenRetry) {
279
+ const retryHandler = new RetryHandler(retryConfig, logger);
280
+ let hasAttemptedRefresh = false;
281
+ // Use retry handler with token refresh support
282
+ return retryHandler.executeWithRetry(async () => {
283
+ const response = await makeRequest(endpoint, body, contentHeaders, auth);
284
+ // Check for 401 and retry with token refresh if applicable (only once)
285
+ if (response.status === TOKEN_REFRESH_STATUS &&
286
+ enableTokenRetry &&
287
+ !hasAttemptedRefresh &&
288
+ shouldRetryWithRefresh(auth)) {
289
+ hasAttemptedRefresh = true;
290
+ const refreshedResponse = await retryWithTokenRefresh(endpoint, body, contentHeaders, auth, logger);
291
+ return refreshedResponse;
292
+ }
293
+ return response;
294
+ }, (status) => {
295
+ return retryConfig.retryOn.includes(status);
296
+ });
297
+ }
298
+ /**
299
+ * Sleep for specified milliseconds
300
+ */
301
+ function sleep(ms) {
302
+ return new Promise((resolve) => setTimeout(resolve, ms));
303
+ }
304
+ /**
305
+ * Check if error is a network error (more specific to avoid false positives)
306
+ */
307
+ function isNetworkError(error) {
308
+ if (!(error instanceof Error)) {
309
+ return false;
310
+ }
311
+ const message = error.message.toLowerCase();
312
+ // Check for specific network error patterns
313
+ return (
314
+ // Standard fetch network errors
315
+ message.includes('failed to fetch') ||
316
+ message.includes('network request failed') ||
317
+ message.includes('networkerror') ||
318
+ // Connection issues
319
+ message.includes('network error') ||
320
+ message.includes('connection') ||
321
+ // Timeout errors
322
+ message.includes('timeout') ||
323
+ // Standard error names
324
+ error.name === 'NetworkError' ||
325
+ error.name === 'AbortError' ||
326
+ // TypeError only if it mentions fetch or network
327
+ (error.name === 'TypeError' && (message.includes('fetch') || message.includes('network'))));
328
+ }
329
+ /**
330
+ * Retry request with refreshed token
331
+ */
332
+ async function retryWithTokenRefresh(endpoint, body, contentHeaders, auth, logger) {
333
+ try {
334
+ logger.warn('Token expired, attempting refresh...');
335
+ // Get new token
336
+ const newToken = await auth.onTokenExpired();
337
+ // Create updated auth config
338
+ const refreshedAuth = Object.assign(Object.assign({}, auth), { token: newToken });
339
+ // Retry request
340
+ const response = await makeRequest(endpoint, body, contentHeaders, refreshedAuth);
341
+ logger.log('Request retried with refreshed token');
342
+ return response;
343
+ }
344
+ catch (error) {
345
+ logger.error('Token refresh failed:', error);
346
+ // Return original 401 - caller should handle
347
+ return new Response(null, { status: TOKEN_REFRESH_STATUS, statusText: 'Unauthorized' });
348
+ }
349
+ }
350
+ // Re-export offline queue utilities
351
+ var offline_queue_2 = require("./offline-queue");
352
+ Object.defineProperty(exports, "clearOfflineQueue", { enumerable: true, get: function () { return offline_queue_2.clearOfflineQueue; } });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Upload Helpers
3
+ * Utilities for preparing data for presigned URL uploads
4
+ */
5
+ /**
6
+ * Compress replay events using CompressionStream API (browser native)
7
+ * Falls back to no compression if API not available
8
+ * @param events - Session replay events array
9
+ * @returns Compressed Blob (gzip)
10
+ */
11
+ export declare function compressReplayEvents(events: unknown[]): Promise<Blob>;
12
+ /**
13
+ * Convert screenshot canvas to Blob
14
+ * @param canvas - HTML Canvas element with screenshot
15
+ * @param quality - JPEG quality (0-1), default 0.9
16
+ * @returns Screenshot Blob
17
+ */
18
+ export declare function canvasToBlob(canvas: HTMLCanvasElement, quality?: number): Promise<Blob>;
19
+ /**
20
+ * Estimate compressed size of replay events
21
+ * Uses rough heuristic: gzip typically achieves 80-90% compression for JSON
22
+ * @param events - Replay events array
23
+ * @returns Estimated compressed size in bytes
24
+ */
25
+ export declare function estimateCompressedReplaySize(events: unknown[]): number;
26
+ /**
27
+ * Check if file size is within upload limits
28
+ * @param blob - File or Blob to check
29
+ * @param maxSizeMB - Maximum size in megabytes
30
+ * @returns True if within limit
31
+ */
32
+ export declare function isWithinSizeLimit(blob: Blob, maxSizeMB: number): boolean;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ /**
3
+ * Upload Helpers
4
+ * Utilities for preparing data for presigned URL uploads
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.compressReplayEvents = compressReplayEvents;
8
+ exports.canvasToBlob = canvasToBlob;
9
+ exports.estimateCompressedReplaySize = estimateCompressedReplaySize;
10
+ exports.isWithinSizeLimit = isWithinSizeLimit;
11
+ /**
12
+ * Compress replay events using CompressionStream API (browser native)
13
+ * Falls back to no compression if API not available
14
+ * @param events - Session replay events array
15
+ * @returns Compressed Blob (gzip)
16
+ */
17
+ async function compressReplayEvents(events) {
18
+ // Convert events to JSON string
19
+ const jsonString = JSON.stringify(events);
20
+ const textEncoder = new TextEncoder();
21
+ const data = textEncoder.encode(jsonString);
22
+ // Check if CompressionStream is supported (Chrome 80+, Firefox 113+, Safari 16.4+)
23
+ if (typeof CompressionStream === 'undefined') {
24
+ console.warn('CompressionStream not supported, uploading uncompressed replay data');
25
+ return new Blob([data], { type: 'application/json' });
26
+ }
27
+ try {
28
+ // Use modern streaming API: Blob → ReadableStream → CompressionStream → Response → Blob
29
+ const blob = new Blob([data]);
30
+ const compressedStream = blob.stream().pipeThrough(new CompressionStream('gzip'));
31
+ return await new Response(compressedStream, {
32
+ headers: { 'Content-Type': 'application/gzip' },
33
+ }).blob();
34
+ }
35
+ catch (error) {
36
+ console.error('Compression failed, uploading uncompressed:', error);
37
+ return new Blob([data], { type: 'application/json' });
38
+ }
39
+ }
40
+ /**
41
+ * Convert screenshot canvas to Blob
42
+ * @param canvas - HTML Canvas element with screenshot
43
+ * @param quality - JPEG quality (0-1), default 0.9
44
+ * @returns Screenshot Blob
45
+ */
46
+ async function canvasToBlob(canvas, quality = 0.9) {
47
+ return new Promise((resolve, reject) => {
48
+ canvas.toBlob((blob) => {
49
+ if (blob) {
50
+ resolve(blob);
51
+ }
52
+ else {
53
+ reject(new Error('Failed to convert canvas to Blob'));
54
+ }
55
+ }, 'image/png', quality);
56
+ });
57
+ }
58
+ /**
59
+ * Estimate compressed size of replay events
60
+ * Uses rough heuristic: gzip typically achieves 80-90% compression for JSON
61
+ * @param events - Replay events array
62
+ * @returns Estimated compressed size in bytes
63
+ */
64
+ function estimateCompressedReplaySize(events) {
65
+ const jsonString = JSON.stringify(events);
66
+ const uncompressedSize = new TextEncoder().encode(jsonString).length;
67
+ // Assume 85% compression ratio (conservative estimate)
68
+ return Math.round(uncompressedSize * 0.15);
69
+ }
70
+ /**
71
+ * Check if file size is within upload limits
72
+ * @param blob - File or Blob to check
73
+ * @param maxSizeMB - Maximum size in megabytes
74
+ * @returns True if within limit
75
+ */
76
+ function isWithinSizeLimit(blob, maxSizeMB) {
77
+ const maxBytes = maxSizeMB * 1024 * 1024;
78
+ return blob.size <= maxBytes;
79
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * DirectUploader
3
+ * Handles direct client-to-storage uploads using presigned URLs
4
+ */
5
+ export interface DirectUploadConfig {
6
+ apiEndpoint: string;
7
+ apiKey: string;
8
+ projectId: string;
9
+ bugId: string;
10
+ }
11
+ export interface UploadProgress {
12
+ loaded: number;
13
+ total: number;
14
+ percentage: number;
15
+ }
16
+ export type UploadProgressCallback = (progress: UploadProgress) => void;
17
+ export interface UploadResult {
18
+ success: boolean;
19
+ storageKey?: string;
20
+ error?: string;
21
+ }
22
+ /**
23
+ * DirectUploader handles uploading files directly to storage using presigned URLs
24
+ * This bypasses the API server for file data, reducing memory usage and improving performance
25
+ */
26
+ export declare class DirectUploader {
27
+ private readonly config;
28
+ constructor(config: DirectUploadConfig);
29
+ /**
30
+ * Upload a screenshot file directly to storage
31
+ * @param file - Screenshot file or Blob
32
+ * @param onProgress - Optional progress callback
33
+ * @returns Upload result with storage key
34
+ */
35
+ uploadScreenshot(file: File | Blob, onProgress?: UploadProgressCallback): Promise<UploadResult>;
36
+ /**
37
+ * Upload a compressed session replay directly to storage
38
+ * @param compressedData - Gzip-compressed replay data
39
+ * @param onProgress - Optional progress callback
40
+ * @returns Upload result with storage key
41
+ */
42
+ uploadReplay(compressedData: Blob, onProgress?: UploadProgressCallback): Promise<UploadResult>;
43
+ /**
44
+ * Upload an attachment file directly to storage
45
+ * @param file - Attachment file
46
+ * @param onProgress - Optional progress callback
47
+ * @returns Upload result with storage key
48
+ */
49
+ uploadAttachment(file: File, onProgress?: UploadProgressCallback): Promise<UploadResult>;
50
+ /**
51
+ * Generic file upload method
52
+ * 1. Request presigned URL from API
53
+ * 2. Upload file directly to storage using presigned URL
54
+ * 3. Confirm upload with API
55
+ */
56
+ private uploadFile;
57
+ /**
58
+ * Request a presigned URL from the API
59
+ */
60
+ private requestPresignedUrl;
61
+ /**
62
+ * Upload file to storage using presigned URL
63
+ * Uses XMLHttpRequest for progress tracking
64
+ */
65
+ private uploadToStorage;
66
+ /**
67
+ * Confirm successful upload with the API
68
+ */
69
+ private confirmUpload;
70
+ }