@dofe/file-sdk-web 0.1.1 → 0.1.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/README.md +16 -16
- package/dist/uploader.d.ts +1 -0
- package/dist/uploader.js +27 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,8 +26,8 @@ const { fileId, cdnUrl } = await uploader.upload(file, {
|
|
|
26
26
|
// Large files: multipart upload with concurrency
|
|
27
27
|
const { fileId, cdnUrl } = await uploader.uploadMultipart(bigFile, {
|
|
28
28
|
scope: 'media-asset',
|
|
29
|
-
partSize: 5 * 1024 * 1024,
|
|
30
|
-
concurrency: 3,
|
|
29
|
+
partSize: 5 * 1024 * 1024, // 5MB parts
|
|
30
|
+
concurrency: 3, // 3 parallel parts
|
|
31
31
|
onProgress: ({ percent, loaded, total }) => {
|
|
32
32
|
console.log(`${percent}% (${loaded}/${total})`);
|
|
33
33
|
},
|
|
@@ -38,28 +38,28 @@ const { fileId, cdnUrl } = await uploader.uploadMultipart(bigFile, {
|
|
|
38
38
|
|
|
39
39
|
### `new FileUploader(config)`
|
|
40
40
|
|
|
41
|
-
| Option
|
|
42
|
-
|
|
43
|
-
| `apiBase` | `string` |
|
|
44
|
-
| `timeout` | `number` |
|
|
41
|
+
| Option | Type | Required | Default | Description |
|
|
42
|
+
| --------- | -------- | :------: | ------- | -------------------------------------------------------------- |
|
|
43
|
+
| `apiBase` | `string` | ✅ | — | API base URL (e.g., `/api/proxy/sso` or `https://sso.dofe.ai`) |
|
|
44
|
+
| `timeout` | `number` | ❌ | `30000` | Request timeout in ms |
|
|
45
45
|
|
|
46
46
|
### Methods
|
|
47
47
|
|
|
48
|
-
| Method
|
|
49
|
-
|
|
50
|
-
| `upload(file, options)`
|
|
51
|
-
| `uploadDirect(file, options)`
|
|
52
|
-
| `uploadMultipart(file, options)` | `Promise<UploadResult>` | Multipart upload with configurable part size and concurrency
|
|
48
|
+
| Method | Returns | Description |
|
|
49
|
+
| -------------------------------- | ----------------------- | ---------------------------------------------------------------------- |
|
|
50
|
+
| `upload(file, options)` | `Promise<UploadResult>` | Auto-chooses direct or multipart based on file size (>5MB → multipart) |
|
|
51
|
+
| `uploadDirect(file, options)` | `Promise<UploadResult>` | Direct upload for small files |
|
|
52
|
+
| `uploadMultipart(file, options)` | `Promise<UploadResult>` | Multipart upload with configurable part size and concurrency |
|
|
53
53
|
|
|
54
54
|
### UploadResult
|
|
55
55
|
|
|
56
56
|
```typescript
|
|
57
57
|
interface UploadResult {
|
|
58
|
-
fileId: string;
|
|
59
|
-
key: string;
|
|
60
|
-
url: string;
|
|
61
|
-
cdnUrl: string | null;
|
|
62
|
-
bucket: string;
|
|
58
|
+
fileId: string; // SSO FileSource UUID
|
|
59
|
+
key: string; // Object storage key
|
|
60
|
+
url: string; // Upload URL / token
|
|
61
|
+
cdnUrl: string | null; // CDN URL (null for private files)
|
|
62
|
+
bucket: string; // Storage bucket name
|
|
63
63
|
}
|
|
64
64
|
```
|
|
65
65
|
|
package/dist/uploader.d.ts
CHANGED
package/dist/uploader.js
CHANGED
|
@@ -71,10 +71,14 @@ class FileUploader {
|
|
|
71
71
|
const { token, fileId, key, bucket, url: uploadId } = (_c = initRes.data) !== null && _c !== void 0 ? _c : initRes;
|
|
72
72
|
// Step 2: Upload first part
|
|
73
73
|
const part1 = file.slice(0, partSize);
|
|
74
|
-
await this.uploadToPresignedUrl(token, part1);
|
|
75
|
-
const parts = [{ partNumber: 1, etag:
|
|
74
|
+
const etag1 = await this.uploadToPresignedUrl(token, part1);
|
|
75
|
+
const parts = [{ partNumber: 1, etag: etag1 }];
|
|
76
76
|
let loaded = partSize;
|
|
77
|
-
(_d = options.onProgress) === null || _d === void 0 ? void 0 : _d.call(options, {
|
|
77
|
+
(_d = options.onProgress) === null || _d === void 0 ? void 0 : _d.call(options, {
|
|
78
|
+
percent: Math.round((loaded / file.size) * 100),
|
|
79
|
+
loaded,
|
|
80
|
+
total: file.size,
|
|
81
|
+
});
|
|
78
82
|
// Step 3: Upload remaining parts in parallel batches
|
|
79
83
|
for (let i = 2; i <= totalParts; i += concurrency) {
|
|
80
84
|
const batch = [];
|
|
@@ -86,7 +90,11 @@ class FileUploader {
|
|
|
86
90
|
parts.push(...results.filter(Boolean));
|
|
87
91
|
loaded += (batchEnd - i + 1) * partSize;
|
|
88
92
|
const actualLoaded = Math.min(loaded, file.size);
|
|
89
|
-
(_e = options.onProgress) === null || _e === void 0 ? void 0 : _e.call(options, {
|
|
93
|
+
(_e = options.onProgress) === null || _e === void 0 ? void 0 : _e.call(options, {
|
|
94
|
+
percent: Math.round((actualLoaded / file.size) * 100),
|
|
95
|
+
loaded: actualLoaded,
|
|
96
|
+
total: file.size,
|
|
97
|
+
});
|
|
90
98
|
}
|
|
91
99
|
// Step 4: Complete multipart upload
|
|
92
100
|
const completeRes = await this.fetchJson(`${this.apiBase}/api/uploader/complete`, {
|
|
@@ -124,8 +132,8 @@ class FileUploader {
|
|
|
124
132
|
const start = (partNumber - 1) * partSize;
|
|
125
133
|
const end = Math.min(start + partSize, file.size);
|
|
126
134
|
const chunk = file.slice(start, end);
|
|
127
|
-
await this.uploadToPresignedUrl((_b = (_a = tokenRes.data) === null || _a === void 0 ? void 0 : _a.token) !== null && _b !== void 0 ? _b : tokenRes.token, chunk);
|
|
128
|
-
return { partNumber, etag
|
|
135
|
+
const etag = await this.uploadToPresignedUrl((_b = (_a = tokenRes.data) === null || _a === void 0 ? void 0 : _a.token) !== null && _b !== void 0 ? _b : tokenRes.token, chunk);
|
|
136
|
+
return { partNumber, etag };
|
|
129
137
|
}
|
|
130
138
|
catch (err) {
|
|
131
139
|
// Part upload failed; caller can retry
|
|
@@ -134,8 +142,10 @@ class FileUploader {
|
|
|
134
142
|
}
|
|
135
143
|
/**
|
|
136
144
|
* PUT file/blob directly to a presigned URL.
|
|
145
|
+
* Returns the ETag from the response headers for multipart completion.
|
|
137
146
|
*/
|
|
138
147
|
async uploadToPresignedUrl(presignedUrl, data) {
|
|
148
|
+
var _a, _b;
|
|
139
149
|
const controller = new AbortController();
|
|
140
150
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
141
151
|
try {
|
|
@@ -150,6 +160,8 @@ class FileUploader {
|
|
|
150
160
|
if (!res.ok) {
|
|
151
161
|
throw new Error(`Upload to storage failed: HTTP ${res.status}`);
|
|
152
162
|
}
|
|
163
|
+
// 从响应头中提取实际 ETag(用于 multipart complete 验证)
|
|
164
|
+
return (_b = (_a = res.headers.get('ETag')) !== null && _a !== void 0 ? _a : res.headers.get('etag')) !== null && _b !== void 0 ? _b : '';
|
|
153
165
|
}
|
|
154
166
|
finally {
|
|
155
167
|
clearTimeout(timeoutId);
|
|
@@ -159,7 +171,7 @@ class FileUploader {
|
|
|
159
171
|
* Fetch JSON from API, extracting the data field from the standard response wrapper.
|
|
160
172
|
*/
|
|
161
173
|
async fetchJson(url, options) {
|
|
162
|
-
var _a;
|
|
174
|
+
var _a, _b;
|
|
163
175
|
const controller = new AbortController();
|
|
164
176
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
165
177
|
try {
|
|
@@ -168,6 +180,14 @@ class FileUploader {
|
|
|
168
180
|
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
169
181
|
}
|
|
170
182
|
const json = await res.json();
|
|
183
|
+
// Check for API-level errors in { code, msg, data } envelope
|
|
184
|
+
if (json && typeof json === 'object' && 'code' in json) {
|
|
185
|
+
const code = json.code;
|
|
186
|
+
if (code !== 0 && code !== 200) {
|
|
187
|
+
const msg = (_b = json.msg) !== null && _b !== void 0 ? _b : 'Unknown API error';
|
|
188
|
+
throw new Error(`API error [${code}]: ${msg}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
171
191
|
// Unwrap standard { code, msg, data } envelope
|
|
172
192
|
if (json && typeof json === 'object' && 'data' in json) {
|
|
173
193
|
return json;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dofe/file-sdk-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "DofeAI unified file uploader SDK for browsers — direct upload, multipart upload with progress, via sso.dofe.ai",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|