@uploadista/client-browser 1.0.0-beta.4 → 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/package.json +5 -5
- package/src/__tests__/setup.ts +11 -9
- package/src/framework-utils.test.ts +7 -3
- package/src/http-client.test.ts +10 -10
- package/src/services/create-browser-services.test.ts +7 -4
- package/src/services/file-reader.test.ts +5 -3
- package/src/services/fingerprint-service.test.ts +16 -12
- package/src/services/id-generation/id-generation.test.ts +1 -1
- package/src/utils/hash-util.test.ts +1 -1
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
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"description": "Browser client for Uploadista",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Uploadista",
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"js-base64": "3.8.0",
|
|
28
|
-
"@uploadista/core": "1.0.0
|
|
29
|
-
"@uploadista/client-core": "1.0.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
33
|
"tsdown": "0.22.3",
|
|
34
|
-
"vitest": "4.
|
|
35
|
-
"@uploadista/typescript-config": "1.0.0
|
|
34
|
+
"vitest": "4.1.10",
|
|
35
|
+
"@uploadista/typescript-config": "1.0.0"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc --noEmit && tsdown",
|
package/src/__tests__/setup.ts
CHANGED
|
@@ -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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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", {
|
|
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", {
|
|
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");
|
package/src/http-client.test.ts
CHANGED
|
@@ -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 {
|
|
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], {
|
|
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", {
|
|
63
|
-
|
|
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
|
|
|
@@ -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
|