@uploadista/client-browser 1.0.0-beta.3 → 1.0.0

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.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,4 @@
1
- import * as _uploadista_client_core0 from "@uploadista/client-core";
2
1
  import { ConnectionPoolConfig, FileReaderService, FlowResult, HttpClient, SliceResult, UploadResult, UploadistaClientOptions as UploadistaClientOptions$1 } from "@uploadista/client-core";
3
- import * as _uploadista_core0 from "@uploadista/core";
4
2
  import { FlowEvent } from "@uploadista/core/flow";
5
3
  import { UploadEvent, UploadFile } from "@uploadista/core/types";
6
4
  export * from "@uploadista/client-core";
@@ -149,10 +147,10 @@ declare function createUploadistaClient(options: UploadistaClientOptions): {
149
147
  onSuccess,
150
148
  onShouldRetry,
151
149
  onError
152
- }?: _uploadista_client_core0.UploadistaUploadOptions) => Promise<{
150
+ }?: import("@uploadista/client-core").UploadistaUploadOptions) => Promise<{
153
151
  abort: () => void;
154
152
  }>;
155
- uploadWithFlow: (file: BrowserUploadInput, flowConfig: _uploadista_client_core0.FlowUploadConfig, {
153
+ uploadWithFlow: (file: BrowserUploadInput, flowConfig: import("@uploadista/client-core").FlowUploadConfig, {
156
154
  onProgress,
157
155
  onChunkComplete,
158
156
  onSuccess,
@@ -160,7 +158,7 @@ declare function createUploadistaClient(options: UploadistaClientOptions): {
160
158
  onJobStart,
161
159
  onError,
162
160
  onAbort
163
- }?: Omit<_uploadista_client_core0.UploadistaUploadOptions, "uploadLengthDeferred" | "uploadSize" | "metadata"> & {
161
+ }?: Omit<import("@uploadista/client-core").UploadistaUploadOptions, "uploadLengthDeferred" | "uploadSize" | "metadata"> & {
164
162
  onAbort?: () => void;
165
163
  }) => Promise<{
166
164
  abort: () => Promise<void>;
@@ -168,7 +166,7 @@ declare function createUploadistaClient(options: UploadistaClientOptions): {
168
166
  resume: () => Promise<void>;
169
167
  jobId: string;
170
168
  }>;
171
- multiInputFlowUpload: (inputs: Record<string, unknown>, flowConfig: _uploadista_client_core0.FlowUploadConfig, {
169
+ multiInputFlowUpload: (inputs: Record<string, unknown>, flowConfig: import("@uploadista/client-core").FlowUploadConfig, {
172
170
  onProgress,
173
171
  onChunkComplete,
174
172
  onShouldRetry,
@@ -177,7 +175,7 @@ declare function createUploadistaClient(options: UploadistaClientOptions): {
177
175
  onInputProgress,
178
176
  onInputComplete,
179
177
  onInputError
180
- }?: Omit<_uploadista_client_core0.UploadistaUploadOptions, "uploadLengthDeferred" | "uploadSize" | "metadata"> & {
178
+ }?: Omit<import("@uploadista/client-core").UploadistaUploadOptions, "uploadLengthDeferred" | "uploadSize" | "metadata"> & {
181
179
  onInputProgress?: (nodeId: string, progress: number, bytesUploaded: number, totalBytes: number | null) => void;
182
180
  onInputComplete?: (nodeId: string) => void;
183
181
  onInputError?: (nodeId: string, error: Error) => void;
@@ -200,17 +198,17 @@ declare function createUploadistaClient(options: UploadistaClientOptions): {
200
198
  }: {
201
199
  uploadId: string;
202
200
  uploadIdStorageKey: string | undefined;
203
- retryTimeout: _uploadista_client_core0.Timeout | null;
201
+ retryTimeout: import("@uploadista/client-core").Timeout | null;
204
202
  shouldTerminate: boolean;
205
- abortController: _uploadista_client_core0.AbortControllerLike;
206
- uploadistaApi: _uploadista_client_core0.UploadistaApi;
207
- platformService: _uploadista_client_core0.PlatformService;
203
+ abortController: import("@uploadista/client-core").AbortControllerLike;
204
+ uploadistaApi: import("@uploadista/client-core").UploadistaApi;
205
+ platformService: import("@uploadista/client-core").PlatformService;
208
206
  retryDelays?: number[];
209
- clientStorage: _uploadista_client_core0.ClientStorage;
207
+ clientStorage: import("@uploadista/client-core").ClientStorage;
210
208
  }) => Promise<void>>[0]) => Promise<void>;
211
209
  getFlow: (flowId: string) => Promise<{
212
210
  status: number;
213
- flow: _uploadista_core0.FlowData;
211
+ flow: import("@uploadista/core").FlowData;
214
212
  }>;
215
213
  runFlow: ({
216
214
  flowId,
@@ -222,7 +220,7 @@ declare function createUploadistaClient(options: UploadistaClientOptions): {
222
220
  storageId?: string;
223
221
  }) => Promise<{
224
222
  status: number;
225
- job: _uploadista_core0.FlowJob;
223
+ job: import("@uploadista/core").FlowJob;
226
224
  }>;
227
225
  resumeFlow: ({
228
226
  jobId,
@@ -234,13 +232,13 @@ declare function createUploadistaClient(options: UploadistaClientOptions): {
234
232
  nodeId: string;
235
233
  newData: unknown;
236
234
  contentType?: "application/json" | "application/octet-stream";
237
- }) => Promise<_uploadista_core0.FlowJob>;
238
- pauseFlow: (jobId: string) => Promise<_uploadista_core0.FlowJob>;
239
- cancelFlow: (jobId: string) => Promise<_uploadista_core0.FlowJob>;
235
+ }) => Promise<import("@uploadista/core").FlowJob>;
236
+ pauseFlow: (jobId: string) => Promise<import("@uploadista/core").FlowJob>;
237
+ cancelFlow: (jobId: string) => Promise<import("@uploadista/core").FlowJob>;
240
238
  findInputNode: (flowId: string) => Promise<{
241
239
  inputNodes: {
242
240
  id: string;
243
- type: _uploadista_core0.NodeType;
241
+ type: import("@uploadista/core").NodeType;
244
242
  name: string;
245
243
  }[];
246
244
  single: boolean;
@@ -250,12 +248,12 @@ declare function createUploadistaClient(options: UploadistaClientOptions): {
250
248
  onJobStart?: (jobId: string) => void;
251
249
  }) => Promise<{
252
250
  status: number;
253
- job: _uploadista_core0.FlowJob;
251
+ job: import("@uploadista/core").FlowJob;
254
252
  }>;
255
- getJobStatus: (jobId: string) => Promise<_uploadista_core0.FlowJob>;
256
- openUploadWebSocket: (uploadId: string) => Promise<_uploadista_client_core0.WebSocketLike>;
257
- openFlowWebSocket: (jobId: string) => Promise<_uploadista_client_core0.WebSocketLike>;
258
- openWebSocket: (id: string) => Promise<_uploadista_client_core0.WebSocketLike>;
253
+ getJobStatus: (jobId: string) => Promise<import("@uploadista/core").FlowJob>;
254
+ openUploadWebSocket: (uploadId: string) => Promise<import("@uploadista/client-core").WebSocketLike>;
255
+ openFlowWebSocket: (jobId: string) => Promise<import("@uploadista/client-core").WebSocketLike>;
256
+ openWebSocket: (id: string) => Promise<import("@uploadista/client-core").WebSocketLike>;
259
257
  closeWebSocket: (id: string) => void;
260
258
  closeAllWebSockets: () => void;
261
259
  sendPing: (jobId: string) => boolean;
@@ -266,16 +264,16 @@ declare function createUploadistaClient(options: UploadistaClientOptions): {
266
264
  flow: number;
267
265
  total: number;
268
266
  };
269
- getNetworkMetrics: () => _uploadista_client_core0.NetworkMetrics;
270
- getNetworkCondition: () => _uploadista_client_core0.NetworkCondition;
271
- getChunkingInsights: () => _uploadista_client_core0.PerformanceInsights;
267
+ getNetworkMetrics: () => import("@uploadista/client-core").NetworkMetrics;
268
+ getNetworkCondition: () => import("@uploadista/client-core").NetworkCondition;
269
+ getChunkingInsights: () => import("@uploadista/client-core").PerformanceInsights;
272
270
  exportMetrics: () => {
273
- session: Partial<_uploadista_client_core0.UploadSessionMetrics>;
274
- chunks: _uploadista_client_core0.ChunkMetrics[];
275
- insights: _uploadista_client_core0.PerformanceInsights;
271
+ session: Partial<import("@uploadista/client-core").UploadSessionMetrics>;
272
+ chunks: import("@uploadista/client-core").ChunkMetrics[];
273
+ insights: import("@uploadista/client-core").PerformanceInsights;
276
274
  };
277
- getConnectionMetrics: () => _uploadista_client_core0.ConnectionMetrics;
278
- getDetailedConnectionMetrics: () => _uploadista_client_core0.DetailedConnectionMetrics;
275
+ getConnectionMetrics: () => import("@uploadista/client-core").ConnectionMetrics;
276
+ getDetailedConnectionMetrics: () => import("@uploadista/client-core").DetailedConnectionMetrics;
279
277
  warmupConnections: (urls: string[]) => Promise<void>;
280
278
  getConnectionPoolingInsights: () => Promise<{
281
279
  isOptimized: boolean;
@@ -293,9 +291,9 @@ declare function createUploadistaClient(options: UploadistaClientOptions): {
293
291
  valid: boolean;
294
292
  errors: string[];
295
293
  warnings: string[];
296
- capabilities: _uploadista_core0.DataStoreCapabilities;
294
+ capabilities: import("@uploadista/core").DataStoreCapabilities;
297
295
  }>;
298
- getCapabilities: () => Promise<_uploadista_core0.DataStoreCapabilities>;
296
+ getCapabilities: () => Promise<import("@uploadista/core").DataStoreCapabilities>;
299
297
  };
300
298
  //#endregion
301
299
  //#region src/framework-utils.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/upload-input.ts","../src/client/create-uploadista-client.ts","../src/framework-utils.ts","../src/http-client.ts","../src/services/file-reader.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/upload-input.ts","../src/client/create-uploadista-client.ts","../src/framework-utils.ts","../src/http-client.ts","../src/services/file-reader.ts"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["createUploadistaClientCore"],"sources":["../src/http-client.ts","../src/services/abort-controller-factory.ts","../src/utils/hash-util.ts","../src/services/checksum-service.ts","../src/services/file-reader.ts","../src/services/fingerprint-service.ts","../src/services/id-generation/id-generation.ts","../src/services/platform-service.ts","../src/services/storage/local-storage-service.ts","../src/services/websocket-factory.ts","../src/services/create-browser-services.ts","../src/client/create-uploadista-client.ts","../src/framework-utils.ts"],"sourcesContent":["import type {\n ConnectionHealth,\n ConnectionMetrics,\n ConnectionPoolConfig,\n DetailedConnectionMetrics,\n Http2Info,\n HttpClient,\n HttpRequestOptions,\n HttpResponse,\n} from \"@uploadista/client-core\";\n\n/**\n * Creates a browser-optimized HTTP client using the Fetch API.\n *\n * This factory function returns an HttpClient implementation that uses the browser's\n * native fetch() API with connection keep-alive headers for optimal performance.\n * The client automatically manages connection pooling, tracks metrics, and provides\n * connection health monitoring.\n *\n * @param config - Optional connection pooling configuration\n * @returns A configured HTTP client ready for making requests\n *\n * @example\n * ```typescript\n * import { createHttpClient } from '@uploadista/client-browser';\n *\n * // Basic usage with defaults\n * const client = createHttpClient();\n *\n * // With custom configuration\n * const client = createHttpClient({\n * maxConnectionsPerHost: 10,\n * connectionTimeout: 60000,\n * keepAliveTimeout: 120000,\n * enableHttp2: true,\n * retryOnConnectionError: true\n * });\n *\n * // Make a request\n * const response = await client.request('https://api.example.com/data', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ key: 'value' })\n * });\n *\n * // Check connection health\n * const metrics = client.getDetailedMetrics();\n * console.log('Connection health:', metrics.health.status);\n * ```\n *\n * @see {@link BrowserHttpClient} for implementation details\n */\nexport function createHttpClient(config?: ConnectionPoolConfig): HttpClient {\n return new BrowserHttpClient(config);\n}\n\n/**\n * Browser-optimized HTTP client implementation using the Fetch API with connection keep-alive.\n *\n * This class implements the HttpClient interface and provides:\n * - Connection pooling via keep-alive headers\n * - Connection metrics tracking (reuse rate, latency, error rates)\n * - HTTP/2 multiplexing detection and support\n * - Connection health monitoring with actionable recommendations\n * - Connection warmup capabilities\n * - Automatic timeout handling\n *\n * The browser manages actual connection pooling, but this client optimizes for\n * reuse through proper HTTP headers and provides visibility into connection performance.\n *\n * @example\n * ```typescript\n * const client = new BrowserHttpClient({\n * maxConnectionsPerHost: 6,\n * keepAliveTimeout: 60000,\n * enableHttp2: true\n * });\n *\n * // Monitor connection health\n * setInterval(() => {\n * const health = client.getDetailedMetrics().health;\n * if (health.status === 'poor') {\n * console.warn('Connection issues:', health.issues);\n * console.log('Recommendations:', health.recommendations);\n * }\n * }, 30000);\n * ```\n */\nclass BrowserHttpClient implements HttpClient {\n private config: Required<ConnectionPoolConfig>;\n private metrics: ConnectionMetrics;\n private connectionTimes: number[] = [];\n private requestCount = 0;\n private connectionCount = 0;\n private errorCount = 0;\n private timeoutCount = 0;\n private retryCount = 0;\n private startTime = Date.now();\n private http2Info: Http2Info;\n\n /**\n * Creates a new browser HTTP client instance.\n *\n * @param config - Connection pooling configuration with optional overrides\n */\n constructor(config: ConnectionPoolConfig = {}) {\n this.config = {\n maxConnectionsPerHost: config.maxConnectionsPerHost ?? 6,\n connectionTimeout: config.connectionTimeout ?? 30000,\n keepAliveTimeout: config.keepAliveTimeout ?? 60000,\n enableHttp2: config.enableHttp2 ?? true,\n retryOnConnectionError: config.retryOnConnectionError ?? true,\n };\n\n this.metrics = {\n activeConnections: 0,\n totalConnections: 0,\n reuseRate: 0,\n averageConnectionTime: 0,\n };\n\n // Initialize HTTP/2 detection\n this.http2Info = this.detectHttp2Support();\n }\n\n /**\n * Detects HTTP/2 support in the current browser environment.\n *\n * This method uses feature detection to determine if the browser supports\n * HTTP/2 features like multiplexing. In browsers, we can't directly query\n * the protocol version, so we infer support based on modern stream APIs.\n *\n * @returns HTTP/2 information with support status and detected features\n * @private\n */\n private detectHttp2Support(): Http2Info {\n // Check if we're in a browser environment\n if (typeof window === \"undefined\" || typeof navigator === \"undefined\") {\n // SSR/Node.js environment - return safe defaults\n return {\n supported: false,\n detected: false,\n version: \"h1.1\",\n multiplexingActive: false,\n };\n }\n\n // Check if the browser supports HTTP/2\n const supported = \"serviceWorker\" in navigator && \"fetch\" in window;\n\n // In browsers, we can't directly detect HTTP/2 protocol version\n // but we can make educated guesses based on browser features\n const hasModernFeatures =\n \"ReadableStream\" in window &&\n \"WritableStream\" in window &&\n \"TransformStream\" in window;\n\n return {\n supported,\n detected: false, // Will be updated during actual requests\n version: hasModernFeatures ? \"h2\" : \"h1.1\",\n multiplexingActive: hasModernFeatures && this.config.enableHttp2,\n };\n }\n\n /**\n * Makes an HTTP request using the Fetch API with optimized connection settings.\n *\n * This method automatically adds keep-alive headers for connection reuse,\n * handles timeouts via AbortController, and tracks connection metrics.\n *\n * @param url - The URL to request\n * @param options - Request options including method, headers, body, credentials, signal, and timeout\n * @returns Promise resolving to the HTTP response\n *\n * @throws {Error} When the request fails or times out\n *\n * @example\n * ```typescript\n * // Simple GET request\n * const response = await client.request('https://api.example.com/data');\n * const data = await response.json();\n *\n * // POST with timeout\n * const response = await client.request('https://api.example.com/upload', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ file: 'data' }),\n * timeout: 10000, // 10 seconds\n * signal: abortController.signal\n * });\n * ```\n */\n async request(\n url: string,\n options: HttpRequestOptions = {},\n ): Promise<HttpResponse> {\n this.requestCount++;\n\n // Create optimized fetch options for connection reuse\n const fetchOptions: RequestInit = {\n method: options.method || \"GET\",\n headers: {\n // Add connection keep-alive headers for better reuse\n Connection: \"keep-alive\",\n \"Keep-Alive\": `timeout=${this.config.keepAliveTimeout / 1000}`,\n ...options.headers,\n },\n body: options.body as BodyInit | null | undefined,\n credentials: options.credentials || \"include\",\n signal: options.signal as AbortSignal | undefined,\n };\n\n // Add timeout if specified\n if (options.timeout) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n if (options.signal) {\n options.signal.addEventListener(\"abort\", () => controller.abort());\n }\n\n fetchOptions.signal = controller.signal;\n\n try {\n const response = await this.makeRequest(url, fetchOptions);\n clearTimeout(timeoutId);\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n throw error;\n }\n }\n\n return this.makeRequest(url, fetchOptions);\n }\n\n /**\n * Internal method to execute the fetch request and track metrics.\n *\n * @param url - The URL to request\n * @param options - Native fetch RequestInit options\n * @returns Promise resolving to the HTTP response\n * @throws {Error} When the fetch fails\n * @private\n */\n private async makeRequest(\n url: string,\n options: RequestInit,\n ): Promise<HttpResponse> {\n const startTime = Date.now();\n\n try {\n const response = await fetch(url, options);\n const connectionTime = Date.now() - startTime;\n\n this.recordConnectionMetrics(connectionTime);\n\n return {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n ok: response.ok,\n json: () => response.json(),\n text: () => response.text(),\n arrayBuffer: () => response.arrayBuffer(),\n };\n } catch (error) {\n // Record failed connection attempt\n this.connectionCount++;\n throw error;\n }\n }\n\n /**\n * Records connection timing metrics and updates statistics.\n *\n * Tracks connection times, calculates reuse rates based on latency patterns,\n * and maintains a rolling window of the last 100 measurements.\n *\n * @param connectionTime - Time in milliseconds for this connection\n * @private\n */\n private recordConnectionMetrics(connectionTime: number): void {\n this.connectionTimes.push(connectionTime);\n this.connectionCount++;\n\n // Keep only last 100 measurements for average calculation\n if (this.connectionTimes.length > 100) {\n this.connectionTimes.shift();\n }\n\n // Update metrics\n this.metrics.totalConnections = this.connectionCount;\n this.metrics.averageConnectionTime =\n this.connectionTimes.reduce((sum, time) => sum + time, 0) /\n this.connectionTimes.length;\n\n // Estimate reuse rate based on connection time patterns\n // Faster connections are likely reused connections\n const fastConnections = this.connectionTimes.filter(\n (time) => time < 100,\n ).length;\n this.metrics.reuseRate = fastConnections / this.connectionTimes.length;\n }\n\n /**\n * Retrieves basic connection metrics.\n *\n * @returns Current connection metrics including total connections, reuse rate, and average connection time\n *\n * @example\n * ```typescript\n * const metrics = client.getMetrics();\n * console.log(`Reuse rate: ${Math.round(metrics.reuseRate * 100)}%`);\n * console.log(`Avg connection time: ${Math.round(metrics.averageConnectionTime)}ms`);\n * ```\n */\n getMetrics(): ConnectionMetrics {\n return { ...this.metrics };\n }\n\n /**\n * Retrieves detailed connection metrics with health assessment.\n *\n * Provides comprehensive metrics including error rates, request throughput,\n * connection health status with score, identified issues, and actionable\n * recommendations for improving connection performance.\n *\n * @returns Detailed metrics with health information, request rates, and HTTP/2 info\n *\n * @example\n * ```typescript\n * const detailed = client.getDetailedMetrics();\n *\n * // Check health\n * if (detailed.health.status === 'degraded') {\n * console.warn('Issues:', detailed.health.issues);\n * console.log('Try:', detailed.health.recommendations);\n * }\n *\n * // Monitor performance\n * console.log(`Requests/sec: ${detailed.requestsPerSecond.toFixed(2)}`);\n * console.log(`Error rate: ${(detailed.errorRate * 100).toFixed(1)}%`);\n * console.log(`Fast connections: ${detailed.fastConnections}/${detailed.fastConnections + detailed.slowConnections}`);\n * ```\n */\n getDetailedMetrics(): DetailedConnectionMetrics {\n const health = this.calculateConnectionHealth();\n const elapsed = (Date.now() - this.startTime) / 1000; // seconds\n const requestsPerSecond = elapsed > 0 ? this.requestCount / elapsed : 0;\n const errorRate =\n this.requestCount > 0 ? this.errorCount / this.requestCount : 0;\n\n const fastConnections = this.connectionTimes.filter(\n (time) => time < 100,\n ).length;\n const slowConnections = this.connectionTimes.length - fastConnections;\n\n return {\n ...this.metrics,\n health,\n requestsPerSecond,\n errorRate,\n timeouts: this.timeoutCount,\n retries: this.retryCount,\n fastConnections,\n slowConnections,\n http2Info: this.http2Info,\n };\n }\n\n /**\n * Calculates connection health status based on current metrics.\n *\n * Analyzes reuse rates, error rates, and connection latency to determine\n * overall health (healthy/degraded/poor), assigns a health score (0-100),\n * and provides specific issues and recommendations.\n *\n * @returns Health assessment with status, score, issues, and recommendations\n * @private\n */\n private calculateConnectionHealth(): ConnectionHealth {\n const issues: string[] = [];\n const recommendations: string[] = [];\n let score = 100;\n\n // Check reuse rate\n if (this.metrics.reuseRate < 0.3) {\n issues.push(\"Low connection reuse rate\");\n recommendations.push(\"Check if keep-alive headers are working\");\n score -= 30;\n } else if (this.metrics.reuseRate < 0.7) {\n issues.push(\"Moderate connection reuse rate\");\n recommendations.push(\"Consider adjusting keep-alive timeout\");\n score -= 15;\n }\n\n // Check error rate\n const errorRate =\n this.requestCount > 0 ? this.errorCount / this.requestCount : 0;\n if (errorRate > 0.1) {\n issues.push(\"High error rate\");\n recommendations.push(\"Check network stability and server configuration\");\n score -= 25;\n } else if (errorRate > 0.05) {\n issues.push(\"Moderate error rate\");\n recommendations.push(\"Monitor network conditions\");\n score -= 10;\n }\n\n // Check average connection time\n if (this.metrics.averageConnectionTime > 1000) {\n issues.push(\"Slow connection establishment\");\n recommendations.push(\"Check network latency and DNS resolution\");\n score -= 20;\n } else if (this.metrics.averageConnectionTime > 500) {\n issues.push(\"Moderate connection latency\");\n recommendations.push(\"Consider connection warming\");\n score -= 10;\n }\n\n // Determine status\n let status: \"healthy\" | \"degraded\" | \"poor\";\n if (score >= 80) {\n status = \"healthy\";\n } else if (score >= 60) {\n status = \"degraded\";\n } else {\n status = \"poor\";\n }\n\n return {\n status,\n score: Math.max(0, score),\n issues,\n recommendations,\n };\n }\n\n /**\n * Warms up HTTP connections to specified URLs by making lightweight HEAD requests.\n *\n * This is useful for establishing connections before actual data transfer,\n * reducing latency for subsequent requests. Particularly beneficial when\n * uploading to multiple endpoints or when connection setup time is critical.\n *\n * @param urls - Array of URLs to warm up connections to\n *\n * @example\n * ```typescript\n * // Warm up connections before uploading\n * await client.warmupConnections([\n * 'https://upload1.example.com',\n * 'https://upload2.example.com',\n * 'https://cdn.example.com'\n * ]);\n *\n * // Now actual uploads will use pre-warmed connections\n * await client.request('https://upload1.example.com/file', {\n * method: 'PUT',\n * body: fileData\n * });\n * ```\n */\n async warmupConnections(urls: string[]): Promise<void> {\n if (urls.length === 0) return;\n\n console.log(`Warming up connections to ${urls.length} hosts...`);\n\n // Make lightweight HEAD requests to warm up connections\n const warmupPromises = urls.map(async (url) => {\n try {\n await this.request(url, {\n method: \"HEAD\",\n timeout: 5000, // 5 second timeout for warmup\n });\n } catch (error) {\n // Ignore warmup failures - they're optional\n console.warn(`Connection warmup failed for ${url}:`, error);\n }\n });\n\n // Wait for all warmup requests (with timeout)\n await Promise.allSettled(warmupPromises);\n console.log(\"Connection warmup completed\");\n }\n\n /**\n * Resets all connection metrics and statistics to initial state.\n *\n * Useful for clearing metrics after a long-running session or when\n * you want to start fresh measurement without creating a new client instance.\n *\n * @example\n * ```typescript\n * // Reset metrics after a batch of uploads\n * client.reset();\n *\n * // Start fresh measurements\n * const metrics = client.getMetrics(); // All counters back to zero\n * ```\n */\n reset(): void {\n this.connectionTimes = [];\n this.requestCount = 0;\n this.connectionCount = 0;\n this.errorCount = 0;\n this.timeoutCount = 0;\n this.retryCount = 0;\n this.startTime = Date.now();\n this.metrics = {\n activeConnections: 0,\n totalConnections: 0,\n reuseRate: 0,\n averageConnectionTime: 0,\n };\n this.http2Info = this.detectHttp2Support();\n }\n\n /**\n * Gracefully shuts down the HTTP client and logs final statistics.\n *\n * In browser environments, connections are managed by the browser and cannot\n * be explicitly closed. This method waits briefly for pending requests to complete,\n * logs final connection metrics, and resets internal state.\n *\n * @example\n * ```typescript\n * // Clean shutdown with metrics logging\n * await client.close();\n * // Logs: \"Total requests: 150, Connection reuse: 85%, Avg time: 45ms, Health: healthy\"\n * ```\n */\n async close(): Promise<void> {\n console.log(\"Gracefully shutting down HTTP client...\");\n\n // In browser environment, we can't explicitly close connections\n // The browser manages connection pooling automatically\n // But we can clean up our internal state\n\n // Wait a short time for any pending requests to complete\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n // Log final statistics\n const finalMetrics = this.getDetailedMetrics();\n console.log(\"Final connection metrics:\", {\n totalRequests: this.requestCount,\n connectionReuse: `${Math.round(finalMetrics.reuseRate * 100)}%`,\n averageConnectionTime: `${Math.round(finalMetrics.averageConnectionTime)}ms`,\n health: finalMetrics.health.status,\n });\n\n this.reset();\n console.log(\"HTTP client shutdown complete\");\n }\n}\n","import type {\n AbortControllerFactory,\n AbortControllerLike,\n AbortSignalLike,\n} from \"@uploadista/client-core\";\n\n/**\n * Browser implementation of AbortController that wraps the native AbortController API.\n *\n * This class provides a minimal wrapper around the browser's native AbortController\n * to ensure compatibility with the Uploadista client's AbortControllerLike interface.\n * It's used for canceling uploads and HTTP requests.\n *\n * @example\n * ```typescript\n * const controller = new BrowserAbortController();\n *\n * // Start an operation with the signal\n * fetch('https://api.example.com/upload', {\n * signal: controller.signal,\n * method: 'POST',\n * body: formData\n * });\n *\n * // Cancel the operation\n * controller.abort('User canceled');\n * ```\n */\nclass BrowserAbortController implements AbortControllerLike {\n private native: AbortController;\n\n /**\n * Creates a new BrowserAbortController instance.\n *\n * Initializes a new native AbortController from the browser's API.\n */\n constructor() {\n this.native = new AbortController();\n }\n\n /**\n * Gets the AbortSignal associated with this controller.\n *\n * This signal is passed to abortable operations (like fetch or upload)\n * and will be triggered when abort() is called.\n *\n * @returns The abort signal that operations can listen to\n */\n get signal(): AbortSignalLike {\n return this.native.signal;\n }\n\n /**\n * Aborts the operation associated with this controller.\n *\n * When called, this will trigger the abort signal, causing any operations\n * listening to it (such as fetch requests or file uploads) to be canceled.\n *\n * @param reason - Optional reason for the abort, which will be available on the AbortSignal\n *\n * @example\n * ```typescript\n * const controller = new BrowserAbortController();\n *\n * // Start upload\n * const upload = client.upload(file, { signal: controller.signal });\n *\n * // Cancel upload after 5 seconds\n * setTimeout(() => {\n * controller.abort('Timeout exceeded');\n * }, 5000);\n * ```\n */\n abort(reason?: unknown): void {\n this.native.abort(reason);\n }\n}\n\n/**\n * Creates a factory for browser AbortController instances.\n *\n * This factory is used by the Uploadista client to create AbortController\n * instances for canceling uploads and HTTP requests. It wraps the browser's\n * native AbortController API.\n *\n * @returns An AbortControllerFactory that creates browser-compatible abort controllers\n *\n * @example\n * ```typescript\n * import { createBrowserAbortControllerFactory } from '@uploadista/client-browser';\n *\n * const factory = createBrowserAbortControllerFactory();\n *\n * // Create a controller\n * const controller = factory.create();\n *\n * // Use it to cancel operations\n * const upload = client.upload(file, {\n * signal: controller.signal\n * });\n *\n * // Later, cancel the upload\n * controller.abort();\n * ```\n */\nexport const createBrowserAbortControllerFactory =\n (): AbortControllerFactory => ({\n create: (): AbortControllerLike => new BrowserAbortController(),\n });\n","/**\n * Computes the SHA-256 checksum of a Blob using the Web Crypto API.\n *\n * This utility function provides a browser-native way to compute cryptographic\n * hashes of file data. It uses the SubtleCrypto API (part of Web Crypto) which\n * provides hardware-accelerated cryptographic operations when available.\n *\n * The SHA-256 algorithm produces a 256-bit (32-byte) hash value, typically\n * rendered as a 64-character hexadecimal string. SHA-256 is widely used for:\n * - File integrity verification\n * - Content deduplication\n * - File fingerprinting\n * - Checksum validation\n *\n * **Performance note:** For large files (>100MB), this function loads the entire\n * file into memory before hashing. For extremely large files, consider chunked\n * hashing approaches if memory is a concern.\n *\n * @param blob - The Blob or File to hash\n * @returns Promise resolving to the hex-encoded SHA-256 hash\n *\n * @throws {Error} When the hash computation fails (e.g., out of memory, crypto API unavailable)\n *\n * @example\n * ```typescript\n * import { computeblobSha256 } from '@uploadista/client-browser';\n *\n * // Hash a File from input\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * const file = fileInput.files[0];\n * const hash = await computeblobSha256(file);\n * console.log('File SHA-256:', hash);\n * // Output: \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"\n *\n * // Hash a Blob\n * const blob = new Blob(['Hello, World!'], { type: 'text/plain' });\n * const hash = await computeblobSha256(blob);\n * console.log('Blob SHA-256:', hash);\n *\n * // Verify file integrity\n * const expectedHash = 'abc123...';\n * const actualHash = await computeblobSha256(file);\n * if (actualHash === expectedHash) {\n * console.log('File integrity verified');\n * } else {\n * console.error('File has been modified or corrupted');\n * }\n *\n * // Check for duplicate files\n * const file1Hash = await computeblobSha256(file1);\n * const file2Hash = await computeblobSha256(file2);\n * if (file1Hash === file2Hash) {\n * console.log('Files are identical (same content)');\n * }\n * ```\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest} for SubtleCrypto.digest API\n */\nexport async function computeblobSha256(blob: Blob): Promise<string> {\n try {\n // Read blob as ArrayBuffer\n const arrayBuffer = await blob.arrayBuffer();\n\n // Compute SHA-256 hash using Web Crypto API\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", arrayBuffer);\n\n // Convert hash to hex string\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n return hashHex;\n } catch (error) {\n throw new Error(\n `Failed to compute file checksum: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n}\n","import type { ChecksumService } from \"@uploadista/client-core\";\nimport { computeblobSha256 } from \"../utils/hash-util\";\n\n/**\n * Creates a checksum service for verifying data integrity using SHA-256 hashing.\n *\n * This service uses the browser's Web Crypto API to compute SHA-256 checksums\n * of data chunks during upload. Checksums ensure that uploaded data hasn't been\n * corrupted in transit and matches what was sent.\n *\n * The service is optimized for browser environments and leverages native crypto\n * APIs for performance.\n *\n * @returns A ChecksumService that computes SHA-256 hashes for data chunks\n *\n * @example\n * ```typescript\n * import { createChecksumService } from '@uploadista/client-browser';\n *\n * const checksumService = createChecksumService();\n *\n * // Compute checksum for a data chunk\n * const data = new Uint8Array([1, 2, 3, 4, 5]);\n * const checksum = await checksumService.computeChecksum(data);\n * console.log('SHA-256 checksum:', checksum);\n * // Output: \"74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0\"\n * ```\n *\n * @see {@link computeblobSha256} for the underlying hash implementation\n */\nexport function createChecksumService(): ChecksumService {\n return {\n /**\n * Computes a SHA-256 checksum for the provided data.\n *\n * Converts the Uint8Array to a Blob and uses the Web Crypto API\n * to calculate its SHA-256 hash, returned as a hex string.\n *\n * @param data - The data to checksum\n * @returns Promise resolving to the hex-encoded SHA-256 checksum\n */\n computeChecksum: async (data: Uint8Array<ArrayBuffer>) => {\n return computeblobSha256(new Blob([data]));\n },\n };\n}\n","import type {\n FileSource as CoreFileSource,\n FileReaderService,\n SliceResult,\n} from \"@uploadista/client-core\";\nimport type { BrowserUploadInput } from \"../types/upload-input\";\n\n/**\n * Browser-specific file reader interface for opening and reading file data.\n *\n * Provides methods to open File/Blob objects and create FileSource instances\n * that support chunked reading for upload operations.\n */\nexport type FileReader = {\n /**\n * Opens a file and prepares it for chunked reading.\n *\n * @param input - The File or Blob to open\n * @param chunkSize - Size of chunks to read (in bytes)\n * @returns Promise resolving to a FileSource for reading the file\n */\n openFile: (\n input: BrowserUploadInput,\n chunkSize: number,\n ) => Promise<FileSource>;\n};\n\n/**\n * Represents an opened file that can be read in chunks.\n *\n * This interface provides the core functionality for reading file data\n * in a streaming fashion, which is essential for uploading large files\n * without loading them entirely into memory.\n */\nexport type FileSource = {\n /** The original input File or Blob */\n input: BrowserUploadInput;\n\n /** Total size of the file in bytes, or null if unknown */\n size: number | null;\n\n /**\n * Reads a slice of data from the file.\n *\n * @param start - Starting byte offset\n * @param end - Ending byte offset (exclusive)\n * @returns Promise resolving to the slice result with data and metadata\n */\n slice: (start: number, end: number) => Promise<SliceResult>;\n\n /**\n * Closes the file and releases any resources.\n *\n * For browser File/Blob objects, this is typically a no-op as there are\n * no resources to release, but is included for interface compatibility.\n */\n close: () => void;\n};\n\n/**\n * Re-export SliceResult from core for convenience.\n */\nexport type { SliceResult };\n\n/**\n * Creates a FileSource from a Blob or File object.\n *\n * This function wraps a Blob (or File, which extends Blob) and provides\n * a slice() method for reading specific byte ranges. It uses the Blob.slice()\n * and arrayBuffer() APIs to efficiently read file chunks without loading\n * the entire file into memory.\n *\n * @param blob - The Blob or File to wrap\n * @returns A FileSource that can read chunks from the blob\n *\n * @example\n * ```typescript\n * const file = new File(['content'], 'test.txt');\n * const source = blobFileSource(file);\n *\n * // Read first 1024 bytes\n * const chunk = await source.slice(0, 1024);\n * console.log('Chunk size:', chunk.size);\n * console.log('Is complete:', chunk.done);\n * ```\n */\nfunction blobFileSource(blob: Blob): FileSource {\n return {\n input: blob,\n size: blob.size,\n slice: async (start: number, end: number) => {\n const value = blob.slice(start, end);\n const size = value.size;\n const done = end >= blob.size;\n\n return { value: new Uint8Array(await value.arrayBuffer()), size, done };\n },\n close: () => {},\n };\n}\n\n/**\n * Creates a browser-specific file reader service for the Uploadista client.\n *\n * This service provides the ability to open and read File/Blob objects from\n * the browser's File API. It converts browser-native file objects into a\n * format that can be chunked and uploaded efficiently.\n *\n * The service supports:\n * - File objects from `<input type=\"file\">` elements\n * - File objects from drag-and-drop events\n * - Blob objects created programmatically\n *\n * @returns A FileReaderService configured for browser environments\n *\n * @example\n * ```typescript\n * import { createBrowserFileReaderService } from '@uploadista/client-browser';\n *\n * const fileReader = createBrowserFileReaderService();\n *\n * // Open a file from input element\n * const input = document.querySelector('input[type=\"file\"]');\n * const file = input.files[0];\n * const source = await fileReader.openFile(file, 5 * 1024 * 1024); // 5MB chunks\n *\n * console.log('File name:', source.name);\n * console.log('File size:', source.size);\n * console.log('File type:', source.type);\n *\n * // Read first chunk\n * const chunk = await source.slice(0, 5 * 1024 * 1024);\n * console.log('Read', chunk.size, 'bytes');\n * ```\n *\n * @throws {Error} When the input is not a File or Blob\n */\nexport function createBrowserFileReaderService(): FileReaderService<BrowserUploadInput> {\n return {\n openFile: async (\n input: BrowserUploadInput,\n _chunkSize: number,\n ): Promise<CoreFileSource> => {\n // File is a subtype of Blob, so we can check for Blob here.\n if (input instanceof Blob) {\n const source = blobFileSource(input);\n return {\n input: source.input,\n size: source.size,\n slice: source.slice,\n close: source.close,\n name: source.input instanceof File ? source.input.name : null,\n type: source.input instanceof File ? source.input.type : null,\n lastModified:\n source.input instanceof File ? source.input.lastModified : null,\n };\n }\n\n throw new Error(\n \"source object may only be an instance of File, Blob in this environment\",\n );\n },\n };\n}\n","import type { FingerprintService } from \"@uploadista/client-core\";\nimport type { BrowserUploadInput } from \"../types/upload-input\";\nimport { computeblobSha256 } from \"../utils/hash-util\";\n\n/**\n * Creates a fingerprint service for generating unique file identifiers.\n *\n * This service computes SHA-256 fingerprints of files to uniquely identify them.\n * Fingerprints are used for:\n * - Detecting duplicate uploads\n * - Resuming interrupted uploads\n * - Verifying file integrity\n * - Implementing deduplication strategies\n *\n * The fingerprint is computed using the Web Crypto API and represents the\n * SHA-256 hash of the entire file content. Two identical files will always\n * produce the same fingerprint.\n *\n * @returns A FingerprintService that computes SHA-256 fingerprints for browser files\n *\n * @example\n * ```typescript\n * import { createFingerprintService } from '@uploadista/client-browser';\n *\n * const fingerprintService = createFingerprintService();\n *\n * // Generate fingerprint for a file\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * const file = fileInput.files[0];\n *\n * const fingerprint = await fingerprintService.computeFingerprint(\n * file,\n * 'https://api.example.com/upload'\n * );\n *\n * console.log('File fingerprint:', fingerprint);\n * // Can be used to check if file was previously uploaded\n * ```\n *\n * @see {@link computeblobSha256} for the underlying hash implementation\n */\nexport function createFingerprintService(): FingerprintService<BrowserUploadInput> {\n return {\n /**\n * Computes a unique fingerprint for a file.\n *\n * Calculates the SHA-256 hash of the entire file content. The endpoint\n * parameter is currently unused but included for interface compatibility\n * with other platform implementations that might use it for salt or\n * endpoint-specific fingerprinting.\n *\n * @param file - The File or Blob to fingerprint\n * @param _endpoint - Upload endpoint (currently unused, reserved for future use)\n * @returns Promise resolving to the hex-encoded SHA-256 fingerprint\n *\n * @example\n * ```typescript\n * const file = new File(['content'], 'example.txt');\n * const fingerprint = await service.computeFingerprint(file, 'https://api.example.com');\n * // fingerprint: \"ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73\"\n * ```\n */\n computeFingerprint: async (file, _endpoint) => {\n return computeblobSha256(file);\n },\n };\n}\n","import type { IdGenerationService } from \"@uploadista/client-core\";\n\n/**\n * Creates a browser-specific ID generation service using the Web Crypto API.\n *\n * This service generates cryptographically secure random UUIDs (v4) using the\n * browser's native `crypto.randomUUID()` method. These UUIDs are used throughout\n * the Uploadista client for:\n * - Upload session identifiers\n * - Chunk identifiers\n * - Request correlation IDs\n * - Internal tracking\n *\n * The generated UUIDs conform to RFC 4122 version 4 (random) and provide\n * strong uniqueness guarantees suitable for distributed systems.\n *\n * Browser compatibility: Requires support for `crypto.randomUUID()` (available\n * in modern browsers). If you need to support older browsers, consider using a\n * polyfill.\n *\n * @returns An IdGenerationService that generates cryptographically secure UUIDs\n *\n * @example\n * ```typescript\n * import { createBrowserIdGenerationService } from '@uploadista/client-browser';\n *\n * const idService = createBrowserIdGenerationService();\n *\n * // Generate a unique ID\n * const id = idService.generate();\n * console.log('Generated ID:', id);\n * // Output: \"550e8400-e29b-41d4-a716-446655440000\" (example UUID v4)\n *\n * // Each call generates a new unique ID\n * const id2 = idService.generate();\n * console.log('Another ID:', id2);\n * // Output: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\" (different UUID)\n * ```\n */\nexport function createBrowserIdGenerationService(): IdGenerationService {\n return {\n /**\n * Generates a cryptographically secure random UUID (v4).\n *\n * Uses the Web Crypto API's `crypto.randomUUID()` method to generate\n * a UUID conforming to RFC 4122 version 4. Each UUID is statistically\n * unique and suitable for use as an identifier in distributed systems.\n *\n * @returns A UUID v4 string in the format \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\"\n *\n * @example\n * ```typescript\n * const service = createBrowserIdGenerationService();\n * const uploadId = service.generate();\n * console.log(uploadId); // \"f47ac10b-58cc-4372-a567-0e02b2c3d479\"\n * ```\n */\n generate: () => crypto.randomUUID(),\n };\n}\n","import type { PlatformService, Timeout } from \"@uploadista/client-core\";\n\n/**\n * Creates a browser-specific platform service that provides environment capabilities.\n *\n * This service abstracts platform-specific functionality and provides a consistent\n * interface for the Uploadista client to interact with browser APIs. It handles:\n * - Timer management (setTimeout/clearTimeout)\n * - Environment detection (browser vs. Node.js)\n * - Network connectivity status\n * - File object detection and metadata extraction\n *\n * This abstraction allows the core upload logic to remain platform-agnostic while\n * still accessing browser-specific features when needed.\n *\n * @returns A PlatformService configured for browser environments\n *\n * @example\n * ```typescript\n * import { createBrowserPlatformService } from '@uploadista/client-browser';\n *\n * const platform = createBrowserPlatformService();\n *\n * // Check if running in browser\n * console.log('Is browser:', platform.isBrowser()); // true\n *\n * // Check network status\n * console.log('Is online:', platform.isOnline()); // true or false\n *\n * // Extract file metadata\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * const file = fileInput.files[0];\n * console.log('File name:', platform.getFileName(file));\n * console.log('File type:', platform.getFileType(file));\n * console.log('File size:', platform.getFileSize(file));\n * ```\n */\nexport function createBrowserPlatformService(): PlatformService {\n return {\n /**\n * Schedules a function to be executed after a specified delay.\n *\n * Wraps the browser's native `setTimeout` function.\n *\n * @param callback - Function to execute after the delay\n * @param ms - Delay in milliseconds before executing the callback\n * @returns A timeout ID that can be passed to clearTimeout\n *\n * @example\n * ```typescript\n * const timeoutId = platform.setTimeout(() => {\n * console.log('Executed after 1 second');\n * }, 1000);\n * ```\n */\n setTimeout: (callback: () => void, ms: number | undefined) => {\n return globalThis.setTimeout(callback, ms);\n },\n\n /**\n * Cancels a timeout previously scheduled with setTimeout.\n *\n * Wraps the browser's native `clearTimeout` function.\n *\n * @param id - The timeout ID returned by setTimeout\n *\n * @example\n * ```typescript\n * const timeoutId = platform.setTimeout(() => { }, 1000);\n * platform.clearTimeout(timeoutId); // Cancel the timeout\n * ```\n */\n clearTimeout: (id: Timeout) => {\n globalThis.clearTimeout(id as number);\n },\n\n /**\n * Checks if the code is running in a browser environment.\n *\n * Detects browser by checking for the existence of the `window` object.\n * This is useful for conditional logic that should only run in browsers.\n *\n * @returns `true` if running in a browser, `false` otherwise\n *\n * @example\n * ```typescript\n * if (platform.isBrowser()) {\n * // Browser-specific code\n * console.log('Running in browser');\n * }\n * ```\n */\n isBrowser: () => {\n return typeof window !== \"undefined\";\n },\n\n /**\n * Checks if the browser is currently online.\n *\n * Uses the Navigator Online Status API (`navigator.onLine`) to determine\n * network connectivity. Note that this only indicates if the device has\n * a network connection, not if it can reach the internet.\n *\n * @returns `true` if online, `false` if offline, defaults to `true` if not in browser\n *\n * @example\n * ```typescript\n * if (platform.isOnline()) {\n * // Proceed with upload\n * await client.upload(file);\n * } else {\n * console.log('Waiting for network connection...');\n * }\n *\n * // Listen for online/offline events\n * window.addEventListener('online', () => {\n * console.log('Back online, resuming upload');\n * });\n * ```\n */\n isOnline: () => {\n if (typeof navigator !== \"undefined\") {\n return navigator.onLine;\n }\n return true;\n },\n\n /**\n * Checks if a value is a File object.\n *\n * Type guard to determine if an unknown value is a browser File object.\n * Useful for validating upload inputs and conditional file handling.\n *\n * @param value - The value to check\n * @returns `true` if the value is a File instance, `false` otherwise\n *\n * @example\n * ```typescript\n * const input = getUploadInput(); // unknown type\n *\n * if (platform.isFileLike(input)) {\n * // TypeScript knows input is a File here\n * console.log('Uploading file:', input.name);\n * }\n * ```\n */\n isFileLike: (value: unknown) => {\n return value instanceof File;\n },\n\n /**\n * Extracts the file name from a File object.\n *\n * @param file - The file to extract the name from\n * @returns The file name string, or `undefined` if not a File\n *\n * @example\n * ```typescript\n * const file = new File(['content'], 'document.pdf');\n * const name = platform.getFileName(file);\n * console.log(name); // \"document.pdf\"\n * ```\n */\n getFileName: (file: unknown) => {\n if (file instanceof File) {\n return file.name;\n }\n return undefined;\n },\n\n /**\n * Extracts the MIME type from a File object.\n *\n * @param file - The file to extract the type from\n * @returns The MIME type string, or `undefined` if not a File\n *\n * @example\n * ```typescript\n * const file = new File(['content'], 'image.png', { type: 'image/png' });\n * const type = platform.getFileType(file);\n * console.log(type); // \"image/png\"\n * ```\n */\n getFileType: (file: unknown) => {\n if (file instanceof File) {\n return file.type;\n }\n return undefined;\n },\n\n /**\n * Extracts the file size in bytes from a File object.\n *\n * @param file - The file to extract the size from\n * @returns The file size in bytes, or `undefined` if not a File\n *\n * @example\n * ```typescript\n * const file = new File(['Hello'], 'greeting.txt');\n * const size = platform.getFileSize(file);\n * console.log(size); // 5 (bytes)\n * ```\n */\n getFileSize: (file: unknown) => {\n if (file instanceof File) {\n return file.size;\n }\n return undefined;\n },\n\n /**\n * Extracts the last modified timestamp from a File object.\n *\n * @param file - The file to extract the timestamp from\n * @returns The last modified timestamp in milliseconds since epoch, or `undefined` if not a File\n *\n * @example\n * ```typescript\n * const file = new File(['content'], 'data.txt');\n * const lastModified = platform.getFileLastModified(file);\n * console.log(new Date(lastModified)); // Date object of when file was last modified\n * ```\n */\n getFileLastModified: (file: unknown) => {\n if (file instanceof File) {\n return file.lastModified;\n }\n return undefined;\n },\n };\n}\n","import type { StorageService } from \"@uploadista/client-core\";\n\n/**\n * Creates a browser storage service using localStorage for persistent data storage.\n *\n * This service provides a key-value storage interface backed by the browser's\n * localStorage API. Data stored with this service persists across browser sessions\n * and page reloads until explicitly deleted or cleared by the user.\n *\n * Use cases include:\n * - Persisting upload state for resumable uploads\n * - Caching file metadata and fingerprints\n * - Storing user preferences and settings\n * - Maintaining upload history\n *\n * **Important notes:**\n * - localStorage has a typical limit of 5-10MB per origin\n * - Data is stored as strings (objects are JSON-serialized)\n * - localStorage is synchronous but this service wraps it in Promises for consistency\n * - Data is scoped to the origin (protocol + domain + port)\n * - Users can clear localStorage through browser settings\n *\n * @returns A StorageService backed by browser localStorage\n *\n * @example\n * ```typescript\n * import { createLocalStorageService } from '@uploadista/client-browser';\n *\n * const storage = createLocalStorageService();\n *\n * // Store upload state\n * await storage.setItem('upload:123', JSON.stringify({\n * fileId: '123',\n * progress: 75,\n * uploadedChunks: [0, 1, 2]\n * }));\n *\n * // Retrieve upload state\n * const data = await storage.getItem('upload:123');\n * if (data) {\n * const state = JSON.parse(data);\n * console.log('Resuming upload at', state.progress, '%');\n * }\n *\n * // Find all uploads\n * const uploads = await storage.find('upload:');\n * console.log('Found', Object.keys(uploads).length, 'uploads');\n *\n * // Clean up completed upload\n * await storage.removeItem('upload:123');\n * ```\n *\n * @see {@link createSessionStorageService} for session-only storage\n */\nexport function createLocalStorageService(): StorageService {\n /**\n * Internal helper to find entries matching a prefix.\n *\n * Iterates through all localStorage keys and returns those that start\n * with the specified prefix along with their values.\n *\n * @param prefix - Key prefix to filter by\n * @returns Object mapping matching keys to their values\n * @private\n */\n const findEntries = (prefix: string): Record<string, string> => {\n const results: Record<string, string> = {};\n\n for (const key in localStorage) {\n if (key.startsWith(prefix)) {\n const item = localStorage.getItem(key);\n if (item) {\n results[key] = item;\n }\n }\n }\n\n return results;\n };\n\n return {\n /**\n * Retrieves a value from localStorage by key.\n *\n * @param key - The key to retrieve\n * @returns Promise resolving to the value, or null if the key doesn't exist\n *\n * @example\n * ```typescript\n * const value = await storage.getItem('user:preferences');\n * if (value) {\n * const prefs = JSON.parse(value);\n * }\n * ```\n */\n async getItem(key: string): Promise<string | null> {\n return localStorage.getItem(key);\n },\n\n /**\n * Stores a value in localStorage.\n *\n * If the key already exists, its value will be overwritten.\n * Values must be strings; use JSON.stringify() for objects.\n *\n * @param key - The key to store under\n * @param value - The string value to store\n *\n * @throws {QuotaExceededError} If localStorage quota is exceeded\n *\n * @example\n * ```typescript\n * // Store string\n * await storage.setItem('upload:status', 'completed');\n *\n * // Store object\n * await storage.setItem('upload:metadata', JSON.stringify({\n * name: 'file.txt',\n * size: 1024\n * }));\n * ```\n */\n async setItem(key: string, value: string): Promise<void> {\n localStorage.setItem(key, value);\n },\n\n /**\n * Removes a value from localStorage by key.\n *\n * If the key doesn't exist, this is a no-op (no error is thrown).\n *\n * @param key - The key to remove\n *\n * @example\n * ```typescript\n * // Clean up completed upload\n * await storage.removeItem('upload:123');\n * ```\n */\n async removeItem(key: string): Promise<void> {\n localStorage.removeItem(key);\n },\n\n /**\n * Retrieves all entries from localStorage.\n *\n * Returns an object mapping every key in localStorage to its value.\n * Use with caution as this can return a large amount of data.\n *\n * @returns Promise resolving to object with all key-value pairs\n *\n * @example\n * ```typescript\n * const all = await storage.findAll();\n * console.log('Total items in storage:', Object.keys(all).length);\n * ```\n */\n async findAll(): Promise<Record<string, string>> {\n return findEntries(\"\");\n },\n\n /**\n * Finds all entries with keys starting with a given prefix.\n *\n * Useful for querying related data or implementing namespacing.\n * For example, use \"upload:\" prefix to find all upload-related entries.\n *\n * @param prefix - The key prefix to search for\n * @returns Promise resolving to object with matching key-value pairs\n *\n * @example\n * ```typescript\n * // Find all uploads\n * const uploads = await storage.find('upload:');\n * for (const [key, value] of Object.entries(uploads)) {\n * console.log('Upload:', key, JSON.parse(value));\n * }\n *\n * // Find user preferences\n * const prefs = await storage.find('pref:');\n * ```\n */\n async find(prefix: string): Promise<Record<string, string>> {\n return findEntries(prefix);\n },\n };\n}\n","import type { WebSocketFactory, WebSocketLike } from \"@uploadista/client-core\";\n\n/**\n * Browser implementation of WebSocket that wraps the native WebSocket API.\n *\n * This class provides a minimal wrapper around the browser's native WebSocket\n * to ensure compatibility with the Uploadista client's WebSocketLike interface.\n * It's used for real-time communication features like:\n * - Upload progress streaming\n * - Flow execution status updates\n * - Real-time error notifications\n * - Live event feeds\n *\n * The wrapper preserves all WebSocket states and properly proxies all events\n * while maintaining the standard WebSocket lifecycle.\n *\n * @example\n * ```typescript\n * const ws = new BrowserWebSocket('wss://api.example.com/ws');\n *\n * ws.onopen = () => {\n * console.log('Connected');\n * ws.send('Hello server');\n * };\n *\n * ws.onmessage = (event) => {\n * console.log('Message:', event.data);\n * };\n *\n * ws.onerror = (event) => {\n * console.error('Error:', event.message);\n * };\n *\n * ws.onclose = (event) => {\n * console.log('Closed:', event.code, event.reason);\n * };\n * ```\n */\nclass BrowserWebSocket implements WebSocketLike {\n /** WebSocket is currently connecting (readyState = 0) */\n readonly CONNECTING = 0;\n /** WebSocket connection is open and ready (readyState = 1) */\n readonly OPEN = 1;\n /** WebSocket is closing (readyState = 2) */\n readonly CLOSING = 2;\n /** WebSocket connection is closed (readyState = 3) */\n readonly CLOSED = 3;\n\n /**\n * Current state of the WebSocket connection.\n *\n * Possible values:\n * - 0 (CONNECTING): Connection is being established\n * - 1 (OPEN): Connection is open and ready for communication\n * - 2 (CLOSING): Connection is closing\n * - 3 (CLOSED): Connection is closed or couldn't be opened\n */\n readyState: number;\n\n /** Event handler called when the connection is established */\n onopen: (() => void) | null = null;\n\n /** Event handler called when the connection is closed */\n onclose: ((event: { code: number; reason: string }) => void) | null = null;\n\n /** Event handler called when an error occurs */\n onerror: ((event: { message: string }) => void) | null = null;\n\n /** Event handler called when a message is received */\n onmessage: ((event: { data: string }) => void) | null = null;\n\n private native: WebSocket;\n\n /**\n * Creates a new BrowserWebSocket instance.\n *\n * Initializes a native WebSocket connection to the specified URL and sets up\n * event handler proxying to convert native events to the WebSocketLike format.\n *\n * @param url - WebSocket URL to connect to (must use ws:// or wss:// protocol)\n *\n * @example\n * ```typescript\n * const ws = new BrowserWebSocket('wss://api.example.com/upload/progress');\n * ```\n */\n constructor(url: string) {\n this.native = new WebSocket(url);\n this.readyState = this.native.readyState;\n\n // Proxy event handlers to convert native events to WebSocketLike format\n this.native.onopen = () => {\n this.readyState = this.native.readyState;\n this.onopen?.();\n };\n\n this.native.onclose = (event) => {\n this.readyState = this.native.readyState;\n const closeEvent = event as CloseEvent;\n this.onclose?.({ code: closeEvent.code, reason: closeEvent.reason });\n };\n\n this.native.onerror = (_event) => {\n this.onerror?.({ message: \"WebSocket error\" });\n };\n\n this.native.onmessage = (event) => {\n const messageEvent = event as MessageEvent;\n this.onmessage?.({ data: messageEvent.data });\n };\n }\n\n /**\n * Sends data through the WebSocket connection.\n *\n * The data can be either a string (text message) or a Uint8Array (binary message).\n * The connection must be in the OPEN state before sending data.\n *\n * @param data - String or binary data to send\n *\n * @throws {Error} If the connection is not open\n *\n * @example\n * ```typescript\n * // Send text message\n * ws.send('{\"type\": \"subscribe\", \"channel\": \"uploads\"}');\n *\n * // Send binary data\n * const buffer = new Uint8Array([1, 2, 3, 4]);\n * ws.send(buffer);\n * ```\n */\n send(data: string | Uint8Array): void {\n this.native.send(data);\n }\n\n /**\n * Closes the WebSocket connection.\n *\n * Optionally accepts a close code and reason that will be sent to the server.\n * Standard close codes include:\n * - 1000: Normal closure\n * - 1001: Going away (e.g., page navigation)\n * - 1002: Protocol error\n * - 1003: Unsupported data\n *\n * @param code - Optional close code (default: 1000 for normal closure)\n * @param reason - Optional human-readable reason for closing\n *\n * @example\n * ```typescript\n * // Normal close\n * ws.close();\n *\n * // Close with reason\n * ws.close(1000, 'Upload completed');\n *\n * // Close due to error\n * ws.close(1011, 'Internal error during upload');\n * ```\n */\n close(code?: number, reason?: string): void {\n this.native.close(code, reason);\n }\n}\n\n/**\n * Creates a factory for browser WebSocket connections.\n *\n * This factory is used by the Uploadista client to create WebSocket connections\n * for real-time features. It wraps the browser's native WebSocket API and provides\n * a consistent interface for the client.\n *\n * The factory creates WebSockets that support:\n * - Real-time upload progress updates\n * - Flow execution status streaming\n * - Live error and event notifications\n * - Bidirectional client-server communication\n *\n * @returns A WebSocketFactory that creates browser-compatible WebSocket connections\n *\n * @example\n * ```typescript\n * import { createBrowserWebSocketFactory } from '@uploadista/client-browser';\n *\n * const factory = createBrowserWebSocketFactory();\n *\n * // Create a WebSocket connection\n * const ws = factory.create('wss://api.example.com/ws/upload/123');\n *\n * // Set up event handlers\n * ws.onmessage = (event) => {\n * const data = JSON.parse(event.data);\n * if (data.type === 'progress') {\n * console.log('Upload progress:', data.progress);\n * }\n * };\n *\n * ws.onopen = () => {\n * console.log('WebSocket connected');\n * };\n *\n * ws.onclose = (event) => {\n * console.log('WebSocket closed:', event.code, event.reason);\n * };\n * ```\n *\n * @see {@link BrowserWebSocket} for the WebSocket implementation details\n */\nexport const createBrowserWebSocketFactory = (): WebSocketFactory => ({\n create: (url: string): WebSocketLike => new BrowserWebSocket(url),\n});\n","import type {\n ConnectionPoolConfig,\n ServiceContainer,\n} from \"@uploadista/client-core\";\nimport { createHttpClient } from \"../http-client\";\nimport type { BrowserUploadInput } from \"../types/upload-input\";\nimport { createBrowserAbortControllerFactory } from \"./abort-controller-factory\";\nimport { createChecksumService } from \"./checksum-service\";\nimport { createBrowserFileReaderService } from \"./file-reader\";\nimport { createFingerprintService } from \"./fingerprint-service\";\nimport { createBrowserIdGenerationService } from \"./id-generation/id-generation\";\nimport { createBrowserPlatformService } from \"./platform-service\";\nimport { createLocalStorageService } from \"./storage/local-storage-service\";\nimport { createBrowserWebSocketFactory } from \"./websocket-factory\";\n\nexport interface BrowserServiceOptions {\n /**\n * HTTP client configuration for connection pooling\n */\n connectionPooling?: ConnectionPoolConfig;\n\n /**\n * Whether to use localStorage for persistence\n * If false, uses in-memory storage\n * @default true\n */\n useLocalStorage?: boolean;\n}\n\n/**\n * Creates a service container with browser-specific implementations\n * of all required services for the upload client\n *\n * @param options - Configuration options for browser services\n * @returns ServiceContainer with browser implementations\n *\n * @example\n * ```typescript\n * import { createBrowserServices } from '@uploadista/browser/services';\n *\n * const services = createBrowserServices({\n * useLocalStorage: true,\n * connectionPooling: {\n * maxConnectionsPerHost: 6,\n * connectionTimeout: 30000,\n * }\n * });\n * ```\n */\nexport function createBrowserServices(\n options: BrowserServiceOptions = {},\n): ServiceContainer<BrowserUploadInput> {\n const { connectionPooling, useLocalStorage = true } = options;\n\n // Create storage service (localStorage or in-memory fallback)\n const storage = useLocalStorage\n ? createLocalStorageService()\n : // Placeholder for in-memory storage - use localStorage as default for browser\n createLocalStorageService();\n\n // Create other services\n const idGeneration = createBrowserIdGenerationService();\n const httpClient = createHttpClient(connectionPooling);\n const fileReader = createBrowserFileReaderService();\n const websocket = createBrowserWebSocketFactory();\n const abortController = createBrowserAbortControllerFactory();\n const checksumService = createChecksumService();\n const fingerprintService = createFingerprintService();\n\n return {\n platform: createBrowserPlatformService(),\n storage,\n idGeneration,\n httpClient,\n fileReader,\n websocket,\n abortController,\n checksumService,\n fingerprintService,\n };\n}\n","import {\n type ConnectionPoolConfig,\n createClientStorage,\n createLogger,\n createUploadistaClient as createUploadistaClientCore,\n type UploadistaClientOptions as UploadistaClientOptionsCore,\n} from \"@uploadista/client-core\";\nimport { createBrowserServices } from \"../services/create-browser-services\";\nimport type { BrowserUploadInput } from \"../types/upload-input\";\n\n/**\n * Configuration options for creating a browser-specific Uploadista client.\n *\n * This interface extends the core client options but omits browser-specific\n * services that are automatically provided by the browser environment.\n * These services include WebSocket factory, AbortController, ID generation,\n * storage, logging, platform detection, fingerprinting, HTTP client, file reader,\n * and checksum calculation.\n *\n * @example\n * ```typescript\n * import { createUploadistaClient } from '@uploadista/client-browser';\n *\n * const client = createUploadistaClient({\n * endpoint: 'https://api.uploadista.com/upload',\n * connectionPooling: {\n * maxConnectionsPerHost: 6,\n * enableHttp2: true\n * }\n * });\n * ```\n */\nexport interface UploadistaClientOptions\n extends Omit<\n UploadistaClientOptionsCore<BrowserUploadInput>,\n | \"webSocketFactory\"\n | \"abortControllerFactory\"\n | \"generateId\"\n | \"clientStorage\"\n | \"logger\"\n | \"platformService\"\n | \"fingerprintService\"\n | \"httpClient\"\n | \"fileReader\"\n | \"checksumService\"\n > {\n /**\n * Connection pooling configuration for the HTTP client.\n *\n * Controls how the browser manages HTTP connections for optimal performance.\n * The browser's native fetch API with keep-alive headers is used under the hood.\n *\n * @default\n * ```typescript\n * {\n * maxConnectionsPerHost: 6,\n * connectionTimeout: 30000,\n * keepAliveTimeout: 60000,\n * enableHttp2: true,\n * retryOnConnectionError: true\n * }\n * ```\n *\n * @example\n * ```typescript\n * connectionPooling: {\n * maxConnectionsPerHost: 10,\n * enableHttp2: true,\n * keepAliveTimeout: 120000\n * }\n * ```\n */\n connectionPooling?: ConnectionPoolConfig;\n}\n\n/**\n * Creates a browser-optimized Uploadista client for file uploads and flow processing.\n *\n * This factory function automatically configures all browser-specific services including:\n * - Fetch-based HTTP client with connection pooling\n * - Native WebSocket support for real-time progress\n * - localStorage for upload state persistence\n * - Web Crypto API for checksums and fingerprints\n * - File API for reading and chunking files\n * - Browser platform detection and capabilities\n *\n * The created client can handle File and Blob objects from file inputs, drag-and-drop,\n * or programmatically created content. It supports resumable uploads, progress tracking,\n * and flow-based file processing.\n *\n * @param options - Configuration options for the browser client\n * @returns A fully configured Uploadista client ready for browser use\n *\n * @example\n * ```typescript\n * import { createUploadistaClient } from '@uploadista/client-browser';\n *\n * // Basic usage\n * const client = createUploadistaClient({\n * endpoint: 'https://api.uploadista.com/upload'\n * });\n *\n * // With custom configuration\n * const client = createUploadistaClient({\n * endpoint: 'https://api.uploadista.com/upload',\n * connectionPooling: {\n * maxConnectionsPerHost: 6,\n * enableHttp2: true,\n * keepAliveTimeout: 60000\n * },\n * chunkSize: 5 * 1024 * 1024, // 5MB chunks\n * retryDelays: [1000, 3000, 5000],\n * allowedMetaFields: ['userId', 'projectId']\n * });\n *\n * // Upload a file\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * const file = fileInput.files[0];\n *\n * const upload = await client.upload(file, {\n * onProgress: (event) => {\n * console.log(`Progress: ${event.progress}%`);\n * }\n * });\n *\n * console.log('Upload complete:', upload.id);\n * ```\n *\n * @see {@link UploadistaClientOptions} for available configuration options\n * @see {@link BrowserUploadInput} for supported file input types\n */\nexport function createUploadistaClient(options: UploadistaClientOptions) {\n const services = createBrowserServices({\n connectionPooling: options.connectionPooling,\n });\n\n return createUploadistaClientCore<BrowserUploadInput>({\n ...options,\n webSocketFactory: services.websocket,\n abortControllerFactory: services.abortController,\n platformService: services.platform,\n httpClient: services.httpClient,\n fileReader: services.fileReader,\n generateId: services.idGeneration,\n fingerprintService: services.fingerprintService,\n checksumService: services.checksumService,\n logger: createLogger(false, () => {}),\n clientStorage: createClientStorage(services.storage),\n });\n}\n","/**\n * Framework Integration Utilities\n *\n * This module provides TypeScript utilities and helper types for building\n * framework-specific wrappers around the Uploadista client.\n *\n * @module framework-utils\n */\n\nimport type { FlowResult, UploadResult } from \"@uploadista/client-core\";\nimport type { FlowEvent } from \"@uploadista/core/flow\";\nimport type { UploadEvent, UploadFile } from \"@uploadista/core/types\";\n\n/**\n * Base upload state that framework wrappers should implement\n */\nexport interface BaseUploadState {\n status: \"idle\" | \"uploading\" | \"success\" | \"error\" | \"aborted\";\n progress: number;\n bytesUploaded: number;\n totalBytes: number;\n error?: Error;\n result?: UploadResult<UploadFile>;\n}\n\n/**\n * Base flow upload state\n */\nexport interface BaseFlowUploadState extends BaseUploadState {\n jobId?: string;\n flowStatus?: \"pending\" | \"processing\" | \"completed\" | \"failed\";\n flowResult?: FlowResult<unknown>;\n}\n\n/**\n * Progress callback signature\n */\nexport type ProgressCallback = (\n uploadId: string,\n bytesUploaded: number,\n totalBytes: number,\n) => void;\n\n/**\n * Complete callback signature\n */\nexport type CompleteCallback = (uploadId: string, result: UploadResult) => void;\n\n/**\n * Error callback signature\n */\nexport type ErrorCallback = (uploadId: string, error: Error) => void;\n\n/**\n * Abort callback signature\n */\nexport type AbortCallback = (uploadId: string) => void;\n\n/**\n * Event handler signature for framework wrappers\n */\nexport type EventHandler<T = unknown> = (event: T) => void;\n\n/**\n * WebSocket event handler signature\n */\nexport type WebSocketEventHandler = (event: UploadEvent | FlowEvent) => void;\n\n/**\n * Framework state updater function signature\n * @template T - The state type\n */\nexport type StateUpdater<T> = (updater: (prevState: T) => T) => void;\n\n/**\n * Cleanup function returned by setup functions\n */\nexport type CleanupFunction = () => void;\n\n/**\n * Upload item for multi-upload tracking\n */\nexport interface UploadItem {\n id: string;\n file: File;\n status: BaseUploadState[\"status\"];\n progress: number;\n bytesUploaded: number;\n totalBytes: number;\n error?: Error;\n result?: UploadResult;\n}\n\n/**\n * Multi-upload aggregate statistics\n */\nexport interface MultiUploadStats {\n totalFiles: number;\n completedFiles: number;\n failedFiles: number;\n totalBytes: number;\n uploadedBytes: number;\n totalProgress: number;\n allComplete: boolean;\n hasErrors: boolean;\n}\n\n/**\n * Drag and drop state\n */\nexport interface DragDropState {\n isDragging: boolean;\n isOver: boolean;\n files: File[];\n}\n\n/**\n * File validation result\n */\nexport interface FileValidationResult {\n valid: boolean;\n error?: string;\n}\n\n/**\n * File validation function signature\n */\nexport type FileValidator = (file: File) => FileValidationResult;\n\n/**\n * Utility: Calculate aggregate upload statistics\n */\nexport function calculateMultiUploadStats(\n uploads: UploadItem[],\n): MultiUploadStats {\n const totalFiles = uploads.length;\n const completedFiles = uploads.filter((u) => u.status === \"success\").length;\n const failedFiles = uploads.filter((u) => u.status === \"error\").length;\n const totalBytes = uploads.reduce((sum, u) => sum + u.totalBytes, 0);\n const uploadedBytes = uploads.reduce((sum, u) => sum + u.bytesUploaded, 0);\n const totalProgress = totalBytes > 0 ? (uploadedBytes / totalBytes) * 100 : 0;\n const allComplete = uploads.every((u) => u.status === \"success\");\n const hasErrors = uploads.some((u) => u.status === \"error\");\n\n return {\n totalFiles,\n completedFiles,\n failedFiles,\n totalBytes,\n uploadedBytes,\n totalProgress,\n allComplete,\n hasErrors,\n };\n}\n\n/**\n * Utility: Format file size for display\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;\n}\n\n/**\n * Utility: Format progress percentage\n */\nexport function formatProgress(progress: number): string {\n return `${Math.round(progress)}%`;\n}\n\n/**\n * Utility: Get file extension\n */\nexport function getFileExtension(filename: string): string {\n const lastDot = filename.lastIndexOf(\".\");\n return lastDot === -1 ? \"\" : filename.slice(lastDot + 1).toLowerCase();\n}\n\n/**\n * Utility: Check if file is an image\n */\nexport function isImageFile(file: File): boolean {\n return file.type.startsWith(\"image/\");\n}\n\n/**\n * Utility: Check if file is a video\n */\nexport function isVideoFile(file: File): boolean {\n return file.type.startsWith(\"video/\");\n}\n\n/**\n * Utility: Create file size validator\n */\nexport function createFileSizeValidator(maxSizeBytes: number): FileValidator {\n return (file: File): FileValidationResult => {\n if (file.size > maxSizeBytes) {\n return {\n valid: false,\n error: `File size exceeds maximum of ${formatFileSize(maxSizeBytes)}`,\n };\n }\n return { valid: true };\n };\n}\n\n/**\n * Utility: Create file type validator\n */\nexport function createFileTypeValidator(allowedTypes: string[]): FileValidator {\n return (file: File): FileValidationResult => {\n const fileType = file.type.toLowerCase();\n const fileExt = getFileExtension(file.name);\n\n const isAllowed = allowedTypes.some((type) => {\n if (type.startsWith(\".\")) {\n return type.slice(1) === fileExt;\n }\n if (type.includes(\"*\")) {\n const pattern = type.replace(\"*\", \"\");\n return fileType.startsWith(pattern);\n }\n return fileType === type;\n });\n\n if (!isAllowed) {\n return {\n valid: false,\n error: `File type not allowed. Allowed types: ${allowedTypes.join(\", \")}`,\n };\n }\n return { valid: true };\n };\n}\n\n/**\n * Utility: Compose multiple validators\n */\nexport function composeValidators(\n ...validators: FileValidator[]\n): FileValidator {\n return (file: File): FileValidationResult => {\n for (const validator of validators) {\n const result = validator(file);\n if (!result.valid) {\n return result;\n }\n }\n return { valid: true };\n };\n}\n\n/**\n * Utility: Generate unique upload ID\n */\nexport function generateUploadId(): string {\n return `upload-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Utility: Create delay promise for retry logic\n */\nexport function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Utility: Calculate exponential backoff delay\n */\nexport function calculateBackoff(\n attempt: number,\n baseDelay = 1000,\n maxDelay = 30000,\n): number {\n const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);\n // Add jitter to prevent thundering herd\n return delay + Math.random() * 1000;\n}\n\n/**\n * Utility: Create retry wrapper for upload function\n */\nexport function createRetryWrapper<T>(\n fn: () => Promise<T>,\n maxAttempts = 3,\n shouldRetry: (error: unknown) => boolean = () => true,\n): () => Promise<T> {\n return async () => {\n let lastError: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n if (attempt < maxAttempts - 1 && shouldRetry(error)) {\n const delayMs = calculateBackoff(attempt);\n await delay(delayMs);\n continue;\n }\n break;\n }\n }\n throw lastError;\n };\n}\n\n/**\n * Type guard: Check if error is network-related (should retry)\n */\nexport function isNetworkError(error: unknown): boolean {\n if (error instanceof Error) {\n return (\n error.message.includes(\"network\") ||\n error.message.includes(\"timeout\") ||\n error.message.includes(\"connection\") ||\n error.message.includes(\"ECONNREFUSED\") ||\n error.message.includes(\"ETIMEDOUT\")\n );\n }\n return false;\n}\n\n/**\n * Type guard: Check if error is abort-related (should not retry)\n */\nexport function isAbortError(error: unknown): boolean {\n if (error instanceof Error) {\n return error.name === \"AbortError\" || error.message.includes(\"abort\");\n }\n return false;\n}\n\n/**\n * Format upload speed in human-readable format\n */\nexport function formatSpeed(bytesPerSecond: number): string {\n if (bytesPerSecond === 0) return \"0 B/s\";\n const k = 1024;\n const sizes = [\"B/s\", \"KB/s\", \"MB/s\", \"GB/s\"];\n const i = Math.floor(Math.log(bytesPerSecond) / Math.log(k));\n return `${parseFloat((bytesPerSecond / k ** i).toFixed(1))} ${sizes[i]}`;\n}\n\n/**\n * Format duration in human-readable format\n */\nexport function formatDuration(milliseconds: number): string {\n if (milliseconds < 1000) {\n return `${Math.round(milliseconds)}ms`;\n }\n\n if (milliseconds < 60000) {\n return `${Math.round(milliseconds / 1000)}s`;\n }\n\n if (milliseconds < 3600000) {\n const minutes = Math.floor(milliseconds / 60000);\n const seconds = Math.round((milliseconds % 60000) / 1000);\n return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;\n }\n\n const hours = Math.floor(milliseconds / 3600000);\n const minutes = Math.round((milliseconds % 3600000) / 60000);\n return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;\n}\n\n/**\n * Validate file type against accepted types\n */\nexport function validateFileType(file: File, accept: string[]): boolean {\n if (!accept || accept.length === 0) return true;\n\n return accept.some((acceptType) => {\n if (acceptType.startsWith(\".\")) {\n // File extension check\n return file.name.toLowerCase().endsWith(acceptType.toLowerCase());\n }\n\n // MIME type check (supports wildcards like image/*)\n if (acceptType.endsWith(\"/*\")) {\n const baseType = acceptType.slice(0, -2);\n return file.type.startsWith(baseType);\n }\n\n return file.type === acceptType;\n });\n}\n\n/**\n * Check if a file is an audio file\n */\nexport function isAudioFile(file: File): boolean {\n return file.type.startsWith(\"audio/\");\n}\n\n/**\n * Check if a file is a document\n */\nexport function isDocumentFile(file: File): boolean {\n const documentTypes = [\n \"application/pdf\",\n \"application/msword\",\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n \"application/vnd.ms-excel\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n \"application/vnd.ms-powerpoint\",\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n \"text/plain\",\n \"text/csv\",\n \"application/rtf\",\n ];\n\n return documentTypes.includes(file.type);\n}\n\n/**\n * Create a preview URL for a file (if supported)\n */\nexport function createFilePreview(file: File): string | null {\n if (isImageFile(file) || isVideoFile(file) || isAudioFile(file)) {\n return URL.createObjectURL(file);\n }\n return null;\n}\n\n/**\n * Clean up a preview URL created with createFilePreview\n */\nexport function revokeFilePreview(previewUrl: string): void {\n URL.revokeObjectURL(previewUrl);\n}\n\n/**\n * Calculate progress percentage\n */\nexport function calculateProgress(current: number, total: number): number {\n if (total === 0) return 0;\n return Math.min(100, Math.max(0, Math.round((current / total) * 100)));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["createUploadistaClientCore"],"sources":["../src/http-client.ts","../src/services/abort-controller-factory.ts","../src/utils/hash-util.ts","../src/services/checksum-service.ts","../src/services/file-reader.ts","../src/services/fingerprint-service.ts","../src/services/id-generation/id-generation.ts","../src/services/platform-service.ts","../src/services/storage/local-storage-service.ts","../src/services/websocket-factory.ts","../src/services/create-browser-services.ts","../src/client/create-uploadista-client.ts","../src/framework-utils.ts"],"sourcesContent":["import type {\n ConnectionHealth,\n ConnectionMetrics,\n ConnectionPoolConfig,\n DetailedConnectionMetrics,\n Http2Info,\n HttpClient,\n HttpRequestOptions,\n HttpResponse,\n} from \"@uploadista/client-core\";\n\n/**\n * Creates a browser-optimized HTTP client using the Fetch API.\n *\n * This factory function returns an HttpClient implementation that uses the browser's\n * native fetch() API with connection keep-alive headers for optimal performance.\n * The client automatically manages connection pooling, tracks metrics, and provides\n * connection health monitoring.\n *\n * @param config - Optional connection pooling configuration\n * @returns A configured HTTP client ready for making requests\n *\n * @example\n * ```typescript\n * import { createHttpClient } from '@uploadista/client-browser';\n *\n * // Basic usage with defaults\n * const client = createHttpClient();\n *\n * // With custom configuration\n * const client = createHttpClient({\n * maxConnectionsPerHost: 10,\n * connectionTimeout: 60000,\n * keepAliveTimeout: 120000,\n * enableHttp2: true,\n * retryOnConnectionError: true\n * });\n *\n * // Make a request\n * const response = await client.request('https://api.example.com/data', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ key: 'value' })\n * });\n *\n * // Check connection health\n * const metrics = client.getDetailedMetrics();\n * console.log('Connection health:', metrics.health.status);\n * ```\n *\n * @see {@link BrowserHttpClient} for implementation details\n */\nexport function createHttpClient(config?: ConnectionPoolConfig): HttpClient {\n return new BrowserHttpClient(config);\n}\n\n/**\n * Browser-optimized HTTP client implementation using the Fetch API with connection keep-alive.\n *\n * This class implements the HttpClient interface and provides:\n * - Connection pooling via keep-alive headers\n * - Connection metrics tracking (reuse rate, latency, error rates)\n * - HTTP/2 multiplexing detection and support\n * - Connection health monitoring with actionable recommendations\n * - Connection warmup capabilities\n * - Automatic timeout handling\n *\n * The browser manages actual connection pooling, but this client optimizes for\n * reuse through proper HTTP headers and provides visibility into connection performance.\n *\n * @example\n * ```typescript\n * const client = new BrowserHttpClient({\n * maxConnectionsPerHost: 6,\n * keepAliveTimeout: 60000,\n * enableHttp2: true\n * });\n *\n * // Monitor connection health\n * setInterval(() => {\n * const health = client.getDetailedMetrics().health;\n * if (health.status === 'poor') {\n * console.warn('Connection issues:', health.issues);\n * console.log('Recommendations:', health.recommendations);\n * }\n * }, 30000);\n * ```\n */\nclass BrowserHttpClient implements HttpClient {\n private config: Required<ConnectionPoolConfig>;\n private metrics: ConnectionMetrics;\n private connectionTimes: number[] = [];\n private requestCount = 0;\n private connectionCount = 0;\n private errorCount = 0;\n private timeoutCount = 0;\n private retryCount = 0;\n private startTime = Date.now();\n private http2Info: Http2Info;\n\n /**\n * Creates a new browser HTTP client instance.\n *\n * @param config - Connection pooling configuration with optional overrides\n */\n constructor(config: ConnectionPoolConfig = {}) {\n this.config = {\n maxConnectionsPerHost: config.maxConnectionsPerHost ?? 6,\n connectionTimeout: config.connectionTimeout ?? 30000,\n keepAliveTimeout: config.keepAliveTimeout ?? 60000,\n enableHttp2: config.enableHttp2 ?? true,\n retryOnConnectionError: config.retryOnConnectionError ?? true,\n };\n\n this.metrics = {\n activeConnections: 0,\n totalConnections: 0,\n reuseRate: 0,\n averageConnectionTime: 0,\n };\n\n // Initialize HTTP/2 detection\n this.http2Info = this.detectHttp2Support();\n }\n\n /**\n * Detects HTTP/2 support in the current browser environment.\n *\n * This method uses feature detection to determine if the browser supports\n * HTTP/2 features like multiplexing. In browsers, we can't directly query\n * the protocol version, so we infer support based on modern stream APIs.\n *\n * @returns HTTP/2 information with support status and detected features\n * @private\n */\n private detectHttp2Support(): Http2Info {\n // Check if we're in a browser environment\n if (typeof window === \"undefined\" || typeof navigator === \"undefined\") {\n // SSR/Node.js environment - return safe defaults\n return {\n supported: false,\n detected: false,\n version: \"h1.1\",\n multiplexingActive: false,\n };\n }\n\n // Check if the browser supports HTTP/2\n const supported = \"serviceWorker\" in navigator && \"fetch\" in window;\n\n // In browsers, we can't directly detect HTTP/2 protocol version\n // but we can make educated guesses based on browser features\n const hasModernFeatures =\n \"ReadableStream\" in window &&\n \"WritableStream\" in window &&\n \"TransformStream\" in window;\n\n return {\n supported,\n detected: false, // Will be updated during actual requests\n version: hasModernFeatures ? \"h2\" : \"h1.1\",\n multiplexingActive: hasModernFeatures && this.config.enableHttp2,\n };\n }\n\n /**\n * Makes an HTTP request using the Fetch API with optimized connection settings.\n *\n * This method automatically adds keep-alive headers for connection reuse,\n * handles timeouts via AbortController, and tracks connection metrics.\n *\n * @param url - The URL to request\n * @param options - Request options including method, headers, body, credentials, signal, and timeout\n * @returns Promise resolving to the HTTP response\n *\n * @throws {Error} When the request fails or times out\n *\n * @example\n * ```typescript\n * // Simple GET request\n * const response = await client.request('https://api.example.com/data');\n * const data = await response.json();\n *\n * // POST with timeout\n * const response = await client.request('https://api.example.com/upload', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ file: 'data' }),\n * timeout: 10000, // 10 seconds\n * signal: abortController.signal\n * });\n * ```\n */\n async request(\n url: string,\n options: HttpRequestOptions = {},\n ): Promise<HttpResponse> {\n this.requestCount++;\n\n // Create optimized fetch options for connection reuse\n const fetchOptions: RequestInit = {\n method: options.method || \"GET\",\n headers: {\n // Add connection keep-alive headers for better reuse\n Connection: \"keep-alive\",\n \"Keep-Alive\": `timeout=${this.config.keepAliveTimeout / 1000}`,\n ...options.headers,\n },\n body: options.body as BodyInit | null | undefined,\n credentials: options.credentials || \"include\",\n signal: options.signal as AbortSignal | undefined,\n };\n\n // Add timeout if specified\n if (options.timeout) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n if (options.signal) {\n options.signal.addEventListener(\"abort\", () => controller.abort());\n }\n\n fetchOptions.signal = controller.signal;\n\n try {\n const response = await this.makeRequest(url, fetchOptions);\n clearTimeout(timeoutId);\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n throw error;\n }\n }\n\n return this.makeRequest(url, fetchOptions);\n }\n\n /**\n * Internal method to execute the fetch request and track metrics.\n *\n * @param url - The URL to request\n * @param options - Native fetch RequestInit options\n * @returns Promise resolving to the HTTP response\n * @throws {Error} When the fetch fails\n * @private\n */\n private async makeRequest(\n url: string,\n options: RequestInit,\n ): Promise<HttpResponse> {\n const startTime = Date.now();\n\n try {\n const response = await fetch(url, options);\n const connectionTime = Date.now() - startTime;\n\n this.recordConnectionMetrics(connectionTime);\n\n return {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n ok: response.ok,\n json: () => response.json(),\n text: () => response.text(),\n arrayBuffer: () => response.arrayBuffer(),\n };\n } catch (error) {\n // Record failed connection attempt\n this.connectionCount++;\n throw error;\n }\n }\n\n /**\n * Records connection timing metrics and updates statistics.\n *\n * Tracks connection times, calculates reuse rates based on latency patterns,\n * and maintains a rolling window of the last 100 measurements.\n *\n * @param connectionTime - Time in milliseconds for this connection\n * @private\n */\n private recordConnectionMetrics(connectionTime: number): void {\n this.connectionTimes.push(connectionTime);\n this.connectionCount++;\n\n // Keep only last 100 measurements for average calculation\n if (this.connectionTimes.length > 100) {\n this.connectionTimes.shift();\n }\n\n // Update metrics\n this.metrics.totalConnections = this.connectionCount;\n this.metrics.averageConnectionTime =\n this.connectionTimes.reduce((sum, time) => sum + time, 0) /\n this.connectionTimes.length;\n\n // Estimate reuse rate based on connection time patterns\n // Faster connections are likely reused connections\n const fastConnections = this.connectionTimes.filter(\n (time) => time < 100,\n ).length;\n this.metrics.reuseRate = fastConnections / this.connectionTimes.length;\n }\n\n /**\n * Retrieves basic connection metrics.\n *\n * @returns Current connection metrics including total connections, reuse rate, and average connection time\n *\n * @example\n * ```typescript\n * const metrics = client.getMetrics();\n * console.log(`Reuse rate: ${Math.round(metrics.reuseRate * 100)}%`);\n * console.log(`Avg connection time: ${Math.round(metrics.averageConnectionTime)}ms`);\n * ```\n */\n getMetrics(): ConnectionMetrics {\n return { ...this.metrics };\n }\n\n /**\n * Retrieves detailed connection metrics with health assessment.\n *\n * Provides comprehensive metrics including error rates, request throughput,\n * connection health status with score, identified issues, and actionable\n * recommendations for improving connection performance.\n *\n * @returns Detailed metrics with health information, request rates, and HTTP/2 info\n *\n * @example\n * ```typescript\n * const detailed = client.getDetailedMetrics();\n *\n * // Check health\n * if (detailed.health.status === 'degraded') {\n * console.warn('Issues:', detailed.health.issues);\n * console.log('Try:', detailed.health.recommendations);\n * }\n *\n * // Monitor performance\n * console.log(`Requests/sec: ${detailed.requestsPerSecond.toFixed(2)}`);\n * console.log(`Error rate: ${(detailed.errorRate * 100).toFixed(1)}%`);\n * console.log(`Fast connections: ${detailed.fastConnections}/${detailed.fastConnections + detailed.slowConnections}`);\n * ```\n */\n getDetailedMetrics(): DetailedConnectionMetrics {\n const health = this.calculateConnectionHealth();\n const elapsed = (Date.now() - this.startTime) / 1000; // seconds\n const requestsPerSecond = elapsed > 0 ? this.requestCount / elapsed : 0;\n const errorRate =\n this.requestCount > 0 ? this.errorCount / this.requestCount : 0;\n\n const fastConnections = this.connectionTimes.filter(\n (time) => time < 100,\n ).length;\n const slowConnections = this.connectionTimes.length - fastConnections;\n\n return {\n ...this.metrics,\n health,\n requestsPerSecond,\n errorRate,\n timeouts: this.timeoutCount,\n retries: this.retryCount,\n fastConnections,\n slowConnections,\n http2Info: this.http2Info,\n };\n }\n\n /**\n * Calculates connection health status based on current metrics.\n *\n * Analyzes reuse rates, error rates, and connection latency to determine\n * overall health (healthy/degraded/poor), assigns a health score (0-100),\n * and provides specific issues and recommendations.\n *\n * @returns Health assessment with status, score, issues, and recommendations\n * @private\n */\n private calculateConnectionHealth(): ConnectionHealth {\n const issues: string[] = [];\n const recommendations: string[] = [];\n let score = 100;\n\n // Check reuse rate\n if (this.metrics.reuseRate < 0.3) {\n issues.push(\"Low connection reuse rate\");\n recommendations.push(\"Check if keep-alive headers are working\");\n score -= 30;\n } else if (this.metrics.reuseRate < 0.7) {\n issues.push(\"Moderate connection reuse rate\");\n recommendations.push(\"Consider adjusting keep-alive timeout\");\n score -= 15;\n }\n\n // Check error rate\n const errorRate =\n this.requestCount > 0 ? this.errorCount / this.requestCount : 0;\n if (errorRate > 0.1) {\n issues.push(\"High error rate\");\n recommendations.push(\"Check network stability and server configuration\");\n score -= 25;\n } else if (errorRate > 0.05) {\n issues.push(\"Moderate error rate\");\n recommendations.push(\"Monitor network conditions\");\n score -= 10;\n }\n\n // Check average connection time\n if (this.metrics.averageConnectionTime > 1000) {\n issues.push(\"Slow connection establishment\");\n recommendations.push(\"Check network latency and DNS resolution\");\n score -= 20;\n } else if (this.metrics.averageConnectionTime > 500) {\n issues.push(\"Moderate connection latency\");\n recommendations.push(\"Consider connection warming\");\n score -= 10;\n }\n\n // Determine status\n let status: \"healthy\" | \"degraded\" | \"poor\";\n if (score >= 80) {\n status = \"healthy\";\n } else if (score >= 60) {\n status = \"degraded\";\n } else {\n status = \"poor\";\n }\n\n return {\n status,\n score: Math.max(0, score),\n issues,\n recommendations,\n };\n }\n\n /**\n * Warms up HTTP connections to specified URLs by making lightweight HEAD requests.\n *\n * This is useful for establishing connections before actual data transfer,\n * reducing latency for subsequent requests. Particularly beneficial when\n * uploading to multiple endpoints or when connection setup time is critical.\n *\n * @param urls - Array of URLs to warm up connections to\n *\n * @example\n * ```typescript\n * // Warm up connections before uploading\n * await client.warmupConnections([\n * 'https://upload1.example.com',\n * 'https://upload2.example.com',\n * 'https://cdn.example.com'\n * ]);\n *\n * // Now actual uploads will use pre-warmed connections\n * await client.request('https://upload1.example.com/file', {\n * method: 'PUT',\n * body: fileData\n * });\n * ```\n */\n async warmupConnections(urls: string[]): Promise<void> {\n if (urls.length === 0) return;\n\n console.log(`Warming up connections to ${urls.length} hosts...`);\n\n // Make lightweight HEAD requests to warm up connections\n const warmupPromises = urls.map(async (url) => {\n try {\n await this.request(url, {\n method: \"HEAD\",\n timeout: 5000, // 5 second timeout for warmup\n });\n } catch (error) {\n // Ignore warmup failures - they're optional\n console.warn(`Connection warmup failed for ${url}:`, error);\n }\n });\n\n // Wait for all warmup requests (with timeout)\n await Promise.allSettled(warmupPromises);\n console.log(\"Connection warmup completed\");\n }\n\n /**\n * Resets all connection metrics and statistics to initial state.\n *\n * Useful for clearing metrics after a long-running session or when\n * you want to start fresh measurement without creating a new client instance.\n *\n * @example\n * ```typescript\n * // Reset metrics after a batch of uploads\n * client.reset();\n *\n * // Start fresh measurements\n * const metrics = client.getMetrics(); // All counters back to zero\n * ```\n */\n reset(): void {\n this.connectionTimes = [];\n this.requestCount = 0;\n this.connectionCount = 0;\n this.errorCount = 0;\n this.timeoutCount = 0;\n this.retryCount = 0;\n this.startTime = Date.now();\n this.metrics = {\n activeConnections: 0,\n totalConnections: 0,\n reuseRate: 0,\n averageConnectionTime: 0,\n };\n this.http2Info = this.detectHttp2Support();\n }\n\n /**\n * Gracefully shuts down the HTTP client and logs final statistics.\n *\n * In browser environments, connections are managed by the browser and cannot\n * be explicitly closed. This method waits briefly for pending requests to complete,\n * logs final connection metrics, and resets internal state.\n *\n * @example\n * ```typescript\n * // Clean shutdown with metrics logging\n * await client.close();\n * // Logs: \"Total requests: 150, Connection reuse: 85%, Avg time: 45ms, Health: healthy\"\n * ```\n */\n async close(): Promise<void> {\n console.log(\"Gracefully shutting down HTTP client...\");\n\n // In browser environment, we can't explicitly close connections\n // The browser manages connection pooling automatically\n // But we can clean up our internal state\n\n // Wait a short time for any pending requests to complete\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n // Log final statistics\n const finalMetrics = this.getDetailedMetrics();\n console.log(\"Final connection metrics:\", {\n totalRequests: this.requestCount,\n connectionReuse: `${Math.round(finalMetrics.reuseRate * 100)}%`,\n averageConnectionTime: `${Math.round(finalMetrics.averageConnectionTime)}ms`,\n health: finalMetrics.health.status,\n });\n\n this.reset();\n console.log(\"HTTP client shutdown complete\");\n }\n}\n","import type {\n AbortControllerFactory,\n AbortControllerLike,\n AbortSignalLike,\n} from \"@uploadista/client-core\";\n\n/**\n * Browser implementation of AbortController that wraps the native AbortController API.\n *\n * This class provides a minimal wrapper around the browser's native AbortController\n * to ensure compatibility with the Uploadista client's AbortControllerLike interface.\n * It's used for canceling uploads and HTTP requests.\n *\n * @example\n * ```typescript\n * const controller = new BrowserAbortController();\n *\n * // Start an operation with the signal\n * fetch('https://api.example.com/upload', {\n * signal: controller.signal,\n * method: 'POST',\n * body: formData\n * });\n *\n * // Cancel the operation\n * controller.abort('User canceled');\n * ```\n */\nclass BrowserAbortController implements AbortControllerLike {\n private native: AbortController;\n\n /**\n * Creates a new BrowserAbortController instance.\n *\n * Initializes a new native AbortController from the browser's API.\n */\n constructor() {\n this.native = new AbortController();\n }\n\n /**\n * Gets the AbortSignal associated with this controller.\n *\n * This signal is passed to abortable operations (like fetch or upload)\n * and will be triggered when abort() is called.\n *\n * @returns The abort signal that operations can listen to\n */\n get signal(): AbortSignalLike {\n return this.native.signal;\n }\n\n /**\n * Aborts the operation associated with this controller.\n *\n * When called, this will trigger the abort signal, causing any operations\n * listening to it (such as fetch requests or file uploads) to be canceled.\n *\n * @param reason - Optional reason for the abort, which will be available on the AbortSignal\n *\n * @example\n * ```typescript\n * const controller = new BrowserAbortController();\n *\n * // Start upload\n * const upload = client.upload(file, { signal: controller.signal });\n *\n * // Cancel upload after 5 seconds\n * setTimeout(() => {\n * controller.abort('Timeout exceeded');\n * }, 5000);\n * ```\n */\n abort(reason?: unknown): void {\n this.native.abort(reason);\n }\n}\n\n/**\n * Creates a factory for browser AbortController instances.\n *\n * This factory is used by the Uploadista client to create AbortController\n * instances for canceling uploads and HTTP requests. It wraps the browser's\n * native AbortController API.\n *\n * @returns An AbortControllerFactory that creates browser-compatible abort controllers\n *\n * @example\n * ```typescript\n * import { createBrowserAbortControllerFactory } from '@uploadista/client-browser';\n *\n * const factory = createBrowserAbortControllerFactory();\n *\n * // Create a controller\n * const controller = factory.create();\n *\n * // Use it to cancel operations\n * const upload = client.upload(file, {\n * signal: controller.signal\n * });\n *\n * // Later, cancel the upload\n * controller.abort();\n * ```\n */\nexport const createBrowserAbortControllerFactory =\n (): AbortControllerFactory => ({\n create: (): AbortControllerLike => new BrowserAbortController(),\n });\n","/**\n * Computes the SHA-256 checksum of a Blob using the Web Crypto API.\n *\n * This utility function provides a browser-native way to compute cryptographic\n * hashes of file data. It uses the SubtleCrypto API (part of Web Crypto) which\n * provides hardware-accelerated cryptographic operations when available.\n *\n * The SHA-256 algorithm produces a 256-bit (32-byte) hash value, typically\n * rendered as a 64-character hexadecimal string. SHA-256 is widely used for:\n * - File integrity verification\n * - Content deduplication\n * - File fingerprinting\n * - Checksum validation\n *\n * **Performance note:** For large files (>100MB), this function loads the entire\n * file into memory before hashing. For extremely large files, consider chunked\n * hashing approaches if memory is a concern.\n *\n * @param blob - The Blob or File to hash\n * @returns Promise resolving to the hex-encoded SHA-256 hash\n *\n * @throws {Error} When the hash computation fails (e.g., out of memory, crypto API unavailable)\n *\n * @example\n * ```typescript\n * import { computeblobSha256 } from '@uploadista/client-browser';\n *\n * // Hash a File from input\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * const file = fileInput.files[0];\n * const hash = await computeblobSha256(file);\n * console.log('File SHA-256:', hash);\n * // Output: \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"\n *\n * // Hash a Blob\n * const blob = new Blob(['Hello, World!'], { type: 'text/plain' });\n * const hash = await computeblobSha256(blob);\n * console.log('Blob SHA-256:', hash);\n *\n * // Verify file integrity\n * const expectedHash = 'abc123...';\n * const actualHash = await computeblobSha256(file);\n * if (actualHash === expectedHash) {\n * console.log('File integrity verified');\n * } else {\n * console.error('File has been modified or corrupted');\n * }\n *\n * // Check for duplicate files\n * const file1Hash = await computeblobSha256(file1);\n * const file2Hash = await computeblobSha256(file2);\n * if (file1Hash === file2Hash) {\n * console.log('Files are identical (same content)');\n * }\n * ```\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest} for SubtleCrypto.digest API\n */\nexport async function computeblobSha256(blob: Blob): Promise<string> {\n try {\n // Read blob as ArrayBuffer\n const arrayBuffer = await blob.arrayBuffer();\n\n // Compute SHA-256 hash using Web Crypto API\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", arrayBuffer);\n\n // Convert hash to hex string\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n return hashHex;\n } catch (error) {\n throw new Error(\n `Failed to compute file checksum: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n}\n","import type { ChecksumService } from \"@uploadista/client-core\";\nimport { computeblobSha256 } from \"../utils/hash-util\";\n\n/**\n * Creates a checksum service for verifying data integrity using SHA-256 hashing.\n *\n * This service uses the browser's Web Crypto API to compute SHA-256 checksums\n * of data chunks during upload. Checksums ensure that uploaded data hasn't been\n * corrupted in transit and matches what was sent.\n *\n * The service is optimized for browser environments and leverages native crypto\n * APIs for performance.\n *\n * @returns A ChecksumService that computes SHA-256 hashes for data chunks\n *\n * @example\n * ```typescript\n * import { createChecksumService } from '@uploadista/client-browser';\n *\n * const checksumService = createChecksumService();\n *\n * // Compute checksum for a data chunk\n * const data = new Uint8Array([1, 2, 3, 4, 5]);\n * const checksum = await checksumService.computeChecksum(data);\n * console.log('SHA-256 checksum:', checksum);\n * // Output: \"74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0\"\n * ```\n *\n * @see {@link computeblobSha256} for the underlying hash implementation\n */\nexport function createChecksumService(): ChecksumService {\n return {\n /**\n * Computes a SHA-256 checksum for the provided data.\n *\n * Converts the Uint8Array to a Blob and uses the Web Crypto API\n * to calculate its SHA-256 hash, returned as a hex string.\n *\n * @param data - The data to checksum\n * @returns Promise resolving to the hex-encoded SHA-256 checksum\n */\n computeChecksum: async (data: Uint8Array<ArrayBuffer>) => {\n return computeblobSha256(new Blob([data]));\n },\n };\n}\n","import type {\n FileSource as CoreFileSource,\n FileReaderService,\n SliceResult,\n} from \"@uploadista/client-core\";\nimport type { BrowserUploadInput } from \"../types/upload-input\";\n\n/**\n * Browser-specific file reader interface for opening and reading file data.\n *\n * Provides methods to open File/Blob objects and create FileSource instances\n * that support chunked reading for upload operations.\n */\nexport type FileReader = {\n /**\n * Opens a file and prepares it for chunked reading.\n *\n * @param input - The File or Blob to open\n * @param chunkSize - Size of chunks to read (in bytes)\n * @returns Promise resolving to a FileSource for reading the file\n */\n openFile: (\n input: BrowserUploadInput,\n chunkSize: number,\n ) => Promise<FileSource>;\n};\n\n/**\n * Represents an opened file that can be read in chunks.\n *\n * This interface provides the core functionality for reading file data\n * in a streaming fashion, which is essential for uploading large files\n * without loading them entirely into memory.\n */\nexport type FileSource = {\n /** The original input File or Blob */\n input: BrowserUploadInput;\n\n /** Total size of the file in bytes, or null if unknown */\n size: number | null;\n\n /**\n * Reads a slice of data from the file.\n *\n * @param start - Starting byte offset\n * @param end - Ending byte offset (exclusive)\n * @returns Promise resolving to the slice result with data and metadata\n */\n slice: (start: number, end: number) => Promise<SliceResult>;\n\n /**\n * Closes the file and releases any resources.\n *\n * For browser File/Blob objects, this is typically a no-op as there are\n * no resources to release, but is included for interface compatibility.\n */\n close: () => void;\n};\n\n/**\n * Re-export SliceResult from core for convenience.\n */\nexport type { SliceResult };\n\n/**\n * Creates a FileSource from a Blob or File object.\n *\n * This function wraps a Blob (or File, which extends Blob) and provides\n * a slice() method for reading specific byte ranges. It uses the Blob.slice()\n * and arrayBuffer() APIs to efficiently read file chunks without loading\n * the entire file into memory.\n *\n * @param blob - The Blob or File to wrap\n * @returns A FileSource that can read chunks from the blob\n *\n * @example\n * ```typescript\n * const file = new File(['content'], 'test.txt');\n * const source = blobFileSource(file);\n *\n * // Read first 1024 bytes\n * const chunk = await source.slice(0, 1024);\n * console.log('Chunk size:', chunk.size);\n * console.log('Is complete:', chunk.done);\n * ```\n */\nfunction blobFileSource(blob: Blob): FileSource {\n return {\n input: blob,\n size: blob.size,\n slice: async (start: number, end: number) => {\n const value = blob.slice(start, end);\n const size = value.size;\n const done = end >= blob.size;\n\n return { value: new Uint8Array(await value.arrayBuffer()), size, done };\n },\n close: () => {},\n };\n}\n\n/**\n * Creates a browser-specific file reader service for the Uploadista client.\n *\n * This service provides the ability to open and read File/Blob objects from\n * the browser's File API. It converts browser-native file objects into a\n * format that can be chunked and uploaded efficiently.\n *\n * The service supports:\n * - File objects from `<input type=\"file\">` elements\n * - File objects from drag-and-drop events\n * - Blob objects created programmatically\n *\n * @returns A FileReaderService configured for browser environments\n *\n * @example\n * ```typescript\n * import { createBrowserFileReaderService } from '@uploadista/client-browser';\n *\n * const fileReader = createBrowserFileReaderService();\n *\n * // Open a file from input element\n * const input = document.querySelector('input[type=\"file\"]');\n * const file = input.files[0];\n * const source = await fileReader.openFile(file, 5 * 1024 * 1024); // 5MB chunks\n *\n * console.log('File name:', source.name);\n * console.log('File size:', source.size);\n * console.log('File type:', source.type);\n *\n * // Read first chunk\n * const chunk = await source.slice(0, 5 * 1024 * 1024);\n * console.log('Read', chunk.size, 'bytes');\n * ```\n *\n * @throws {Error} When the input is not a File or Blob\n */\nexport function createBrowserFileReaderService(): FileReaderService<BrowserUploadInput> {\n return {\n openFile: async (\n input: BrowserUploadInput,\n _chunkSize: number,\n ): Promise<CoreFileSource> => {\n // File is a subtype of Blob, so we can check for Blob here.\n if (input instanceof Blob) {\n const source = blobFileSource(input);\n return {\n input: source.input,\n size: source.size,\n slice: source.slice,\n close: source.close,\n name: source.input instanceof File ? source.input.name : null,\n type: source.input instanceof File ? source.input.type : null,\n lastModified:\n source.input instanceof File ? source.input.lastModified : null,\n };\n }\n\n throw new Error(\n \"source object may only be an instance of File, Blob in this environment\",\n );\n },\n };\n}\n","import type { FingerprintService } from \"@uploadista/client-core\";\nimport type { BrowserUploadInput } from \"../types/upload-input\";\nimport { computeblobSha256 } from \"../utils/hash-util\";\n\n/**\n * Creates a fingerprint service for generating unique file identifiers.\n *\n * This service computes SHA-256 fingerprints of files to uniquely identify them.\n * Fingerprints are used for:\n * - Detecting duplicate uploads\n * - Resuming interrupted uploads\n * - Verifying file integrity\n * - Implementing deduplication strategies\n *\n * The fingerprint is computed using the Web Crypto API and represents the\n * SHA-256 hash of the entire file content. Two identical files will always\n * produce the same fingerprint.\n *\n * @returns A FingerprintService that computes SHA-256 fingerprints for browser files\n *\n * @example\n * ```typescript\n * import { createFingerprintService } from '@uploadista/client-browser';\n *\n * const fingerprintService = createFingerprintService();\n *\n * // Generate fingerprint for a file\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * const file = fileInput.files[0];\n *\n * const fingerprint = await fingerprintService.computeFingerprint(\n * file,\n * 'https://api.example.com/upload'\n * );\n *\n * console.log('File fingerprint:', fingerprint);\n * // Can be used to check if file was previously uploaded\n * ```\n *\n * @see {@link computeblobSha256} for the underlying hash implementation\n */\nexport function createFingerprintService(): FingerprintService<BrowserUploadInput> {\n return {\n /**\n * Computes a unique fingerprint for a file.\n *\n * Calculates the SHA-256 hash of the entire file content. The endpoint\n * parameter is currently unused but included for interface compatibility\n * with other platform implementations that might use it for salt or\n * endpoint-specific fingerprinting.\n *\n * @param file - The File or Blob to fingerprint\n * @param _endpoint - Upload endpoint (currently unused, reserved for future use)\n * @returns Promise resolving to the hex-encoded SHA-256 fingerprint\n *\n * @example\n * ```typescript\n * const file = new File(['content'], 'example.txt');\n * const fingerprint = await service.computeFingerprint(file, 'https://api.example.com');\n * // fingerprint: \"ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73\"\n * ```\n */\n computeFingerprint: async (file, _endpoint) => {\n return computeblobSha256(file);\n },\n };\n}\n","import type { IdGenerationService } from \"@uploadista/client-core\";\n\n/**\n * Creates a browser-specific ID generation service using the Web Crypto API.\n *\n * This service generates cryptographically secure random UUIDs (v4) using the\n * browser's native `crypto.randomUUID()` method. These UUIDs are used throughout\n * the Uploadista client for:\n * - Upload session identifiers\n * - Chunk identifiers\n * - Request correlation IDs\n * - Internal tracking\n *\n * The generated UUIDs conform to RFC 4122 version 4 (random) and provide\n * strong uniqueness guarantees suitable for distributed systems.\n *\n * Browser compatibility: Requires support for `crypto.randomUUID()` (available\n * in modern browsers). If you need to support older browsers, consider using a\n * polyfill.\n *\n * @returns An IdGenerationService that generates cryptographically secure UUIDs\n *\n * @example\n * ```typescript\n * import { createBrowserIdGenerationService } from '@uploadista/client-browser';\n *\n * const idService = createBrowserIdGenerationService();\n *\n * // Generate a unique ID\n * const id = idService.generate();\n * console.log('Generated ID:', id);\n * // Output: \"550e8400-e29b-41d4-a716-446655440000\" (example UUID v4)\n *\n * // Each call generates a new unique ID\n * const id2 = idService.generate();\n * console.log('Another ID:', id2);\n * // Output: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\" (different UUID)\n * ```\n */\nexport function createBrowserIdGenerationService(): IdGenerationService {\n return {\n /**\n * Generates a cryptographically secure random UUID (v4).\n *\n * Uses the Web Crypto API's `crypto.randomUUID()` method to generate\n * a UUID conforming to RFC 4122 version 4. Each UUID is statistically\n * unique and suitable for use as an identifier in distributed systems.\n *\n * @returns A UUID v4 string in the format \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\"\n *\n * @example\n * ```typescript\n * const service = createBrowserIdGenerationService();\n * const uploadId = service.generate();\n * console.log(uploadId); // \"f47ac10b-58cc-4372-a567-0e02b2c3d479\"\n * ```\n */\n generate: () => crypto.randomUUID(),\n };\n}\n","import type { PlatformService, Timeout } from \"@uploadista/client-core\";\n\n/**\n * Creates a browser-specific platform service that provides environment capabilities.\n *\n * This service abstracts platform-specific functionality and provides a consistent\n * interface for the Uploadista client to interact with browser APIs. It handles:\n * - Timer management (setTimeout/clearTimeout)\n * - Environment detection (browser vs. Node.js)\n * - Network connectivity status\n * - File object detection and metadata extraction\n *\n * This abstraction allows the core upload logic to remain platform-agnostic while\n * still accessing browser-specific features when needed.\n *\n * @returns A PlatformService configured for browser environments\n *\n * @example\n * ```typescript\n * import { createBrowserPlatformService } from '@uploadista/client-browser';\n *\n * const platform = createBrowserPlatformService();\n *\n * // Check if running in browser\n * console.log('Is browser:', platform.isBrowser()); // true\n *\n * // Check network status\n * console.log('Is online:', platform.isOnline()); // true or false\n *\n * // Extract file metadata\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * const file = fileInput.files[0];\n * console.log('File name:', platform.getFileName(file));\n * console.log('File type:', platform.getFileType(file));\n * console.log('File size:', platform.getFileSize(file));\n * ```\n */\nexport function createBrowserPlatformService(): PlatformService {\n return {\n /**\n * Schedules a function to be executed after a specified delay.\n *\n * Wraps the browser's native `setTimeout` function.\n *\n * @param callback - Function to execute after the delay\n * @param ms - Delay in milliseconds before executing the callback\n * @returns A timeout ID that can be passed to clearTimeout\n *\n * @example\n * ```typescript\n * const timeoutId = platform.setTimeout(() => {\n * console.log('Executed after 1 second');\n * }, 1000);\n * ```\n */\n setTimeout: (callback: () => void, ms: number | undefined) => {\n return globalThis.setTimeout(callback, ms);\n },\n\n /**\n * Cancels a timeout previously scheduled with setTimeout.\n *\n * Wraps the browser's native `clearTimeout` function.\n *\n * @param id - The timeout ID returned by setTimeout\n *\n * @example\n * ```typescript\n * const timeoutId = platform.setTimeout(() => { }, 1000);\n * platform.clearTimeout(timeoutId); // Cancel the timeout\n * ```\n */\n clearTimeout: (id: Timeout) => {\n globalThis.clearTimeout(id as number);\n },\n\n /**\n * Checks if the code is running in a browser environment.\n *\n * Detects browser by checking for the existence of the `window` object.\n * This is useful for conditional logic that should only run in browsers.\n *\n * @returns `true` if running in a browser, `false` otherwise\n *\n * @example\n * ```typescript\n * if (platform.isBrowser()) {\n * // Browser-specific code\n * console.log('Running in browser');\n * }\n * ```\n */\n isBrowser: () => {\n return typeof window !== \"undefined\";\n },\n\n /**\n * Checks if the browser is currently online.\n *\n * Uses the Navigator Online Status API (`navigator.onLine`) to determine\n * network connectivity. Note that this only indicates if the device has\n * a network connection, not if it can reach the internet.\n *\n * @returns `true` if online, `false` if offline, defaults to `true` if not in browser\n *\n * @example\n * ```typescript\n * if (platform.isOnline()) {\n * // Proceed with upload\n * await client.upload(file);\n * } else {\n * console.log('Waiting for network connection...');\n * }\n *\n * // Listen for online/offline events\n * window.addEventListener('online', () => {\n * console.log('Back online, resuming upload');\n * });\n * ```\n */\n isOnline: () => {\n if (typeof navigator !== \"undefined\") {\n return navigator.onLine;\n }\n return true;\n },\n\n /**\n * Checks if a value is a File object.\n *\n * Type guard to determine if an unknown value is a browser File object.\n * Useful for validating upload inputs and conditional file handling.\n *\n * @param value - The value to check\n * @returns `true` if the value is a File instance, `false` otherwise\n *\n * @example\n * ```typescript\n * const input = getUploadInput(); // unknown type\n *\n * if (platform.isFileLike(input)) {\n * // TypeScript knows input is a File here\n * console.log('Uploading file:', input.name);\n * }\n * ```\n */\n isFileLike: (value: unknown) => {\n return value instanceof File;\n },\n\n /**\n * Extracts the file name from a File object.\n *\n * @param file - The file to extract the name from\n * @returns The file name string, or `undefined` if not a File\n *\n * @example\n * ```typescript\n * const file = new File(['content'], 'document.pdf');\n * const name = platform.getFileName(file);\n * console.log(name); // \"document.pdf\"\n * ```\n */\n getFileName: (file: unknown) => {\n if (file instanceof File) {\n return file.name;\n }\n return undefined;\n },\n\n /**\n * Extracts the MIME type from a File object.\n *\n * @param file - The file to extract the type from\n * @returns The MIME type string, or `undefined` if not a File\n *\n * @example\n * ```typescript\n * const file = new File(['content'], 'image.png', { type: 'image/png' });\n * const type = platform.getFileType(file);\n * console.log(type); // \"image/png\"\n * ```\n */\n getFileType: (file: unknown) => {\n if (file instanceof File) {\n return file.type;\n }\n return undefined;\n },\n\n /**\n * Extracts the file size in bytes from a File object.\n *\n * @param file - The file to extract the size from\n * @returns The file size in bytes, or `undefined` if not a File\n *\n * @example\n * ```typescript\n * const file = new File(['Hello'], 'greeting.txt');\n * const size = platform.getFileSize(file);\n * console.log(size); // 5 (bytes)\n * ```\n */\n getFileSize: (file: unknown) => {\n if (file instanceof File) {\n return file.size;\n }\n return undefined;\n },\n\n /**\n * Extracts the last modified timestamp from a File object.\n *\n * @param file - The file to extract the timestamp from\n * @returns The last modified timestamp in milliseconds since epoch, or `undefined` if not a File\n *\n * @example\n * ```typescript\n * const file = new File(['content'], 'data.txt');\n * const lastModified = platform.getFileLastModified(file);\n * console.log(new Date(lastModified)); // Date object of when file was last modified\n * ```\n */\n getFileLastModified: (file: unknown) => {\n if (file instanceof File) {\n return file.lastModified;\n }\n return undefined;\n },\n };\n}\n","import type { StorageService } from \"@uploadista/client-core\";\n\n/**\n * Creates a browser storage service using localStorage for persistent data storage.\n *\n * This service provides a key-value storage interface backed by the browser's\n * localStorage API. Data stored with this service persists across browser sessions\n * and page reloads until explicitly deleted or cleared by the user.\n *\n * Use cases include:\n * - Persisting upload state for resumable uploads\n * - Caching file metadata and fingerprints\n * - Storing user preferences and settings\n * - Maintaining upload history\n *\n * **Important notes:**\n * - localStorage has a typical limit of 5-10MB per origin\n * - Data is stored as strings (objects are JSON-serialized)\n * - localStorage is synchronous but this service wraps it in Promises for consistency\n * - Data is scoped to the origin (protocol + domain + port)\n * - Users can clear localStorage through browser settings\n *\n * @returns A StorageService backed by browser localStorage\n *\n * @example\n * ```typescript\n * import { createLocalStorageService } from '@uploadista/client-browser';\n *\n * const storage = createLocalStorageService();\n *\n * // Store upload state\n * await storage.setItem('upload:123', JSON.stringify({\n * fileId: '123',\n * progress: 75,\n * uploadedChunks: [0, 1, 2]\n * }));\n *\n * // Retrieve upload state\n * const data = await storage.getItem('upload:123');\n * if (data) {\n * const state = JSON.parse(data);\n * console.log('Resuming upload at', state.progress, '%');\n * }\n *\n * // Find all uploads\n * const uploads = await storage.find('upload:');\n * console.log('Found', Object.keys(uploads).length, 'uploads');\n *\n * // Clean up completed upload\n * await storage.removeItem('upload:123');\n * ```\n *\n * @see {@link createSessionStorageService} for session-only storage\n */\nexport function createLocalStorageService(): StorageService {\n /**\n * Internal helper to find entries matching a prefix.\n *\n * Iterates through all localStorage keys and returns those that start\n * with the specified prefix along with their values.\n *\n * @param prefix - Key prefix to filter by\n * @returns Object mapping matching keys to their values\n * @private\n */\n const findEntries = (prefix: string): Record<string, string> => {\n const results: Record<string, string> = {};\n\n for (const key in localStorage) {\n if (key.startsWith(prefix)) {\n const item = localStorage.getItem(key);\n if (item) {\n results[key] = item;\n }\n }\n }\n\n return results;\n };\n\n return {\n /**\n * Retrieves a value from localStorage by key.\n *\n * @param key - The key to retrieve\n * @returns Promise resolving to the value, or null if the key doesn't exist\n *\n * @example\n * ```typescript\n * const value = await storage.getItem('user:preferences');\n * if (value) {\n * const prefs = JSON.parse(value);\n * }\n * ```\n */\n async getItem(key: string): Promise<string | null> {\n return localStorage.getItem(key);\n },\n\n /**\n * Stores a value in localStorage.\n *\n * If the key already exists, its value will be overwritten.\n * Values must be strings; use JSON.stringify() for objects.\n *\n * @param key - The key to store under\n * @param value - The string value to store\n *\n * @throws {QuotaExceededError} If localStorage quota is exceeded\n *\n * @example\n * ```typescript\n * // Store string\n * await storage.setItem('upload:status', 'completed');\n *\n * // Store object\n * await storage.setItem('upload:metadata', JSON.stringify({\n * name: 'file.txt',\n * size: 1024\n * }));\n * ```\n */\n async setItem(key: string, value: string): Promise<void> {\n localStorage.setItem(key, value);\n },\n\n /**\n * Removes a value from localStorage by key.\n *\n * If the key doesn't exist, this is a no-op (no error is thrown).\n *\n * @param key - The key to remove\n *\n * @example\n * ```typescript\n * // Clean up completed upload\n * await storage.removeItem('upload:123');\n * ```\n */\n async removeItem(key: string): Promise<void> {\n localStorage.removeItem(key);\n },\n\n /**\n * Retrieves all entries from localStorage.\n *\n * Returns an object mapping every key in localStorage to its value.\n * Use with caution as this can return a large amount of data.\n *\n * @returns Promise resolving to object with all key-value pairs\n *\n * @example\n * ```typescript\n * const all = await storage.findAll();\n * console.log('Total items in storage:', Object.keys(all).length);\n * ```\n */\n async findAll(): Promise<Record<string, string>> {\n return findEntries(\"\");\n },\n\n /**\n * Finds all entries with keys starting with a given prefix.\n *\n * Useful for querying related data or implementing namespacing.\n * For example, use \"upload:\" prefix to find all upload-related entries.\n *\n * @param prefix - The key prefix to search for\n * @returns Promise resolving to object with matching key-value pairs\n *\n * @example\n * ```typescript\n * // Find all uploads\n * const uploads = await storage.find('upload:');\n * for (const [key, value] of Object.entries(uploads)) {\n * console.log('Upload:', key, JSON.parse(value));\n * }\n *\n * // Find user preferences\n * const prefs = await storage.find('pref:');\n * ```\n */\n async find(prefix: string): Promise<Record<string, string>> {\n return findEntries(prefix);\n },\n };\n}\n","import type { WebSocketFactory, WebSocketLike } from \"@uploadista/client-core\";\n\n/**\n * Browser implementation of WebSocket that wraps the native WebSocket API.\n *\n * This class provides a minimal wrapper around the browser's native WebSocket\n * to ensure compatibility with the Uploadista client's WebSocketLike interface.\n * It's used for real-time communication features like:\n * - Upload progress streaming\n * - Flow execution status updates\n * - Real-time error notifications\n * - Live event feeds\n *\n * The wrapper preserves all WebSocket states and properly proxies all events\n * while maintaining the standard WebSocket lifecycle.\n *\n * @example\n * ```typescript\n * const ws = new BrowserWebSocket('wss://api.example.com/ws');\n *\n * ws.onopen = () => {\n * console.log('Connected');\n * ws.send('Hello server');\n * };\n *\n * ws.onmessage = (event) => {\n * console.log('Message:', event.data);\n * };\n *\n * ws.onerror = (event) => {\n * console.error('Error:', event.message);\n * };\n *\n * ws.onclose = (event) => {\n * console.log('Closed:', event.code, event.reason);\n * };\n * ```\n */\nclass BrowserWebSocket implements WebSocketLike {\n /** WebSocket is currently connecting (readyState = 0) */\n readonly CONNECTING = 0;\n /** WebSocket connection is open and ready (readyState = 1) */\n readonly OPEN = 1;\n /** WebSocket is closing (readyState = 2) */\n readonly CLOSING = 2;\n /** WebSocket connection is closed (readyState = 3) */\n readonly CLOSED = 3;\n\n /**\n * Current state of the WebSocket connection.\n *\n * Possible values:\n * - 0 (CONNECTING): Connection is being established\n * - 1 (OPEN): Connection is open and ready for communication\n * - 2 (CLOSING): Connection is closing\n * - 3 (CLOSED): Connection is closed or couldn't be opened\n */\n readyState: number;\n\n /** Event handler called when the connection is established */\n onopen: (() => void) | null = null;\n\n /** Event handler called when the connection is closed */\n onclose: ((event: { code: number; reason: string }) => void) | null = null;\n\n /** Event handler called when an error occurs */\n onerror: ((event: { message: string }) => void) | null = null;\n\n /** Event handler called when a message is received */\n onmessage: ((event: { data: string }) => void) | null = null;\n\n private native: WebSocket;\n\n /**\n * Creates a new BrowserWebSocket instance.\n *\n * Initializes a native WebSocket connection to the specified URL and sets up\n * event handler proxying to convert native events to the WebSocketLike format.\n *\n * @param url - WebSocket URL to connect to (must use ws:// or wss:// protocol)\n *\n * @example\n * ```typescript\n * const ws = new BrowserWebSocket('wss://api.example.com/upload/progress');\n * ```\n */\n constructor(url: string) {\n this.native = new WebSocket(url);\n this.readyState = this.native.readyState;\n\n // Proxy event handlers to convert native events to WebSocketLike format\n this.native.onopen = () => {\n this.readyState = this.native.readyState;\n this.onopen?.();\n };\n\n this.native.onclose = (event) => {\n this.readyState = this.native.readyState;\n const closeEvent = event as CloseEvent;\n this.onclose?.({ code: closeEvent.code, reason: closeEvent.reason });\n };\n\n this.native.onerror = (_event) => {\n this.onerror?.({ message: \"WebSocket error\" });\n };\n\n this.native.onmessage = (event) => {\n const messageEvent = event as MessageEvent;\n this.onmessage?.({ data: messageEvent.data });\n };\n }\n\n /**\n * Sends data through the WebSocket connection.\n *\n * The data can be either a string (text message) or a Uint8Array (binary message).\n * The connection must be in the OPEN state before sending data.\n *\n * @param data - String or binary data to send\n *\n * @throws {Error} If the connection is not open\n *\n * @example\n * ```typescript\n * // Send text message\n * ws.send('{\"type\": \"subscribe\", \"channel\": \"uploads\"}');\n *\n * // Send binary data\n * const buffer = new Uint8Array([1, 2, 3, 4]);\n * ws.send(buffer);\n * ```\n */\n send(data: string | Uint8Array): void {\n // `WebSocketLike.send` accepts a plain `Uint8Array` (backed by `ArrayBufferLike`),\n // but the DOM `WebSocket.send` only accepts an `ArrayBuffer`-backed `BufferSource`\n // (it rejects the `SharedArrayBuffer` case at the type level). In the browser the\n // views we send are always `ArrayBuffer`-backed, so this assertion is safe.\n this.native.send(data as string | Uint8Array<ArrayBuffer>);\n }\n\n /**\n * Closes the WebSocket connection.\n *\n * Optionally accepts a close code and reason that will be sent to the server.\n * Standard close codes include:\n * - 1000: Normal closure\n * - 1001: Going away (e.g., page navigation)\n * - 1002: Protocol error\n * - 1003: Unsupported data\n *\n * @param code - Optional close code (default: 1000 for normal closure)\n * @param reason - Optional human-readable reason for closing\n *\n * @example\n * ```typescript\n * // Normal close\n * ws.close();\n *\n * // Close with reason\n * ws.close(1000, 'Upload completed');\n *\n * // Close due to error\n * ws.close(1011, 'Internal error during upload');\n * ```\n */\n close(code?: number, reason?: string): void {\n this.native.close(code, reason);\n }\n}\n\n/**\n * Creates a factory for browser WebSocket connections.\n *\n * This factory is used by the Uploadista client to create WebSocket connections\n * for real-time features. It wraps the browser's native WebSocket API and provides\n * a consistent interface for the client.\n *\n * The factory creates WebSockets that support:\n * - Real-time upload progress updates\n * - Flow execution status streaming\n * - Live error and event notifications\n * - Bidirectional client-server communication\n *\n * @returns A WebSocketFactory that creates browser-compatible WebSocket connections\n *\n * @example\n * ```typescript\n * import { createBrowserWebSocketFactory } from '@uploadista/client-browser';\n *\n * const factory = createBrowserWebSocketFactory();\n *\n * // Create a WebSocket connection\n * const ws = factory.create('wss://api.example.com/ws/upload/123');\n *\n * // Set up event handlers\n * ws.onmessage = (event) => {\n * const data = JSON.parse(event.data);\n * if (data.type === 'progress') {\n * console.log('Upload progress:', data.progress);\n * }\n * };\n *\n * ws.onopen = () => {\n * console.log('WebSocket connected');\n * };\n *\n * ws.onclose = (event) => {\n * console.log('WebSocket closed:', event.code, event.reason);\n * };\n * ```\n *\n * @see {@link BrowserWebSocket} for the WebSocket implementation details\n */\nexport const createBrowserWebSocketFactory = (): WebSocketFactory => ({\n create: (url: string): WebSocketLike => new BrowserWebSocket(url),\n});\n","import type {\n ConnectionPoolConfig,\n ServiceContainer,\n} from \"@uploadista/client-core\";\nimport { createHttpClient } from \"../http-client\";\nimport type { BrowserUploadInput } from \"../types/upload-input\";\nimport { createBrowserAbortControllerFactory } from \"./abort-controller-factory\";\nimport { createChecksumService } from \"./checksum-service\";\nimport { createBrowserFileReaderService } from \"./file-reader\";\nimport { createFingerprintService } from \"./fingerprint-service\";\nimport { createBrowserIdGenerationService } from \"./id-generation/id-generation\";\nimport { createBrowserPlatformService } from \"./platform-service\";\nimport { createLocalStorageService } from \"./storage/local-storage-service\";\nimport { createBrowserWebSocketFactory } from \"./websocket-factory\";\n\nexport interface BrowserServiceOptions {\n /**\n * HTTP client configuration for connection pooling\n */\n connectionPooling?: ConnectionPoolConfig;\n\n /**\n * Whether to use localStorage for persistence\n * If false, uses in-memory storage\n * @default true\n */\n useLocalStorage?: boolean;\n}\n\n/**\n * Creates a service container with browser-specific implementations\n * of all required services for the upload client\n *\n * @param options - Configuration options for browser services\n * @returns ServiceContainer with browser implementations\n *\n * @example\n * ```typescript\n * import { createBrowserServices } from '@uploadista/browser/services';\n *\n * const services = createBrowserServices({\n * useLocalStorage: true,\n * connectionPooling: {\n * maxConnectionsPerHost: 6,\n * connectionTimeout: 30000,\n * }\n * });\n * ```\n */\nexport function createBrowserServices(\n options: BrowserServiceOptions = {},\n): ServiceContainer<BrowserUploadInput> {\n const { connectionPooling, useLocalStorage = true } = options;\n\n // Create storage service (localStorage or in-memory fallback)\n const storage = useLocalStorage\n ? createLocalStorageService()\n : // Placeholder for in-memory storage - use localStorage as default for browser\n createLocalStorageService();\n\n // Create other services\n const idGeneration = createBrowserIdGenerationService();\n const httpClient = createHttpClient(connectionPooling);\n const fileReader = createBrowserFileReaderService();\n const websocket = createBrowserWebSocketFactory();\n const abortController = createBrowserAbortControllerFactory();\n const checksumService = createChecksumService();\n const fingerprintService = createFingerprintService();\n\n return {\n platform: createBrowserPlatformService(),\n storage,\n idGeneration,\n httpClient,\n fileReader,\n websocket,\n abortController,\n checksumService,\n fingerprintService,\n };\n}\n","import {\n type ConnectionPoolConfig,\n createClientStorage,\n createLogger,\n createUploadistaClient as createUploadistaClientCore,\n type UploadistaClientOptions as UploadistaClientOptionsCore,\n} from \"@uploadista/client-core\";\nimport { createBrowserServices } from \"../services/create-browser-services\";\nimport type { BrowserUploadInput } from \"../types/upload-input\";\n\n/**\n * Configuration options for creating a browser-specific Uploadista client.\n *\n * This interface extends the core client options but omits browser-specific\n * services that are automatically provided by the browser environment.\n * These services include WebSocket factory, AbortController, ID generation,\n * storage, logging, platform detection, fingerprinting, HTTP client, file reader,\n * and checksum calculation.\n *\n * @example\n * ```typescript\n * import { createUploadistaClient } from '@uploadista/client-browser';\n *\n * const client = createUploadistaClient({\n * endpoint: 'https://api.uploadista.com/upload',\n * connectionPooling: {\n * maxConnectionsPerHost: 6,\n * enableHttp2: true\n * }\n * });\n * ```\n */\nexport interface UploadistaClientOptions\n extends Omit<\n UploadistaClientOptionsCore<BrowserUploadInput>,\n | \"webSocketFactory\"\n | \"abortControllerFactory\"\n | \"generateId\"\n | \"clientStorage\"\n | \"logger\"\n | \"platformService\"\n | \"fingerprintService\"\n | \"httpClient\"\n | \"fileReader\"\n | \"checksumService\"\n > {\n /**\n * Connection pooling configuration for the HTTP client.\n *\n * Controls how the browser manages HTTP connections for optimal performance.\n * The browser's native fetch API with keep-alive headers is used under the hood.\n *\n * @default\n * ```typescript\n * {\n * maxConnectionsPerHost: 6,\n * connectionTimeout: 30000,\n * keepAliveTimeout: 60000,\n * enableHttp2: true,\n * retryOnConnectionError: true\n * }\n * ```\n *\n * @example\n * ```typescript\n * connectionPooling: {\n * maxConnectionsPerHost: 10,\n * enableHttp2: true,\n * keepAliveTimeout: 120000\n * }\n * ```\n */\n connectionPooling?: ConnectionPoolConfig;\n}\n\n/**\n * Creates a browser-optimized Uploadista client for file uploads and flow processing.\n *\n * This factory function automatically configures all browser-specific services including:\n * - Fetch-based HTTP client with connection pooling\n * - Native WebSocket support for real-time progress\n * - localStorage for upload state persistence\n * - Web Crypto API for checksums and fingerprints\n * - File API for reading and chunking files\n * - Browser platform detection and capabilities\n *\n * The created client can handle File and Blob objects from file inputs, drag-and-drop,\n * or programmatically created content. It supports resumable uploads, progress tracking,\n * and flow-based file processing.\n *\n * @param options - Configuration options for the browser client\n * @returns A fully configured Uploadista client ready for browser use\n *\n * @example\n * ```typescript\n * import { createUploadistaClient } from '@uploadista/client-browser';\n *\n * // Basic usage\n * const client = createUploadistaClient({\n * endpoint: 'https://api.uploadista.com/upload'\n * });\n *\n * // With custom configuration\n * const client = createUploadistaClient({\n * endpoint: 'https://api.uploadista.com/upload',\n * connectionPooling: {\n * maxConnectionsPerHost: 6,\n * enableHttp2: true,\n * keepAliveTimeout: 60000\n * },\n * chunkSize: 5 * 1024 * 1024, // 5MB chunks\n * retryDelays: [1000, 3000, 5000],\n * allowedMetaFields: ['userId', 'projectId']\n * });\n *\n * // Upload a file\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * const file = fileInput.files[0];\n *\n * const upload = await client.upload(file, {\n * onProgress: (event) => {\n * console.log(`Progress: ${event.progress}%`);\n * }\n * });\n *\n * console.log('Upload complete:', upload.id);\n * ```\n *\n * @see {@link UploadistaClientOptions} for available configuration options\n * @see {@link BrowserUploadInput} for supported file input types\n */\nexport function createUploadistaClient(options: UploadistaClientOptions) {\n const services = createBrowserServices({\n connectionPooling: options.connectionPooling,\n });\n\n return createUploadistaClientCore<BrowserUploadInput>({\n ...options,\n webSocketFactory: services.websocket,\n abortControllerFactory: services.abortController,\n platformService: services.platform,\n httpClient: services.httpClient,\n fileReader: services.fileReader,\n generateId: services.idGeneration,\n fingerprintService: services.fingerprintService,\n checksumService: services.checksumService,\n logger: createLogger(false, () => {}),\n clientStorage: createClientStorage(services.storage),\n });\n}\n","/**\n * Framework Integration Utilities\n *\n * This module provides TypeScript utilities and helper types for building\n * framework-specific wrappers around the Uploadista client.\n *\n * @module framework-utils\n */\n\nimport type { FlowResult, UploadResult } from \"@uploadista/client-core\";\nimport type { FlowEvent } from \"@uploadista/core/flow\";\nimport type { UploadEvent, UploadFile } from \"@uploadista/core/types\";\n\n/**\n * Base upload state that framework wrappers should implement\n */\nexport interface BaseUploadState {\n status: \"idle\" | \"uploading\" | \"success\" | \"error\" | \"aborted\";\n progress: number;\n bytesUploaded: number;\n totalBytes: number;\n error?: Error;\n result?: UploadResult<UploadFile>;\n}\n\n/**\n * Base flow upload state\n */\nexport interface BaseFlowUploadState extends BaseUploadState {\n jobId?: string;\n flowStatus?: \"pending\" | \"processing\" | \"completed\" | \"failed\";\n flowResult?: FlowResult<unknown>;\n}\n\n/**\n * Progress callback signature\n */\nexport type ProgressCallback = (\n uploadId: string,\n bytesUploaded: number,\n totalBytes: number,\n) => void;\n\n/**\n * Complete callback signature\n */\nexport type CompleteCallback = (uploadId: string, result: UploadResult) => void;\n\n/**\n * Error callback signature\n */\nexport type ErrorCallback = (uploadId: string, error: Error) => void;\n\n/**\n * Abort callback signature\n */\nexport type AbortCallback = (uploadId: string) => void;\n\n/**\n * Event handler signature for framework wrappers\n */\nexport type EventHandler<T = unknown> = (event: T) => void;\n\n/**\n * WebSocket event handler signature\n */\nexport type WebSocketEventHandler = (event: UploadEvent | FlowEvent) => void;\n\n/**\n * Framework state updater function signature\n * @template T - The state type\n */\nexport type StateUpdater<T> = (updater: (prevState: T) => T) => void;\n\n/**\n * Cleanup function returned by setup functions\n */\nexport type CleanupFunction = () => void;\n\n/**\n * Upload item for multi-upload tracking\n */\nexport interface UploadItem {\n id: string;\n file: File;\n status: BaseUploadState[\"status\"];\n progress: number;\n bytesUploaded: number;\n totalBytes: number;\n error?: Error;\n result?: UploadResult;\n}\n\n/**\n * Multi-upload aggregate statistics\n */\nexport interface MultiUploadStats {\n totalFiles: number;\n completedFiles: number;\n failedFiles: number;\n totalBytes: number;\n uploadedBytes: number;\n totalProgress: number;\n allComplete: boolean;\n hasErrors: boolean;\n}\n\n/**\n * Drag and drop state\n */\nexport interface DragDropState {\n isDragging: boolean;\n isOver: boolean;\n files: File[];\n}\n\n/**\n * File validation result\n */\nexport interface FileValidationResult {\n valid: boolean;\n error?: string;\n}\n\n/**\n * File validation function signature\n */\nexport type FileValidator = (file: File) => FileValidationResult;\n\n/**\n * Utility: Calculate aggregate upload statistics\n */\nexport function calculateMultiUploadStats(\n uploads: UploadItem[],\n): MultiUploadStats {\n const totalFiles = uploads.length;\n const completedFiles = uploads.filter((u) => u.status === \"success\").length;\n const failedFiles = uploads.filter((u) => u.status === \"error\").length;\n const totalBytes = uploads.reduce((sum, u) => sum + u.totalBytes, 0);\n const uploadedBytes = uploads.reduce((sum, u) => sum + u.bytesUploaded, 0);\n const totalProgress = totalBytes > 0 ? (uploadedBytes / totalBytes) * 100 : 0;\n const allComplete = uploads.every((u) => u.status === \"success\");\n const hasErrors = uploads.some((u) => u.status === \"error\");\n\n return {\n totalFiles,\n completedFiles,\n failedFiles,\n totalBytes,\n uploadedBytes,\n totalProgress,\n allComplete,\n hasErrors,\n };\n}\n\n/**\n * Utility: Format file size for display\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;\n}\n\n/**\n * Utility: Format progress percentage\n */\nexport function formatProgress(progress: number): string {\n return `${Math.round(progress)}%`;\n}\n\n/**\n * Utility: Get file extension\n */\nexport function getFileExtension(filename: string): string {\n const lastDot = filename.lastIndexOf(\".\");\n return lastDot === -1 ? \"\" : filename.slice(lastDot + 1).toLowerCase();\n}\n\n/**\n * Utility: Check if file is an image\n */\nexport function isImageFile(file: File): boolean {\n return file.type.startsWith(\"image/\");\n}\n\n/**\n * Utility: Check if file is a video\n */\nexport function isVideoFile(file: File): boolean {\n return file.type.startsWith(\"video/\");\n}\n\n/**\n * Utility: Create file size validator\n */\nexport function createFileSizeValidator(maxSizeBytes: number): FileValidator {\n return (file: File): FileValidationResult => {\n if (file.size > maxSizeBytes) {\n return {\n valid: false,\n error: `File size exceeds maximum of ${formatFileSize(maxSizeBytes)}`,\n };\n }\n return { valid: true };\n };\n}\n\n/**\n * Utility: Create file type validator\n */\nexport function createFileTypeValidator(allowedTypes: string[]): FileValidator {\n return (file: File): FileValidationResult => {\n const fileType = file.type.toLowerCase();\n const fileExt = getFileExtension(file.name);\n\n const isAllowed = allowedTypes.some((type) => {\n if (type.startsWith(\".\")) {\n return type.slice(1) === fileExt;\n }\n if (type.includes(\"*\")) {\n const pattern = type.replace(\"*\", \"\");\n return fileType.startsWith(pattern);\n }\n return fileType === type;\n });\n\n if (!isAllowed) {\n return {\n valid: false,\n error: `File type not allowed. Allowed types: ${allowedTypes.join(\", \")}`,\n };\n }\n return { valid: true };\n };\n}\n\n/**\n * Utility: Compose multiple validators\n */\nexport function composeValidators(\n ...validators: FileValidator[]\n): FileValidator {\n return (file: File): FileValidationResult => {\n for (const validator of validators) {\n const result = validator(file);\n if (!result.valid) {\n return result;\n }\n }\n return { valid: true };\n };\n}\n\n/**\n * Utility: Generate unique upload ID\n */\nexport function generateUploadId(): string {\n return `upload-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Utility: Create delay promise for retry logic\n */\nexport function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Utility: Calculate exponential backoff delay\n */\nexport function calculateBackoff(\n attempt: number,\n baseDelay = 1000,\n maxDelay = 30000,\n): number {\n const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);\n // Add jitter to prevent thundering herd\n return delay + Math.random() * 1000;\n}\n\n/**\n * Utility: Create retry wrapper for upload function\n */\nexport function createRetryWrapper<T>(\n fn: () => Promise<T>,\n maxAttempts = 3,\n shouldRetry: (error: unknown) => boolean = () => true,\n): () => Promise<T> {\n return async () => {\n let lastError: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n if (attempt < maxAttempts - 1 && shouldRetry(error)) {\n const delayMs = calculateBackoff(attempt);\n await delay(delayMs);\n continue;\n }\n break;\n }\n }\n throw lastError;\n };\n}\n\n/**\n * Type guard: Check if error is network-related (should retry)\n */\nexport function isNetworkError(error: unknown): boolean {\n if (error instanceof Error) {\n return (\n error.message.includes(\"network\") ||\n error.message.includes(\"timeout\") ||\n error.message.includes(\"connection\") ||\n error.message.includes(\"ECONNREFUSED\") ||\n error.message.includes(\"ETIMEDOUT\")\n );\n }\n return false;\n}\n\n/**\n * Type guard: Check if error is abort-related (should not retry)\n */\nexport function isAbortError(error: unknown): boolean {\n if (error instanceof Error) {\n return error.name === \"AbortError\" || error.message.includes(\"abort\");\n }\n return false;\n}\n\n/**\n * Format upload speed in human-readable format\n */\nexport function formatSpeed(bytesPerSecond: number): string {\n if (bytesPerSecond === 0) return \"0 B/s\";\n const k = 1024;\n const sizes = [\"B/s\", \"KB/s\", \"MB/s\", \"GB/s\"];\n const i = Math.floor(Math.log(bytesPerSecond) / Math.log(k));\n return `${parseFloat((bytesPerSecond / k ** i).toFixed(1))} ${sizes[i]}`;\n}\n\n/**\n * Format duration in human-readable format\n */\nexport function formatDuration(milliseconds: number): string {\n if (milliseconds < 1000) {\n return `${Math.round(milliseconds)}ms`;\n }\n\n if (milliseconds < 60000) {\n return `${Math.round(milliseconds / 1000)}s`;\n }\n\n if (milliseconds < 3600000) {\n const minutes = Math.floor(milliseconds / 60000);\n const seconds = Math.round((milliseconds % 60000) / 1000);\n return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;\n }\n\n const hours = Math.floor(milliseconds / 3600000);\n const minutes = Math.round((milliseconds % 3600000) / 60000);\n return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;\n}\n\n/**\n * Validate file type against accepted types\n */\nexport function validateFileType(file: File, accept: string[]): boolean {\n if (!accept || accept.length === 0) return true;\n\n return accept.some((acceptType) => {\n if (acceptType.startsWith(\".\")) {\n // File extension check\n return file.name.toLowerCase().endsWith(acceptType.toLowerCase());\n }\n\n // MIME type check (supports wildcards like image/*)\n if (acceptType.endsWith(\"/*\")) {\n const baseType = acceptType.slice(0, -2);\n return file.type.startsWith(baseType);\n }\n\n return file.type === acceptType;\n });\n}\n\n/**\n * Check if a file is an audio file\n */\nexport function isAudioFile(file: File): boolean {\n return file.type.startsWith(\"audio/\");\n}\n\n/**\n * Check if a file is a document\n */\nexport function isDocumentFile(file: File): boolean {\n const documentTypes = [\n \"application/pdf\",\n \"application/msword\",\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n \"application/vnd.ms-excel\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n \"application/vnd.ms-powerpoint\",\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n \"text/plain\",\n \"text/csv\",\n \"application/rtf\",\n ];\n\n return documentTypes.includes(file.type);\n}\n\n/**\n * Create a preview URL for a file (if supported)\n */\nexport function createFilePreview(file: File): string | null {\n if (isImageFile(file) || isVideoFile(file) || isAudioFile(file)) {\n return URL.createObjectURL(file);\n }\n return null;\n}\n\n/**\n * Clean up a preview URL created with createFilePreview\n */\nexport function revokeFilePreview(previewUrl: string): void {\n URL.revokeObjectURL(previewUrl);\n}\n\n/**\n * Calculate progress percentage\n */\nexport function calculateProgress(current: number, total: number): number {\n if (total === 0) return 0;\n return Math.min(100, Math.max(0, Math.round((current / total) * 100)));\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uploadista/client-browser",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.3",
4
+ "version": "1.0.0",
5
5
  "description": "Browser client for Uploadista",
6
6
  "license": "MIT",
7
7
  "author": "Uploadista",
@@ -24,15 +24,15 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "js-base64": "3.7.8",
28
- "@uploadista/core": "1.0.0-beta.3",
29
- "@uploadista/client-core": "1.0.0-beta.3"
27
+ "js-base64": "3.8.0",
28
+ "@uploadista/core": "1.0.0",
29
+ "@uploadista/client-core": "1.0.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "happy-dom": "20.4.0",
33
- "tsdown": "0.20.3",
34
- "vitest": "4.0.18",
35
- "@uploadista/typescript-config": "1.0.0-beta.3"
33
+ "tsdown": "0.22.3",
34
+ "vitest": "4.1.10",
35
+ "@uploadista/typescript-config": "1.0.0"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsc --noEmit && tsdown",
@@ -8,15 +8,17 @@ import { afterEach, vi } from "vitest";
8
8
 
9
9
  // Mock crypto.subtle for tests that need it
10
10
  const mockSubtle = {
11
- digest: vi.fn().mockImplementation(async (_algorithm: string, data: ArrayBuffer) => {
12
- // Return a mock hash based on data length for testing
13
- const mockHash = new Uint8Array(32);
14
- const view = new Uint8Array(data);
15
- for (let i = 0; i < 32; i++) {
16
- mockHash[i] = (view[i % view.length] || 0) ^ (i * 7);
17
- }
18
- return mockHash.buffer;
19
- }),
11
+ digest: vi
12
+ .fn()
13
+ .mockImplementation(async (_algorithm: string, data: ArrayBuffer) => {
14
+ // Return a mock hash based on data length for testing
15
+ const mockHash = new Uint8Array(32);
16
+ const view = new Uint8Array(data);
17
+ for (let i = 0; i < 32; i++) {
18
+ mockHash[i] = (view[i % view.length] || 0) ^ (i * 7);
19
+ }
20
+ return mockHash.buffer;
21
+ }),
20
22
  };
21
23
 
22
24
  // Mock crypto.randomUUID
@@ -20,8 +20,8 @@ import {
20
20
  isImageFile,
21
21
  isNetworkError,
22
22
  isVideoFile,
23
- validateFileType,
24
23
  type UploadItem,
24
+ validateFileType,
25
25
  } from "./framework-utils";
26
26
 
27
27
  describe("formatFileSize", () => {
@@ -209,14 +209,18 @@ describe("validateFileType", () => {
209
209
  describe("createFileSizeValidator", () => {
210
210
  it("should pass files under the size limit", () => {
211
211
  const validator = createFileSizeValidator(1024 * 1024); // 1MB
212
- const file = new File(["x".repeat(100)], "small.txt", { type: "text/plain" });
212
+ const file = new File(["x".repeat(100)], "small.txt", {
213
+ type: "text/plain",
214
+ });
213
215
  const result = validator(file);
214
216
  expect(result.valid).toBe(true);
215
217
  });
216
218
 
217
219
  it("should fail files over the size limit", () => {
218
220
  const validator = createFileSizeValidator(100);
219
- const file = new File(["x".repeat(200)], "large.txt", { type: "text/plain" });
221
+ const file = new File(["x".repeat(200)], "large.txt", {
222
+ type: "text/plain",
223
+ });
220
224
  const result = validator(file);
221
225
  expect(result.valid).toBe(false);
222
226
  expect(result.error).toContain("exceeds maximum");
@@ -62,7 +62,7 @@ describe("createHttpClient", () => {
62
62
  headers: expect.objectContaining({
63
63
  Connection: "keep-alive",
64
64
  }),
65
- })
65
+ }),
66
66
  );
67
67
  expect(response.status).toBe(200);
68
68
  expect(response.ok).toBe(true);
@@ -93,7 +93,7 @@ describe("createHttpClient", () => {
93
93
  expect.objectContaining({
94
94
  method: "POST",
95
95
  body,
96
- })
96
+ }),
97
97
  );
98
98
  expect(response.status).toBe(201);
99
99
  });
@@ -123,7 +123,7 @@ describe("createHttpClient", () => {
123
123
  clearTimeout(timeoutId);
124
124
  reject(new DOMException("Aborted", "AbortError"));
125
125
  });
126
- })
126
+ }),
127
127
  );
128
128
 
129
129
  const client = createHttpClient();
@@ -131,7 +131,7 @@ describe("createHttpClient", () => {
131
131
  await expect(
132
132
  client.request("https://api.example.com/slow", {
133
133
  timeout: 50,
134
- })
134
+ }),
135
135
  ).rejects.toThrow();
136
136
 
137
137
  // Restore fake timers for other tests
@@ -161,7 +161,7 @@ describe("createHttpClient", () => {
161
161
  "https://api.example.com/data",
162
162
  expect.objectContaining({
163
163
  signal: expect.any(AbortSignal),
164
- })
164
+ }),
165
165
  );
166
166
  });
167
167
 
@@ -184,7 +184,7 @@ describe("createHttpClient", () => {
184
184
  "https://api.example.com/data",
185
185
  expect.objectContaining({
186
186
  credentials: "include",
187
- })
187
+ }),
188
188
  );
189
189
  });
190
190
 
@@ -194,7 +194,7 @@ describe("createHttpClient", () => {
194
194
  const client = createHttpClient();
195
195
 
196
196
  await expect(
197
- client.request("https://api.example.com/data")
197
+ client.request("https://api.example.com/data"),
198
198
  ).rejects.toThrow("Network error");
199
199
  });
200
200
  });
@@ -312,13 +312,13 @@ describe("createHttpClient", () => {
312
312
  "https://api1.example.com",
313
313
  expect.objectContaining({
314
314
  method: "HEAD",
315
- })
315
+ }),
316
316
  );
317
317
  expect(mockFetch).toHaveBeenCalledWith(
318
318
  "https://api2.example.com",
319
319
  expect.objectContaining({
320
320
  method: "HEAD",
321
- })
321
+ }),
322
322
  );
323
323
  });
324
324
 
@@ -329,7 +329,7 @@ describe("createHttpClient", () => {
329
329
 
330
330
  // Should not throw
331
331
  await expect(
332
- client.warmupConnections(["https://api.example.com"])
332
+ client.warmupConnections(["https://api.example.com"]),
333
333
  ).resolves.not.toThrow();
334
334
  });
335
335
 
@@ -1,5 +1,8 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { createBrowserServices, type BrowserServiceOptions } from "./create-browser-services";
2
+ import {
3
+ type BrowserServiceOptions,
4
+ createBrowserServices,
5
+ } from "./create-browser-services";
3
6
 
4
7
  describe("createBrowserServices", () => {
5
8
  it("should create a service container with default options", () => {
@@ -186,7 +189,7 @@ describe("createBrowserServices", () => {
186
189
 
187
190
  const fingerprint = await services.fingerprintService.computeFingerprint(
188
191
  file,
189
- "https://api.example.com"
192
+ "https://api.example.com",
190
193
  );
191
194
 
192
195
  expect(fingerprint).toBeDefined();
@@ -216,7 +219,7 @@ describe("createBrowserServices", () => {
216
219
  // 4. Compute fingerprint for deduplication
217
220
  const fingerprint = await services.fingerprintService.computeFingerprint(
218
221
  file,
219
- "https://api.example.com"
222
+ "https://api.example.com",
220
223
  );
221
224
  expect(fingerprint).toHaveLength(64);
222
225
 
@@ -231,7 +234,7 @@ describe("createBrowserServices", () => {
231
234
  id: uploadId,
232
235
  fingerprint,
233
236
  status: "pending",
234
- })
237
+ }),
235
238
  );
236
239
 
237
240
  // 7. Retrieve stored state
@@ -44,7 +44,7 @@ describe("createBrowserFileReaderService", () => {
44
44
  const service = createBrowserFileReaderService();
45
45
 
46
46
  await expect(
47
- service.openFile("invalid" as unknown as Blob, 1024)
47
+ service.openFile("invalid" as unknown as Blob, 1024),
48
48
  ).rejects.toThrow("source object may only be an instance of File, Blob");
49
49
  });
50
50
  });
@@ -110,7 +110,9 @@ describe("createBrowserFileReaderService", () => {
110
110
  it("should handle binary data", async () => {
111
111
  const service = createBrowserFileReaderService();
112
112
  const binaryData = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
113
- const blob = new Blob([binaryData], { type: "application/octet-stream" });
113
+ const blob = new Blob([binaryData], {
114
+ type: "application/octet-stream",
115
+ });
114
116
 
115
117
  const source = await service.openFile(blob, 1024);
116
118
  const result = await source.slice(0, 5);
@@ -156,7 +158,7 @@ describe("createBrowserFileReaderService", () => {
156
158
 
157
159
  // Verify combined content
158
160
  const combined = new Uint8Array(
159
- chunks.reduce((acc, chunk) => acc + chunk.length, 0)
161
+ chunks.reduce((acc, chunk) => acc + chunk.length, 0),
160
162
  );
161
163
  let pos = 0;
162
164
  for (const chunk of chunks) {
@@ -18,7 +18,7 @@ describe("createFingerprintService", () => {
18
18
 
19
19
  const fingerprint = await service.computeFingerprint(
20
20
  file,
21
- "https://api.example.com"
21
+ "https://api.example.com",
22
22
  );
23
23
 
24
24
  // Should return a 64-character hex string (SHA-256)
@@ -32,7 +32,7 @@ describe("createFingerprintService", () => {
32
32
 
33
33
  const fingerprint = await service.computeFingerprint(
34
34
  blob,
35
- "https://api.example.com"
35
+ "https://api.example.com",
36
36
  );
37
37
 
38
38
  expect(fingerprint).toHaveLength(64);
@@ -47,11 +47,11 @@ describe("createFingerprintService", () => {
47
47
 
48
48
  const fingerprint1 = await service.computeFingerprint(
49
49
  file1,
50
- "https://api.example.com"
50
+ "https://api.example.com",
51
51
  );
52
52
  const fingerprint2 = await service.computeFingerprint(
53
53
  file2,
54
- "https://api.example.com"
54
+ "https://api.example.com",
55
55
  );
56
56
 
57
57
  expect(fingerprint1).toBe(fingerprint2);
@@ -59,16 +59,20 @@ describe("createFingerprintService", () => {
59
59
 
60
60
  it("should return different fingerprint for different files", async () => {
61
61
  const service = createFingerprintService();
62
- const file1 = new File(["Content A"], "file1.txt", { type: "text/plain" });
63
- const file2 = new File(["Content B"], "file2.txt", { type: "text/plain" });
62
+ const file1 = new File(["Content A"], "file1.txt", {
63
+ type: "text/plain",
64
+ });
65
+ const file2 = new File(["Content B"], "file2.txt", {
66
+ type: "text/plain",
67
+ });
64
68
 
65
69
  const fingerprint1 = await service.computeFingerprint(
66
70
  file1,
67
- "https://api.example.com"
71
+ "https://api.example.com",
68
72
  );
69
73
  const fingerprint2 = await service.computeFingerprint(
70
74
  file2,
71
- "https://api.example.com"
75
+ "https://api.example.com",
72
76
  );
73
77
 
74
78
  expect(fingerprint1).not.toBe(fingerprint2);
@@ -82,11 +86,11 @@ describe("createFingerprintService", () => {
82
86
 
83
87
  const fingerprint1 = await service.computeFingerprint(
84
88
  file,
85
- "https://api1.example.com"
89
+ "https://api1.example.com",
86
90
  );
87
91
  const fingerprint2 = await service.computeFingerprint(
88
92
  file,
89
- "https://api2.example.com"
93
+ "https://api2.example.com",
90
94
  );
91
95
 
92
96
  // The current implementation ignores the endpoint
@@ -99,7 +103,7 @@ describe("createFingerprintService", () => {
99
103
 
100
104
  const fingerprint = await service.computeFingerprint(
101
105
  file,
102
- "https://api.example.com"
106
+ "https://api.example.com",
103
107
  );
104
108
 
105
109
  expect(fingerprint).toHaveLength(64);
@@ -113,7 +117,7 @@ describe("createFingerprintService", () => {
113
117
 
114
118
  const fingerprint = await service.computeFingerprint(
115
119
  blob,
116
- "https://api.example.com"
120
+ "https://api.example.com",
117
121
  );
118
122
 
119
123
  expect(fingerprint).toHaveLength(64);
@@ -16,7 +16,7 @@ describe("createBrowserIdGenerationService", () => {
16
16
 
17
17
  // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
18
18
  expect(id).toMatch(
19
- /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
19
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
20
20
  );
21
21
  });
22
22
 
@@ -131,7 +131,11 @@ class BrowserWebSocket implements WebSocketLike {
131
131
  * ```
132
132
  */
133
133
  send(data: string | Uint8Array): void {
134
- this.native.send(data);
134
+ // `WebSocketLike.send` accepts a plain `Uint8Array` (backed by `ArrayBufferLike`),
135
+ // but the DOM `WebSocket.send` only accepts an `ArrayBuffer`-backed `BufferSource`
136
+ // (it rejects the `SharedArrayBuffer` case at the type level). In the browser the
137
+ // views we send are always `ArrayBuffer`-backed, so this assertion is safe.
138
+ this.native.send(data as string | Uint8Array<ArrayBuffer>);
135
139
  }
136
140
 
137
141
  /**
@@ -72,7 +72,7 @@ describe("computeblobSha256", () => {
72
72
  const blob = new Blob(["test"], { type: "text/plain" });
73
73
 
74
74
  await expect(computeblobSha256(blob)).rejects.toThrow(
75
- "Failed to compute file checksum"
75
+ "Failed to compute file checksum",
76
76
  );
77
77
 
78
78
  // Restore original
package/tsconfig.json CHANGED
@@ -2,7 +2,6 @@
2
2
  "extends": "@uploadista/typescript-config/base.json",
3
3
  "compilerOptions": {
4
4
  "lib": ["ES2020", "DOM"],
5
- "baseUrl": "./",
6
5
  "paths": {
7
6
  "@/*": ["./src/*"]
8
7
  },
package/tsdown.config.ts CHANGED
@@ -8,5 +8,7 @@ export default defineConfig({
8
8
  format: ["esm"],
9
9
  dts: true,
10
10
  outDir: "dist",
11
- external: ["react", "react-dom"],
11
+ deps: {
12
+ neverBundle: ["react", "react-dom"],
13
+ },
12
14
  });