@uploadista/client-browser 0.0.3

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 (83) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-check.log +130 -0
  3. package/AUTO_CAPABILITIES.md +98 -0
  4. package/FRAMEWORK_INTEGRATION.md +407 -0
  5. package/LICENSE +21 -0
  6. package/README.md +795 -0
  7. package/SMART_CHUNKING.md +140 -0
  8. package/dist/client/create-uploadista-client.d.ts +182 -0
  9. package/dist/client/create-uploadista-client.d.ts.map +1 -0
  10. package/dist/client/create-uploadista-client.js +76 -0
  11. package/dist/client/index.d.ts +2 -0
  12. package/dist/client/index.d.ts.map +1 -0
  13. package/dist/client/index.js +1 -0
  14. package/dist/framework-utils.d.ts +201 -0
  15. package/dist/framework-utils.d.ts.map +1 -0
  16. package/dist/framework-utils.js +282 -0
  17. package/dist/http-client.d.ts +44 -0
  18. package/dist/http-client.d.ts.map +1 -0
  19. package/dist/http-client.js +489 -0
  20. package/dist/index.d.ts +8 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +7 -0
  23. package/dist/services/abort-controller-factory.d.ts +30 -0
  24. package/dist/services/abort-controller-factory.d.ts.map +1 -0
  25. package/dist/services/abort-controller-factory.js +98 -0
  26. package/dist/services/checksum-service.d.ts +30 -0
  27. package/dist/services/checksum-service.d.ts.map +1 -0
  28. package/dist/services/checksum-service.js +44 -0
  29. package/dist/services/create-browser-services.d.ts +36 -0
  30. package/dist/services/create-browser-services.d.ts.map +1 -0
  31. package/dist/services/create-browser-services.js +56 -0
  32. package/dist/services/file-reader.d.ts +91 -0
  33. package/dist/services/file-reader.d.ts.map +1 -0
  34. package/dist/services/file-reader.js +251 -0
  35. package/dist/services/fingerprint-service.d.ts +41 -0
  36. package/dist/services/fingerprint-service.d.ts.map +1 -0
  37. package/dist/services/fingerprint-service.js +64 -0
  38. package/dist/services/id-generation/id-generation.d.ts +40 -0
  39. package/dist/services/id-generation/id-generation.d.ts.map +1 -0
  40. package/dist/services/id-generation/id-generation.js +58 -0
  41. package/dist/services/platform-service.d.ts +38 -0
  42. package/dist/services/platform-service.d.ts.map +1 -0
  43. package/dist/services/platform-service.js +221 -0
  44. package/dist/services/storage/local-storage-service.d.ts +55 -0
  45. package/dist/services/storage/local-storage-service.d.ts.map +1 -0
  46. package/dist/services/storage/local-storage-service.js +178 -0
  47. package/dist/services/storage/session-storage-service.d.ts +55 -0
  48. package/dist/services/storage/session-storage-service.d.ts.map +1 -0
  49. package/dist/services/storage/session-storage-service.js +179 -0
  50. package/dist/services/websocket-factory.d.ts +46 -0
  51. package/dist/services/websocket-factory.d.ts.map +1 -0
  52. package/dist/services/websocket-factory.js +196 -0
  53. package/dist/types/index.d.ts +2 -0
  54. package/dist/types/index.d.ts.map +1 -0
  55. package/dist/types/index.js +1 -0
  56. package/dist/types/upload-input.d.ts +26 -0
  57. package/dist/types/upload-input.d.ts.map +1 -0
  58. package/dist/types/upload-input.js +1 -0
  59. package/dist/utils/hash-util.d.ts +60 -0
  60. package/dist/utils/hash-util.d.ts.map +1 -0
  61. package/dist/utils/hash-util.js +75 -0
  62. package/package.json +32 -0
  63. package/src/client/create-uploadista-client.ts +150 -0
  64. package/src/client/index.ts +1 -0
  65. package/src/framework-utils.ts +446 -0
  66. package/src/http-client.ts +546 -0
  67. package/src/index.ts +8 -0
  68. package/src/services/abort-controller-factory.ts +108 -0
  69. package/src/services/checksum-service.ts +46 -0
  70. package/src/services/create-browser-services.ts +81 -0
  71. package/src/services/file-reader.ts +344 -0
  72. package/src/services/fingerprint-service.ts +67 -0
  73. package/src/services/id-generation/id-generation.ts +60 -0
  74. package/src/services/platform-service.ts +231 -0
  75. package/src/services/storage/local-storage-service.ts +187 -0
  76. package/src/services/storage/session-storage-service.ts +188 -0
  77. package/src/services/websocket-factory.ts +212 -0
  78. package/src/types/index.ts +1 -0
  79. package/src/types/upload-input.ts +25 -0
  80. package/src/utils/hash-util.ts +79 -0
  81. package/tsconfig.json +22 -0
  82. package/tsconfig.tsbuildinfo +1 -0
  83. package/vitest.config.ts +15 -0
@@ -0,0 +1,546 @@
1
+ import type {
2
+ ConnectionHealth,
3
+ ConnectionMetrics,
4
+ ConnectionPoolConfig,
5
+ DetailedConnectionMetrics,
6
+ Http2Info,
7
+ HttpClient,
8
+ HttpRequestOptions,
9
+ HttpResponse,
10
+ } from "@uploadista/client-core";
11
+
12
+ /**
13
+ * Creates a browser-optimized HTTP client using the Fetch API.
14
+ *
15
+ * This factory function returns an HttpClient implementation that uses the browser's
16
+ * native fetch() API with connection keep-alive headers for optimal performance.
17
+ * The client automatically manages connection pooling, tracks metrics, and provides
18
+ * connection health monitoring.
19
+ *
20
+ * @param config - Optional connection pooling configuration
21
+ * @returns A configured HTTP client ready for making requests
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { createHttpClient } from '@uploadista/client-browser';
26
+ *
27
+ * // Basic usage with defaults
28
+ * const client = createHttpClient();
29
+ *
30
+ * // With custom configuration
31
+ * const client = createHttpClient({
32
+ * maxConnectionsPerHost: 10,
33
+ * connectionTimeout: 60000,
34
+ * keepAliveTimeout: 120000,
35
+ * enableHttp2: true,
36
+ * retryOnConnectionError: true
37
+ * });
38
+ *
39
+ * // Make a request
40
+ * const response = await client.request('https://api.example.com/data', {
41
+ * method: 'POST',
42
+ * headers: { 'Content-Type': 'application/json' },
43
+ * body: JSON.stringify({ key: 'value' })
44
+ * });
45
+ *
46
+ * // Check connection health
47
+ * const metrics = client.getDetailedMetrics();
48
+ * console.log('Connection health:', metrics.health.status);
49
+ * ```
50
+ *
51
+ * @see {@link BrowserHttpClient} for implementation details
52
+ */
53
+ export function createHttpClient(config?: ConnectionPoolConfig): HttpClient {
54
+ return new BrowserHttpClient(config);
55
+ }
56
+
57
+ /**
58
+ * Browser-optimized HTTP client implementation using the Fetch API with connection keep-alive.
59
+ *
60
+ * This class implements the HttpClient interface and provides:
61
+ * - Connection pooling via keep-alive headers
62
+ * - Connection metrics tracking (reuse rate, latency, error rates)
63
+ * - HTTP/2 multiplexing detection and support
64
+ * - Connection health monitoring with actionable recommendations
65
+ * - Connection warmup capabilities
66
+ * - Automatic timeout handling
67
+ *
68
+ * The browser manages actual connection pooling, but this client optimizes for
69
+ * reuse through proper HTTP headers and provides visibility into connection performance.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const client = new BrowserHttpClient({
74
+ * maxConnectionsPerHost: 6,
75
+ * keepAliveTimeout: 60000,
76
+ * enableHttp2: true
77
+ * });
78
+ *
79
+ * // Monitor connection health
80
+ * setInterval(() => {
81
+ * const health = client.getDetailedMetrics().health;
82
+ * if (health.status === 'poor') {
83
+ * console.warn('Connection issues:', health.issues);
84
+ * console.log('Recommendations:', health.recommendations);
85
+ * }
86
+ * }, 30000);
87
+ * ```
88
+ */
89
+ class BrowserHttpClient implements HttpClient {
90
+ private config: Required<ConnectionPoolConfig>;
91
+ private metrics: ConnectionMetrics;
92
+ private connectionTimes: number[] = [];
93
+ private requestCount = 0;
94
+ private connectionCount = 0;
95
+ private errorCount = 0;
96
+ private timeoutCount = 0;
97
+ private retryCount = 0;
98
+ private startTime = Date.now();
99
+ private http2Info: Http2Info;
100
+
101
+ /**
102
+ * Creates a new browser HTTP client instance.
103
+ *
104
+ * @param config - Connection pooling configuration with optional overrides
105
+ */
106
+ constructor(config: ConnectionPoolConfig = {}) {
107
+ this.config = {
108
+ maxConnectionsPerHost: config.maxConnectionsPerHost ?? 6,
109
+ connectionTimeout: config.connectionTimeout ?? 30000,
110
+ keepAliveTimeout: config.keepAliveTimeout ?? 60000,
111
+ enableHttp2: config.enableHttp2 ?? true,
112
+ retryOnConnectionError: config.retryOnConnectionError ?? true,
113
+ };
114
+
115
+ this.metrics = {
116
+ activeConnections: 0,
117
+ totalConnections: 0,
118
+ reuseRate: 0,
119
+ averageConnectionTime: 0,
120
+ };
121
+
122
+ // Initialize HTTP/2 detection
123
+ this.http2Info = this.detectHttp2Support();
124
+ }
125
+
126
+ /**
127
+ * Detects HTTP/2 support in the current browser environment.
128
+ *
129
+ * This method uses feature detection to determine if the browser supports
130
+ * HTTP/2 features like multiplexing. In browsers, we can't directly query
131
+ * the protocol version, so we infer support based on modern stream APIs.
132
+ *
133
+ * @returns HTTP/2 information with support status and detected features
134
+ * @private
135
+ */
136
+ private detectHttp2Support(): Http2Info {
137
+ // Check if the browser supports HTTP/2
138
+ const supported = "serviceWorker" in navigator && "fetch" in window;
139
+
140
+ // In browsers, we can't directly detect HTTP/2 protocol version
141
+ // but we can make educated guesses based on browser features
142
+ const hasModernFeatures =
143
+ "ReadableStream" in window &&
144
+ "WritableStream" in window &&
145
+ "TransformStream" in window;
146
+
147
+ return {
148
+ supported,
149
+ detected: false, // Will be updated during actual requests
150
+ version: hasModernFeatures ? "h2" : "h1.1",
151
+ multiplexingActive: hasModernFeatures && this.config.enableHttp2,
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Makes an HTTP request using the Fetch API with optimized connection settings.
157
+ *
158
+ * This method automatically adds keep-alive headers for connection reuse,
159
+ * handles timeouts via AbortController, and tracks connection metrics.
160
+ *
161
+ * @param url - The URL to request
162
+ * @param options - Request options including method, headers, body, credentials, signal, and timeout
163
+ * @returns Promise resolving to the HTTP response
164
+ *
165
+ * @throws {Error} When the request fails or times out
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * // Simple GET request
170
+ * const response = await client.request('https://api.example.com/data');
171
+ * const data = await response.json();
172
+ *
173
+ * // POST with timeout
174
+ * const response = await client.request('https://api.example.com/upload', {
175
+ * method: 'POST',
176
+ * headers: { 'Content-Type': 'application/json' },
177
+ * body: JSON.stringify({ file: 'data' }),
178
+ * timeout: 10000, // 10 seconds
179
+ * signal: abortController.signal
180
+ * });
181
+ * ```
182
+ */
183
+ async request(
184
+ url: string,
185
+ options: HttpRequestOptions = {},
186
+ ): Promise<HttpResponse> {
187
+ this.requestCount++;
188
+
189
+ // Create optimized fetch options for connection reuse
190
+ const fetchOptions: RequestInit = {
191
+ method: options.method || "GET",
192
+ headers: {
193
+ // Add connection keep-alive headers for better reuse
194
+ Connection: "keep-alive",
195
+ "Keep-Alive": `timeout=${this.config.keepAliveTimeout / 1000}`,
196
+ ...options.headers,
197
+ },
198
+ body: options.body as BodyInit | null | undefined,
199
+ credentials: options.credentials || "include",
200
+ signal: options.signal as AbortSignal | undefined,
201
+ };
202
+
203
+ // Add timeout if specified
204
+ if (options.timeout) {
205
+ const controller = new AbortController();
206
+ const timeoutId = setTimeout(() => controller.abort(), options.timeout);
207
+
208
+ if (options.signal) {
209
+ options.signal.addEventListener("abort", () => controller.abort());
210
+ }
211
+
212
+ fetchOptions.signal = controller.signal;
213
+
214
+ try {
215
+ const response = await this.makeRequest(url, fetchOptions);
216
+ clearTimeout(timeoutId);
217
+ return response;
218
+ } catch (error) {
219
+ clearTimeout(timeoutId);
220
+ throw error;
221
+ }
222
+ }
223
+
224
+ return this.makeRequest(url, fetchOptions);
225
+ }
226
+
227
+ /**
228
+ * Internal method to execute the fetch request and track metrics.
229
+ *
230
+ * @param url - The URL to request
231
+ * @param options - Native fetch RequestInit options
232
+ * @returns Promise resolving to the HTTP response
233
+ * @throws {Error} When the fetch fails
234
+ * @private
235
+ */
236
+ private async makeRequest(
237
+ url: string,
238
+ options: RequestInit,
239
+ ): Promise<HttpResponse> {
240
+ const startTime = Date.now();
241
+
242
+ try {
243
+ const response = await fetch(url, options);
244
+ const connectionTime = Date.now() - startTime;
245
+
246
+ this.recordConnectionMetrics(connectionTime);
247
+
248
+ return {
249
+ status: response.status,
250
+ statusText: response.statusText,
251
+ headers: response.headers,
252
+ ok: response.ok,
253
+ json: () => response.json(),
254
+ text: () => response.text(),
255
+ arrayBuffer: () => response.arrayBuffer(),
256
+ };
257
+ } catch (error) {
258
+ // Record failed connection attempt
259
+ this.connectionCount++;
260
+ throw error;
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Records connection timing metrics and updates statistics.
266
+ *
267
+ * Tracks connection times, calculates reuse rates based on latency patterns,
268
+ * and maintains a rolling window of the last 100 measurements.
269
+ *
270
+ * @param connectionTime - Time in milliseconds for this connection
271
+ * @private
272
+ */
273
+ private recordConnectionMetrics(connectionTime: number): void {
274
+ this.connectionTimes.push(connectionTime);
275
+ this.connectionCount++;
276
+
277
+ // Keep only last 100 measurements for average calculation
278
+ if (this.connectionTimes.length > 100) {
279
+ this.connectionTimes.shift();
280
+ }
281
+
282
+ // Update metrics
283
+ this.metrics.totalConnections = this.connectionCount;
284
+ this.metrics.averageConnectionTime =
285
+ this.connectionTimes.reduce((sum, time) => sum + time, 0) /
286
+ this.connectionTimes.length;
287
+
288
+ // Estimate reuse rate based on connection time patterns
289
+ // Faster connections are likely reused connections
290
+ const fastConnections = this.connectionTimes.filter(
291
+ (time) => time < 100,
292
+ ).length;
293
+ this.metrics.reuseRate = fastConnections / this.connectionTimes.length;
294
+ }
295
+
296
+ /**
297
+ * Retrieves basic connection metrics.
298
+ *
299
+ * @returns Current connection metrics including total connections, reuse rate, and average connection time
300
+ *
301
+ * @example
302
+ * ```typescript
303
+ * const metrics = client.getMetrics();
304
+ * console.log(`Reuse rate: ${Math.round(metrics.reuseRate * 100)}%`);
305
+ * console.log(`Avg connection time: ${Math.round(metrics.averageConnectionTime)}ms`);
306
+ * ```
307
+ */
308
+ getMetrics(): ConnectionMetrics {
309
+ return { ...this.metrics };
310
+ }
311
+
312
+ /**
313
+ * Retrieves detailed connection metrics with health assessment.
314
+ *
315
+ * Provides comprehensive metrics including error rates, request throughput,
316
+ * connection health status with score, identified issues, and actionable
317
+ * recommendations for improving connection performance.
318
+ *
319
+ * @returns Detailed metrics with health information, request rates, and HTTP/2 info
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * const detailed = client.getDetailedMetrics();
324
+ *
325
+ * // Check health
326
+ * if (detailed.health.status === 'degraded') {
327
+ * console.warn('Issues:', detailed.health.issues);
328
+ * console.log('Try:', detailed.health.recommendations);
329
+ * }
330
+ *
331
+ * // Monitor performance
332
+ * console.log(`Requests/sec: ${detailed.requestsPerSecond.toFixed(2)}`);
333
+ * console.log(`Error rate: ${(detailed.errorRate * 100).toFixed(1)}%`);
334
+ * console.log(`Fast connections: ${detailed.fastConnections}/${detailed.fastConnections + detailed.slowConnections}`);
335
+ * ```
336
+ */
337
+ getDetailedMetrics(): DetailedConnectionMetrics {
338
+ const health = this.calculateConnectionHealth();
339
+ const elapsed = (Date.now() - this.startTime) / 1000; // seconds
340
+ const requestsPerSecond = elapsed > 0 ? this.requestCount / elapsed : 0;
341
+ const errorRate =
342
+ this.requestCount > 0 ? this.errorCount / this.requestCount : 0;
343
+
344
+ const fastConnections = this.connectionTimes.filter(
345
+ (time) => time < 100,
346
+ ).length;
347
+ const slowConnections = this.connectionTimes.length - fastConnections;
348
+
349
+ return {
350
+ ...this.metrics,
351
+ health,
352
+ requestsPerSecond,
353
+ errorRate,
354
+ timeouts: this.timeoutCount,
355
+ retries: this.retryCount,
356
+ fastConnections,
357
+ slowConnections,
358
+ http2Info: this.http2Info,
359
+ };
360
+ }
361
+
362
+ /**
363
+ * Calculates connection health status based on current metrics.
364
+ *
365
+ * Analyzes reuse rates, error rates, and connection latency to determine
366
+ * overall health (healthy/degraded/poor), assigns a health score (0-100),
367
+ * and provides specific issues and recommendations.
368
+ *
369
+ * @returns Health assessment with status, score, issues, and recommendations
370
+ * @private
371
+ */
372
+ private calculateConnectionHealth(): ConnectionHealth {
373
+ const issues: string[] = [];
374
+ const recommendations: string[] = [];
375
+ let score = 100;
376
+
377
+ // Check reuse rate
378
+ if (this.metrics.reuseRate < 0.3) {
379
+ issues.push("Low connection reuse rate");
380
+ recommendations.push("Check if keep-alive headers are working");
381
+ score -= 30;
382
+ } else if (this.metrics.reuseRate < 0.7) {
383
+ issues.push("Moderate connection reuse rate");
384
+ recommendations.push("Consider adjusting keep-alive timeout");
385
+ score -= 15;
386
+ }
387
+
388
+ // Check error rate
389
+ const errorRate =
390
+ this.requestCount > 0 ? this.errorCount / this.requestCount : 0;
391
+ if (errorRate > 0.1) {
392
+ issues.push("High error rate");
393
+ recommendations.push("Check network stability and server configuration");
394
+ score -= 25;
395
+ } else if (errorRate > 0.05) {
396
+ issues.push("Moderate error rate");
397
+ recommendations.push("Monitor network conditions");
398
+ score -= 10;
399
+ }
400
+
401
+ // Check average connection time
402
+ if (this.metrics.averageConnectionTime > 1000) {
403
+ issues.push("Slow connection establishment");
404
+ recommendations.push("Check network latency and DNS resolution");
405
+ score -= 20;
406
+ } else if (this.metrics.averageConnectionTime > 500) {
407
+ issues.push("Moderate connection latency");
408
+ recommendations.push("Consider connection warming");
409
+ score -= 10;
410
+ }
411
+
412
+ // Determine status
413
+ let status: "healthy" | "degraded" | "poor";
414
+ if (score >= 80) {
415
+ status = "healthy";
416
+ } else if (score >= 60) {
417
+ status = "degraded";
418
+ } else {
419
+ status = "poor";
420
+ }
421
+
422
+ return {
423
+ status,
424
+ score: Math.max(0, score),
425
+ issues,
426
+ recommendations,
427
+ };
428
+ }
429
+
430
+ /**
431
+ * Warms up HTTP connections to specified URLs by making lightweight HEAD requests.
432
+ *
433
+ * This is useful for establishing connections before actual data transfer,
434
+ * reducing latency for subsequent requests. Particularly beneficial when
435
+ * uploading to multiple endpoints or when connection setup time is critical.
436
+ *
437
+ * @param urls - Array of URLs to warm up connections to
438
+ *
439
+ * @example
440
+ * ```typescript
441
+ * // Warm up connections before uploading
442
+ * await client.warmupConnections([
443
+ * 'https://upload1.example.com',
444
+ * 'https://upload2.example.com',
445
+ * 'https://cdn.example.com'
446
+ * ]);
447
+ *
448
+ * // Now actual uploads will use pre-warmed connections
449
+ * await client.request('https://upload1.example.com/file', {
450
+ * method: 'PUT',
451
+ * body: fileData
452
+ * });
453
+ * ```
454
+ */
455
+ async warmupConnections(urls: string[]): Promise<void> {
456
+ if (urls.length === 0) return;
457
+
458
+ console.log(`Warming up connections to ${urls.length} hosts...`);
459
+
460
+ // Make lightweight HEAD requests to warm up connections
461
+ const warmupPromises = urls.map(async (url) => {
462
+ try {
463
+ await this.request(url, {
464
+ method: "HEAD",
465
+ timeout: 5000, // 5 second timeout for warmup
466
+ });
467
+ } catch (error) {
468
+ // Ignore warmup failures - they're optional
469
+ console.warn(`Connection warmup failed for ${url}:`, error);
470
+ }
471
+ });
472
+
473
+ // Wait for all warmup requests (with timeout)
474
+ await Promise.allSettled(warmupPromises);
475
+ console.log("Connection warmup completed");
476
+ }
477
+
478
+ /**
479
+ * Resets all connection metrics and statistics to initial state.
480
+ *
481
+ * Useful for clearing metrics after a long-running session or when
482
+ * you want to start fresh measurement without creating a new client instance.
483
+ *
484
+ * @example
485
+ * ```typescript
486
+ * // Reset metrics after a batch of uploads
487
+ * client.reset();
488
+ *
489
+ * // Start fresh measurements
490
+ * const metrics = client.getMetrics(); // All counters back to zero
491
+ * ```
492
+ */
493
+ reset(): void {
494
+ this.connectionTimes = [];
495
+ this.requestCount = 0;
496
+ this.connectionCount = 0;
497
+ this.errorCount = 0;
498
+ this.timeoutCount = 0;
499
+ this.retryCount = 0;
500
+ this.startTime = Date.now();
501
+ this.metrics = {
502
+ activeConnections: 0,
503
+ totalConnections: 0,
504
+ reuseRate: 0,
505
+ averageConnectionTime: 0,
506
+ };
507
+ this.http2Info = this.detectHttp2Support();
508
+ }
509
+
510
+ /**
511
+ * Gracefully shuts down the HTTP client and logs final statistics.
512
+ *
513
+ * In browser environments, connections are managed by the browser and cannot
514
+ * be explicitly closed. This method waits briefly for pending requests to complete,
515
+ * logs final connection metrics, and resets internal state.
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * // Clean shutdown with metrics logging
520
+ * await client.close();
521
+ * // Logs: "Total requests: 150, Connection reuse: 85%, Avg time: 45ms, Health: healthy"
522
+ * ```
523
+ */
524
+ async close(): Promise<void> {
525
+ console.log("Gracefully shutting down HTTP client...");
526
+
527
+ // In browser environment, we can't explicitly close connections
528
+ // The browser manages connection pooling automatically
529
+ // But we can clean up our internal state
530
+
531
+ // Wait a short time for any pending requests to complete
532
+ await new Promise((resolve) => setTimeout(resolve, 100));
533
+
534
+ // Log final statistics
535
+ const finalMetrics = this.getDetailedMetrics();
536
+ console.log("Final connection metrics:", {
537
+ totalRequests: this.requestCount,
538
+ connectionReuse: `${Math.round(finalMetrics.reuseRate * 100)}%`,
539
+ averageConnectionTime: `${Math.round(finalMetrics.averageConnectionTime)}ms`,
540
+ health: finalMetrics.health.status,
541
+ });
542
+
543
+ this.reset();
544
+ console.log("HTTP client shutdown complete");
545
+ }
546
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ // Re-export everything from client-core
2
+ export * from "@uploadista/client-core";
3
+ export { createUploadistaClient, type UploadistaClientOptions } from "./client";
4
+ export * from "./framework-utils";
5
+ export { createHttpClient } from "./http-client";
6
+ export type { FileReader } from "./services/file-reader";
7
+ export { createBrowserFileReaderService } from "./services/file-reader";
8
+ export * from "./types";
@@ -0,0 +1,108 @@
1
+ import type {
2
+ AbortControllerFactory,
3
+ AbortControllerLike,
4
+ AbortSignalLike,
5
+ } from "@uploadista/client-core";
6
+
7
+ /**
8
+ * Browser implementation of AbortController that wraps the native AbortController API.
9
+ *
10
+ * This class provides a minimal wrapper around the browser's native AbortController
11
+ * to ensure compatibility with the Uploadista client's AbortControllerLike interface.
12
+ * It's used for canceling uploads and HTTP requests.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const controller = new BrowserAbortController();
17
+ *
18
+ * // Start an operation with the signal
19
+ * fetch('https://api.example.com/upload', {
20
+ * signal: controller.signal,
21
+ * method: 'POST',
22
+ * body: formData
23
+ * });
24
+ *
25
+ * // Cancel the operation
26
+ * controller.abort('User canceled');
27
+ * ```
28
+ */
29
+ class BrowserAbortController implements AbortControllerLike {
30
+ private native: AbortController;
31
+
32
+ /**
33
+ * Creates a new BrowserAbortController instance.
34
+ *
35
+ * Initializes a new native AbortController from the browser's API.
36
+ */
37
+ constructor() {
38
+ this.native = new AbortController();
39
+ }
40
+
41
+ /**
42
+ * Gets the AbortSignal associated with this controller.
43
+ *
44
+ * This signal is passed to abortable operations (like fetch or upload)
45
+ * and will be triggered when abort() is called.
46
+ *
47
+ * @returns The abort signal that operations can listen to
48
+ */
49
+ get signal(): AbortSignalLike {
50
+ return this.native.signal;
51
+ }
52
+
53
+ /**
54
+ * Aborts the operation associated with this controller.
55
+ *
56
+ * When called, this will trigger the abort signal, causing any operations
57
+ * listening to it (such as fetch requests or file uploads) to be canceled.
58
+ *
59
+ * @param reason - Optional reason for the abort, which will be available on the AbortSignal
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const controller = new BrowserAbortController();
64
+ *
65
+ * // Start upload
66
+ * const upload = client.upload(file, { signal: controller.signal });
67
+ *
68
+ * // Cancel upload after 5 seconds
69
+ * setTimeout(() => {
70
+ * controller.abort('Timeout exceeded');
71
+ * }, 5000);
72
+ * ```
73
+ */
74
+ abort(reason?: unknown): void {
75
+ this.native.abort(reason);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Creates a factory for browser AbortController instances.
81
+ *
82
+ * This factory is used by the Uploadista client to create AbortController
83
+ * instances for canceling uploads and HTTP requests. It wraps the browser's
84
+ * native AbortController API.
85
+ *
86
+ * @returns An AbortControllerFactory that creates browser-compatible abort controllers
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * import { createBrowserAbortControllerFactory } from '@uploadista/client-browser';
91
+ *
92
+ * const factory = createBrowserAbortControllerFactory();
93
+ *
94
+ * // Create a controller
95
+ * const controller = factory.create();
96
+ *
97
+ * // Use it to cancel operations
98
+ * const upload = client.upload(file, {
99
+ * signal: controller.signal
100
+ * });
101
+ *
102
+ * // Later, cancel the upload
103
+ * controller.abort();
104
+ * ```
105
+ */
106
+ export const createBrowserAbortControllerFactory = (): AbortControllerFactory => ({
107
+ create: (): AbortControllerLike => new BrowserAbortController(),
108
+ });
@@ -0,0 +1,46 @@
1
+ import type { ChecksumService } from "@uploadista/client-core";
2
+ import { computeblobSha256 } from "../utils/hash-util";
3
+
4
+ /**
5
+ * Creates a checksum service for verifying data integrity using SHA-256 hashing.
6
+ *
7
+ * This service uses the browser's Web Crypto API to compute SHA-256 checksums
8
+ * of data chunks during upload. Checksums ensure that uploaded data hasn't been
9
+ * corrupted in transit and matches what was sent.
10
+ *
11
+ * The service is optimized for browser environments and leverages native crypto
12
+ * APIs for performance.
13
+ *
14
+ * @returns A ChecksumService that computes SHA-256 hashes for data chunks
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { createChecksumService } from '@uploadista/client-browser';
19
+ *
20
+ * const checksumService = createChecksumService();
21
+ *
22
+ * // Compute checksum for a data chunk
23
+ * const data = new Uint8Array([1, 2, 3, 4, 5]);
24
+ * const checksum = await checksumService.computeChecksum(data);
25
+ * console.log('SHA-256 checksum:', checksum);
26
+ * // Output: "74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0"
27
+ * ```
28
+ *
29
+ * @see {@link computeblobSha256} for the underlying hash implementation
30
+ */
31
+ export function createChecksumService(): ChecksumService {
32
+ return {
33
+ /**
34
+ * Computes a SHA-256 checksum for the provided data.
35
+ *
36
+ * Converts the Uint8Array to a Blob and uses the Web Crypto API
37
+ * to calculate its SHA-256 hash, returned as a hex string.
38
+ *
39
+ * @param data - The data to checksum
40
+ * @returns Promise resolving to the hex-encoded SHA-256 checksum
41
+ */
42
+ computeChecksum: async (data: Uint8Array<ArrayBuffer>) => {
43
+ return computeblobSha256(new Blob([data]));
44
+ },
45
+ };
46
+ }