@uploadista/client-browser 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-check.log +130 -0
  3. package/AUTO_CAPABILITIES.md +98 -0
  4. package/FRAMEWORK_INTEGRATION.md +407 -0
  5. package/LICENSE +21 -0
  6. package/README.md +795 -0
  7. package/SMART_CHUNKING.md +140 -0
  8. package/dist/client/create-uploadista-client.d.ts +182 -0
  9. package/dist/client/create-uploadista-client.d.ts.map +1 -0
  10. package/dist/client/create-uploadista-client.js +76 -0
  11. package/dist/client/index.d.ts +2 -0
  12. package/dist/client/index.d.ts.map +1 -0
  13. package/dist/client/index.js +1 -0
  14. package/dist/framework-utils.d.ts +201 -0
  15. package/dist/framework-utils.d.ts.map +1 -0
  16. package/dist/framework-utils.js +282 -0
  17. package/dist/http-client.d.ts +44 -0
  18. package/dist/http-client.d.ts.map +1 -0
  19. package/dist/http-client.js +489 -0
  20. package/dist/index.d.ts +8 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +7 -0
  23. package/dist/services/abort-controller-factory.d.ts +30 -0
  24. package/dist/services/abort-controller-factory.d.ts.map +1 -0
  25. package/dist/services/abort-controller-factory.js +98 -0
  26. package/dist/services/checksum-service.d.ts +30 -0
  27. package/dist/services/checksum-service.d.ts.map +1 -0
  28. package/dist/services/checksum-service.js +44 -0
  29. package/dist/services/create-browser-services.d.ts +36 -0
  30. package/dist/services/create-browser-services.d.ts.map +1 -0
  31. package/dist/services/create-browser-services.js +56 -0
  32. package/dist/services/file-reader.d.ts +91 -0
  33. package/dist/services/file-reader.d.ts.map +1 -0
  34. package/dist/services/file-reader.js +251 -0
  35. package/dist/services/fingerprint-service.d.ts +41 -0
  36. package/dist/services/fingerprint-service.d.ts.map +1 -0
  37. package/dist/services/fingerprint-service.js +64 -0
  38. package/dist/services/id-generation/id-generation.d.ts +40 -0
  39. package/dist/services/id-generation/id-generation.d.ts.map +1 -0
  40. package/dist/services/id-generation/id-generation.js +58 -0
  41. package/dist/services/platform-service.d.ts +38 -0
  42. package/dist/services/platform-service.d.ts.map +1 -0
  43. package/dist/services/platform-service.js +221 -0
  44. package/dist/services/storage/local-storage-service.d.ts +55 -0
  45. package/dist/services/storage/local-storage-service.d.ts.map +1 -0
  46. package/dist/services/storage/local-storage-service.js +178 -0
  47. package/dist/services/storage/session-storage-service.d.ts +55 -0
  48. package/dist/services/storage/session-storage-service.d.ts.map +1 -0
  49. package/dist/services/storage/session-storage-service.js +179 -0
  50. package/dist/services/websocket-factory.d.ts +46 -0
  51. package/dist/services/websocket-factory.d.ts.map +1 -0
  52. package/dist/services/websocket-factory.js +196 -0
  53. package/dist/types/index.d.ts +2 -0
  54. package/dist/types/index.d.ts.map +1 -0
  55. package/dist/types/index.js +1 -0
  56. package/dist/types/upload-input.d.ts +26 -0
  57. package/dist/types/upload-input.d.ts.map +1 -0
  58. package/dist/types/upload-input.js +1 -0
  59. package/dist/utils/hash-util.d.ts +60 -0
  60. package/dist/utils/hash-util.d.ts.map +1 -0
  61. package/dist/utils/hash-util.js +75 -0
  62. package/package.json +32 -0
  63. package/src/client/create-uploadista-client.ts +150 -0
  64. package/src/client/index.ts +1 -0
  65. package/src/framework-utils.ts +446 -0
  66. package/src/http-client.ts +546 -0
  67. package/src/index.ts +8 -0
  68. package/src/services/abort-controller-factory.ts +108 -0
  69. package/src/services/checksum-service.ts +46 -0
  70. package/src/services/create-browser-services.ts +81 -0
  71. package/src/services/file-reader.ts +344 -0
  72. package/src/services/fingerprint-service.ts +67 -0
  73. package/src/services/id-generation/id-generation.ts +60 -0
  74. package/src/services/platform-service.ts +231 -0
  75. package/src/services/storage/local-storage-service.ts +187 -0
  76. package/src/services/storage/session-storage-service.ts +188 -0
  77. package/src/services/websocket-factory.ts +212 -0
  78. package/src/types/index.ts +1 -0
  79. package/src/types/upload-input.ts +25 -0
  80. package/src/utils/hash-util.ts +79 -0
  81. package/tsconfig.json +22 -0
  82. package/tsconfig.tsbuildinfo +1 -0
  83. package/vitest.config.ts +15 -0
@@ -0,0 +1,81 @@
1
+ import type {
2
+ ConnectionPoolConfig,
3
+ ServiceContainer,
4
+ } from "@uploadista/client-core";
5
+ import { createHttpClient } from "../http-client";
6
+ import type { BrowserUploadInput } from "../types/upload-input";
7
+ import { createBrowserAbortControllerFactory } from "./abort-controller-factory";
8
+ import { createChecksumService } from "./checksum-service";
9
+ import { createBrowserFileReaderService } from "./file-reader";
10
+ import { createFingerprintService } from "./fingerprint-service";
11
+ import { createBrowserIdGenerationService } from "./id-generation/id-generation";
12
+ import { createBrowserPlatformService } from "./platform-service";
13
+ import { createLocalStorageService } from "./storage/local-storage-service";
14
+ import { createBrowserWebSocketFactory } from "./websocket-factory";
15
+
16
+ export interface BrowserServiceOptions {
17
+ /**
18
+ * HTTP client configuration for connection pooling
19
+ */
20
+ connectionPooling?: ConnectionPoolConfig;
21
+
22
+ /**
23
+ * Whether to use localStorage for persistence
24
+ * If false, uses in-memory storage
25
+ * @default true
26
+ */
27
+ useLocalStorage?: boolean;
28
+ }
29
+
30
+ /**
31
+ * Creates a service container with browser-specific implementations
32
+ * of all required services for the upload client
33
+ *
34
+ * @param options - Configuration options for browser services
35
+ * @returns ServiceContainer with browser implementations
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * import { createBrowserServices } from '@uploadista/browser/services';
40
+ *
41
+ * const services = createBrowserServices({
42
+ * useLocalStorage: true,
43
+ * connectionPooling: {
44
+ * maxConnectionsPerHost: 6,
45
+ * connectionTimeout: 30000,
46
+ * }
47
+ * });
48
+ * ```
49
+ */
50
+ export function createBrowserServices(
51
+ options: BrowserServiceOptions = {},
52
+ ): ServiceContainer<BrowserUploadInput> {
53
+ const { connectionPooling, useLocalStorage = true } = options;
54
+
55
+ // Create storage service (localStorage or in-memory fallback)
56
+ const storage = useLocalStorage
57
+ ? createLocalStorageService()
58
+ : // Placeholder for in-memory storage - use localStorage as default for browser
59
+ createLocalStorageService();
60
+
61
+ // Create other services
62
+ const idGeneration = createBrowserIdGenerationService();
63
+ const httpClient = createHttpClient(connectionPooling);
64
+ const fileReader = createBrowserFileReaderService();
65
+ const websocket = createBrowserWebSocketFactory();
66
+ const abortController = createBrowserAbortControllerFactory();
67
+ const checksumService = createChecksumService();
68
+ const fingerprintService = createFingerprintService();
69
+
70
+ return {
71
+ platform: createBrowserPlatformService(),
72
+ storage,
73
+ idGeneration,
74
+ httpClient,
75
+ fileReader,
76
+ websocket,
77
+ abortController,
78
+ checksumService,
79
+ fingerprintService,
80
+ };
81
+ }
@@ -0,0 +1,344 @@
1
+ import type {
2
+ FileSource as CoreFileSource,
3
+ FileReaderService,
4
+ SliceResult,
5
+ } from "@uploadista/client-core";
6
+ import type { BrowserUploadInput } from "../types/upload-input";
7
+
8
+ /**
9
+ * Browser-specific file reader interface for opening and reading file data.
10
+ *
11
+ * Provides methods to open File/Blob objects and create FileSource instances
12
+ * that support chunked reading for upload operations.
13
+ */
14
+ export type FileReader = {
15
+ /**
16
+ * Opens a file and prepares it for chunked reading.
17
+ *
18
+ * @param input - The File or Blob to open
19
+ * @param chunkSize - Size of chunks to read (in bytes)
20
+ * @returns Promise resolving to a FileSource for reading the file
21
+ */
22
+ openFile: (
23
+ input: BrowserUploadInput,
24
+ chunkSize: number,
25
+ ) => Promise<FileSource>;
26
+ };
27
+
28
+ /**
29
+ * Represents an opened file that can be read in chunks.
30
+ *
31
+ * This interface provides the core functionality for reading file data
32
+ * in a streaming fashion, which is essential for uploading large files
33
+ * without loading them entirely into memory.
34
+ */
35
+ export type FileSource = {
36
+ /** The original input File or Blob */
37
+ input: BrowserUploadInput;
38
+
39
+ /** Total size of the file in bytes, or null if unknown */
40
+ size: number | null;
41
+
42
+ /**
43
+ * Reads a slice of data from the file.
44
+ *
45
+ * @param start - Starting byte offset
46
+ * @param end - Ending byte offset (exclusive)
47
+ * @returns Promise resolving to the slice result with data and metadata
48
+ */
49
+ slice: (start: number, end: number) => Promise<SliceResult>;
50
+
51
+ /**
52
+ * Closes the file and releases any resources.
53
+ *
54
+ * For browser File/Blob objects, this is typically a no-op as there are
55
+ * no resources to release, but is included for interface compatibility.
56
+ */
57
+ close: () => void;
58
+ };
59
+
60
+ /**
61
+ * Re-export SliceResult from core for convenience.
62
+ */
63
+ export type { SliceResult };
64
+
65
+ // Commented-out stream support for future implementation
66
+ // function isWebStream(
67
+ // input: BrowserUploadInput,
68
+ // ): input is Pick<ReadableStreamDefaultReader, "read"> {
69
+ // return (
70
+ // typeof input === "object" &&
71
+ // input !== null &&
72
+ // "read" in input &&
73
+ // typeof input.read === "function"
74
+ // );
75
+ // }
76
+
77
+ /**
78
+ * Creates a FileSource from a Blob or File object.
79
+ *
80
+ * This function wraps a Blob (or File, which extends Blob) and provides
81
+ * a slice() method for reading specific byte ranges. It uses the Blob.slice()
82
+ * and arrayBuffer() APIs to efficiently read file chunks without loading
83
+ * the entire file into memory.
84
+ *
85
+ * @param blob - The Blob or File to wrap
86
+ * @returns A FileSource that can read chunks from the blob
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const file = new File(['content'], 'test.txt');
91
+ * const source = blobFileSource(file);
92
+ *
93
+ * // Read first 1024 bytes
94
+ * const chunk = await source.slice(0, 1024);
95
+ * console.log('Chunk size:', chunk.size);
96
+ * console.log('Is complete:', chunk.done);
97
+ * ```
98
+ */
99
+ function blobFileSource(blob: Blob): FileSource {
100
+ return {
101
+ input: blob,
102
+ size: blob.size,
103
+ slice: async (start: number, end: number) => {
104
+ const value = blob.slice(start, end);
105
+ const size = value.size;
106
+ const done = end >= blob.size;
107
+
108
+ return { value: new Uint8Array(await value.arrayBuffer()), size, done };
109
+ },
110
+ close: () => {},
111
+ };
112
+ }
113
+
114
+ // Commented-out stream support code for future implementation
115
+ // export type StreamReader = Pick<ReadableStreamDefaultReader, "read">;
116
+
117
+ // type BlobOrArray = Blob | Uint8Array;
118
+
119
+ // function len(blobOrArray: BlobOrArray | undefined): number {
120
+ // if (blobOrArray === undefined) return 0;
121
+ // if (blobOrArray instanceof Blob) return blobOrArray.size;
122
+ // return blobOrArray.length;
123
+ // }
124
+
125
+ // /*
126
+ // Typed arrays and blobs don't have a concat method.
127
+ // This function helps StreamSource accumulate data to reach chunkSize.
128
+ // */
129
+ // function concat<T extends BlobOrArray | undefined>(a: T, b: T): T {
130
+ // if (a instanceof Blob && b instanceof Blob) {
131
+ // return new Blob([a, b], { type: a.type }) as T;
132
+ // }
133
+ // if (a instanceof Uint8Array && b instanceof Uint8Array) {
134
+ // const c = new Uint8Array(a.length + b.length);
135
+ // c.set(a);
136
+ // c.set(b, a.length);
137
+ // return c as T;
138
+ // }
139
+ // throw new Error("Unknown data type");
140
+ // }
141
+
142
+ // function removeDataBeforeStart(
143
+ // buffer: Uint8Array | undefined,
144
+ // bufferOffset: number,
145
+ // start: number,
146
+ // ) {
147
+ // if (buffer === undefined) {
148
+ // throw new Error("cannot removeDataBeforeStart because buffer is unset");
149
+ // }
150
+ // if (start > bufferOffset) {
151
+ // return buffer.slice(start - bufferOffset);
152
+ // }
153
+ // return buffer;
154
+ // }
155
+
156
+ // function getDataFromBuffer(
157
+ // buffer: Uint8Array | undefined,
158
+ // bufferOffset: number,
159
+ // start: number,
160
+ // end: number,
161
+ // done: boolean,
162
+ // ) {
163
+ // if (buffer === undefined) {
164
+ // throw new Error("cannot getDataFromBuffer because buffer is unset");
165
+ // }
166
+
167
+ // // Remove data from buffer before `start`.
168
+ // // Data might be reread from the buffer if an upload fails, so we can only
169
+ // // safely delete data when it comes *before* what is currently being read.
170
+ // const safeBuffer = removeDataBeforeStart(buffer, bufferOffset, start);
171
+
172
+ // // If the buffer is empty after removing old data, all data has been read.
173
+ // const hasAllDataBeenRead = len(safeBuffer) === 0;
174
+ // if (done && hasAllDataBeenRead) {
175
+ // return null;
176
+ // }
177
+
178
+ // // We already removed data before `start`, so we just return the first
179
+ // // chunk from the buffer.
180
+ // return safeBuffer.slice(0, end - start);
181
+ // }
182
+
183
+ // async function readUntilEnoughDataOrDone(
184
+ // reader: StreamReader,
185
+ // bufferOffset: number,
186
+ // buffer: Uint8Array | undefined,
187
+ // start: number,
188
+ // end: number,
189
+ // done: boolean,
190
+ // ): Promise<SliceResult> {
191
+ // const hasEnoughData = end <= bufferOffset + len(buffer);
192
+ // if (done || hasEnoughData) {
193
+ // const value = getDataFromBuffer(buffer, bufferOffset, start, end, done);
194
+ // if (value === null) {
195
+ // return { value: null, size: null, done: true };
196
+ // }
197
+
198
+ // const size = value instanceof Blob ? value.size : value.length;
199
+ // return Promise.resolve({ value, size, done });
200
+ // }
201
+
202
+ // let newDone: boolean = done;
203
+ // let newBuffer: Uint8Array | undefined;
204
+ // const { value, done: isDone } = await reader.read();
205
+
206
+ // if (isDone) {
207
+ // newDone = true;
208
+ // } else if (buffer === undefined) {
209
+ // newBuffer = value;
210
+ // } else {
211
+ // newBuffer = concat(buffer, value);
212
+ // }
213
+
214
+ // return readUntilEnoughDataOrDone(
215
+ // reader,
216
+ // bufferOffset,
217
+ // newBuffer,
218
+ // start,
219
+ // end,
220
+ // newDone,
221
+ // );
222
+ // }
223
+
224
+ // function streamFileSource(reader: StreamReader): FileSource {
225
+ // const bufferOffset = 0;
226
+ // const buffer = new Uint8Array(0);
227
+
228
+ // return {
229
+ // input: reader,
230
+ // size: null,
231
+ // slice: (start: number, end: number) => {
232
+ // if (start < bufferOffset) {
233
+ // return Promise.reject(
234
+ // new Error("Requested data is before the reader's current offset"),
235
+ // );
236
+ // }
237
+
238
+ // return readUntilEnoughDataOrDone(
239
+ // reader,
240
+ // bufferOffset,
241
+ // buffer,
242
+ // start,
243
+ // end,
244
+ // false,
245
+ // );
246
+ // },
247
+ // close: () => {
248
+ // // TODO: We should not call cancel
249
+ // // @ts-expect-error cancel is not defined since we only pick `read`
250
+ // if (reader.cancel) {
251
+ // // @ts-expect-error cancel is not defined since we only pick `read`
252
+ // reader.cancel();
253
+ // }
254
+ // },
255
+ // };
256
+ // }
257
+
258
+ /**
259
+ * Creates a browser-specific file reader service for the Uploadista client.
260
+ *
261
+ * This service provides the ability to open and read File/Blob objects from
262
+ * the browser's File API. It converts browser-native file objects into a
263
+ * format that can be chunked and uploaded efficiently.
264
+ *
265
+ * The service currently supports:
266
+ * - File objects from `<input type="file">` elements
267
+ * - File objects from drag-and-drop events
268
+ * - Blob objects created programmatically
269
+ *
270
+ * Future support may include:
271
+ * - ReadableStream for streaming data
272
+ *
273
+ * @returns A FileReaderService configured for browser environments
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * import { createBrowserFileReaderService } from '@uploadista/client-browser';
278
+ *
279
+ * const fileReader = createBrowserFileReaderService();
280
+ *
281
+ * // Open a file from input element
282
+ * const input = document.querySelector('input[type="file"]');
283
+ * const file = input.files[0];
284
+ * const source = await fileReader.openFile(file, 5 * 1024 * 1024); // 5MB chunks
285
+ *
286
+ * console.log('File name:', source.name);
287
+ * console.log('File size:', source.size);
288
+ * console.log('File type:', source.type);
289
+ *
290
+ * // Read first chunk
291
+ * const chunk = await source.slice(0, 5 * 1024 * 1024);
292
+ * console.log('Read', chunk.size, 'bytes');
293
+ * ```
294
+ *
295
+ * @throws {Error} When the input is not a File or Blob
296
+ */
297
+ export function createBrowserFileReaderService(): FileReaderService<BrowserUploadInput> {
298
+ return {
299
+ openFile: async (
300
+ input: BrowserUploadInput,
301
+ _chunkSize: number,
302
+ ): Promise<CoreFileSource> => {
303
+ // File is a subtype of Blob, so we can check for Blob here.
304
+ if (input instanceof Blob) {
305
+ const source = blobFileSource(input);
306
+ return {
307
+ input: source.input,
308
+ size: source.size,
309
+ slice: source.slice,
310
+ close: source.close,
311
+ name: source.input instanceof File ? source.input.name : null,
312
+ type: source.input instanceof File ? source.input.type : null,
313
+ lastModified:
314
+ source.input instanceof File ? source.input.lastModified : null,
315
+ };
316
+ }
317
+
318
+ // Future support for streams (commented out):
319
+ // if (isWebStream(input)) {
320
+ // if (!Number.isFinite(chunkSize)) {
321
+ // throw new TypeError(
322
+ // "cannot create source for stream without a finite value for the `chunkSize` option",
323
+ // );
324
+ // }
325
+
326
+ // const source = streamFileSource(input);
327
+ // return {
328
+ // input: source.input,
329
+ // size: source.size,
330
+ // slice: source.slice,
331
+ // close: source.close,
332
+ // name: source.input instanceof File ? source.input.name : null,
333
+ // type: source.input instanceof File ? source.input.type : null,
334
+ // lastModified:
335
+ // source.input instanceof File ? source.input.lastModified : null,
336
+ // };
337
+ // }
338
+
339
+ throw new Error(
340
+ "source object may only be an instance of File, Blob in this environment",
341
+ );
342
+ },
343
+ };
344
+ }
@@ -0,0 +1,67 @@
1
+ import type { FingerprintService } from "@uploadista/client-core";
2
+ import type { BrowserUploadInput } from "../types/upload-input";
3
+ import { computeblobSha256 } from "../utils/hash-util";
4
+
5
+ /**
6
+ * Creates a fingerprint service for generating unique file identifiers.
7
+ *
8
+ * This service computes SHA-256 fingerprints of files to uniquely identify them.
9
+ * Fingerprints are used for:
10
+ * - Detecting duplicate uploads
11
+ * - Resuming interrupted uploads
12
+ * - Verifying file integrity
13
+ * - Implementing deduplication strategies
14
+ *
15
+ * The fingerprint is computed using the Web Crypto API and represents the
16
+ * SHA-256 hash of the entire file content. Two identical files will always
17
+ * produce the same fingerprint.
18
+ *
19
+ * @returns A FingerprintService that computes SHA-256 fingerprints for browser files
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import { createFingerprintService } from '@uploadista/client-browser';
24
+ *
25
+ * const fingerprintService = createFingerprintService();
26
+ *
27
+ * // Generate fingerprint for a file
28
+ * const fileInput = document.querySelector('input[type="file"]');
29
+ * const file = fileInput.files[0];
30
+ *
31
+ * const fingerprint = await fingerprintService.computeFingerprint(
32
+ * file,
33
+ * 'https://api.example.com/upload'
34
+ * );
35
+ *
36
+ * console.log('File fingerprint:', fingerprint);
37
+ * // Can be used to check if file was previously uploaded
38
+ * ```
39
+ *
40
+ * @see {@link computeblobSha256} for the underlying hash implementation
41
+ */
42
+ export function createFingerprintService(): FingerprintService<BrowserUploadInput> {
43
+ return {
44
+ /**
45
+ * Computes a unique fingerprint for a file.
46
+ *
47
+ * Calculates the SHA-256 hash of the entire file content. The endpoint
48
+ * parameter is currently unused but included for interface compatibility
49
+ * with other platform implementations that might use it for salt or
50
+ * endpoint-specific fingerprinting.
51
+ *
52
+ * @param file - The File or Blob to fingerprint
53
+ * @param _endpoint - Upload endpoint (currently unused, reserved for future use)
54
+ * @returns Promise resolving to the hex-encoded SHA-256 fingerprint
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const file = new File(['content'], 'example.txt');
59
+ * const fingerprint = await service.computeFingerprint(file, 'https://api.example.com');
60
+ * // fingerprint: "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73"
61
+ * ```
62
+ */
63
+ computeFingerprint: async (file, _endpoint) => {
64
+ return computeblobSha256(file);
65
+ },
66
+ };
67
+ }
@@ -0,0 +1,60 @@
1
+ import type { IdGenerationService } from "@uploadista/client-core";
2
+
3
+ /**
4
+ * Creates a browser-specific ID generation service using the Web Crypto API.
5
+ *
6
+ * This service generates cryptographically secure random UUIDs (v4) using the
7
+ * browser's native `crypto.randomUUID()` method. These UUIDs are used throughout
8
+ * the Uploadista client for:
9
+ * - Upload session identifiers
10
+ * - Chunk identifiers
11
+ * - Request correlation IDs
12
+ * - Internal tracking
13
+ *
14
+ * The generated UUIDs conform to RFC 4122 version 4 (random) and provide
15
+ * strong uniqueness guarantees suitable for distributed systems.
16
+ *
17
+ * Browser compatibility: Requires support for `crypto.randomUUID()` (available
18
+ * in modern browsers). If you need to support older browsers, consider using a
19
+ * polyfill.
20
+ *
21
+ * @returns An IdGenerationService that generates cryptographically secure UUIDs
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { createBrowserIdGenerationService } from '@uploadista/client-browser';
26
+ *
27
+ * const idService = createBrowserIdGenerationService();
28
+ *
29
+ * // Generate a unique ID
30
+ * const id = idService.generate();
31
+ * console.log('Generated ID:', id);
32
+ * // Output: "550e8400-e29b-41d4-a716-446655440000" (example UUID v4)
33
+ *
34
+ * // Each call generates a new unique ID
35
+ * const id2 = idService.generate();
36
+ * console.log('Another ID:', id2);
37
+ * // Output: "7c9e6679-7425-40de-944b-e07fc1f90ae7" (different UUID)
38
+ * ```
39
+ */
40
+ export function createBrowserIdGenerationService(): IdGenerationService {
41
+ return {
42
+ /**
43
+ * Generates a cryptographically secure random UUID (v4).
44
+ *
45
+ * Uses the Web Crypto API's `crypto.randomUUID()` method to generate
46
+ * a UUID conforming to RFC 4122 version 4. Each UUID is statistically
47
+ * unique and suitable for use as an identifier in distributed systems.
48
+ *
49
+ * @returns A UUID v4 string in the format "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const service = createBrowserIdGenerationService();
54
+ * const uploadId = service.generate();
55
+ * console.log(uploadId); // "f47ac10b-58cc-4372-a567-0e02b2c3d479"
56
+ * ```
57
+ */
58
+ generate: () => crypto.randomUUID(),
59
+ };
60
+ }