@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.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-check.log +130 -0
- package/AUTO_CAPABILITIES.md +98 -0
- package/FRAMEWORK_INTEGRATION.md +407 -0
- package/LICENSE +21 -0
- package/README.md +795 -0
- package/SMART_CHUNKING.md +140 -0
- package/dist/client/create-uploadista-client.d.ts +182 -0
- package/dist/client/create-uploadista-client.d.ts.map +1 -0
- package/dist/client/create-uploadista-client.js +76 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +1 -0
- package/dist/framework-utils.d.ts +201 -0
- package/dist/framework-utils.d.ts.map +1 -0
- package/dist/framework-utils.js +282 -0
- package/dist/http-client.d.ts +44 -0
- package/dist/http-client.d.ts.map +1 -0
- package/dist/http-client.js +489 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/services/abort-controller-factory.d.ts +30 -0
- package/dist/services/abort-controller-factory.d.ts.map +1 -0
- package/dist/services/abort-controller-factory.js +98 -0
- package/dist/services/checksum-service.d.ts +30 -0
- package/dist/services/checksum-service.d.ts.map +1 -0
- package/dist/services/checksum-service.js +44 -0
- package/dist/services/create-browser-services.d.ts +36 -0
- package/dist/services/create-browser-services.d.ts.map +1 -0
- package/dist/services/create-browser-services.js +56 -0
- package/dist/services/file-reader.d.ts +91 -0
- package/dist/services/file-reader.d.ts.map +1 -0
- package/dist/services/file-reader.js +251 -0
- package/dist/services/fingerprint-service.d.ts +41 -0
- package/dist/services/fingerprint-service.d.ts.map +1 -0
- package/dist/services/fingerprint-service.js +64 -0
- package/dist/services/id-generation/id-generation.d.ts +40 -0
- package/dist/services/id-generation/id-generation.d.ts.map +1 -0
- package/dist/services/id-generation/id-generation.js +58 -0
- package/dist/services/platform-service.d.ts +38 -0
- package/dist/services/platform-service.d.ts.map +1 -0
- package/dist/services/platform-service.js +221 -0
- package/dist/services/storage/local-storage-service.d.ts +55 -0
- package/dist/services/storage/local-storage-service.d.ts.map +1 -0
- package/dist/services/storage/local-storage-service.js +178 -0
- package/dist/services/storage/session-storage-service.d.ts +55 -0
- package/dist/services/storage/session-storage-service.d.ts.map +1 -0
- package/dist/services/storage/session-storage-service.js +179 -0
- package/dist/services/websocket-factory.d.ts +46 -0
- package/dist/services/websocket-factory.d.ts.map +1 -0
- package/dist/services/websocket-factory.js +196 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/upload-input.d.ts +26 -0
- package/dist/types/upload-input.d.ts.map +1 -0
- package/dist/types/upload-input.js +1 -0
- package/dist/utils/hash-util.d.ts +60 -0
- package/dist/utils/hash-util.d.ts.map +1 -0
- package/dist/utils/hash-util.js +75 -0
- package/package.json +32 -0
- package/src/client/create-uploadista-client.ts +150 -0
- package/src/client/index.ts +1 -0
- package/src/framework-utils.ts +446 -0
- package/src/http-client.ts +546 -0
- package/src/index.ts +8 -0
- package/src/services/abort-controller-factory.ts +108 -0
- package/src/services/checksum-service.ts +46 -0
- package/src/services/create-browser-services.ts +81 -0
- package/src/services/file-reader.ts +344 -0
- package/src/services/fingerprint-service.ts +67 -0
- package/src/services/id-generation/id-generation.ts +60 -0
- package/src/services/platform-service.ts +231 -0
- package/src/services/storage/local-storage-service.ts +187 -0
- package/src/services/storage/session-storage-service.ts +188 -0
- package/src/services/websocket-factory.ts +212 -0
- package/src/types/index.ts +1 -0
- package/src/types/upload-input.ts +25 -0
- package/src/utils/hash-util.ts +79 -0
- package/tsconfig.json +22 -0
- package/tsconfig.tsbuildinfo +1 -0
- 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
|
+
}
|