libfw-client 0.1.1 → 0.1.2
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/index.d.ts +10 -0
- package/index.js +76 -12
- package/package.json +2 -2
- package/pkg/libfw_client.d.ts +7 -0
- package/pkg/libfw_client.js +20 -1
- package/pkg/libfw_client_bg.wasm +0 -0
- package/pkg/libfw_client_bg.wasm.d.ts +1 -0
- package/pkg/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -94,6 +94,16 @@ export declare class LibfwClient {
|
|
|
94
94
|
*/
|
|
95
95
|
downloadFolder(token: string, dirPath?: string): Promise<number>;
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Download a single file from the server at `filePath` into a user-selected
|
|
99
|
+
* local directory.
|
|
100
|
+
*
|
|
101
|
+
* @param token bearer token
|
|
102
|
+
* @param filePath virtual server path of the file to download
|
|
103
|
+
* @returns total bytes written
|
|
104
|
+
*/
|
|
105
|
+
downloadFile(token: string, filePath: string): Promise<number>;
|
|
106
|
+
|
|
97
107
|
/**
|
|
98
108
|
* Upload files to the server.
|
|
99
109
|
*
|
package/index.js
CHANGED
|
@@ -158,7 +158,7 @@ export class LibfwClient {
|
|
|
158
158
|
this._dirHandle = null;
|
|
159
159
|
/** @type {Map<string, FileSystemFileHandle>} path → file handle */
|
|
160
160
|
this._fileHandles = new Map();
|
|
161
|
-
/** @type {Map<string, FileSystemWritableFileStream>} path → writable stream */
|
|
161
|
+
/** @type {Map<string, FileSystemWritableFileStream>} path → open writable stream */
|
|
162
162
|
this._writables = new Map();
|
|
163
163
|
/** @type {Map<string, File>} path → File (upload) */
|
|
164
164
|
this._uploadFiles = new Map();
|
|
@@ -205,7 +205,7 @@ export class LibfwClient {
|
|
|
205
205
|
return {
|
|
206
206
|
onFileStart: (path, size) => this._emit({ type: 'fileStart', path, done: 0, total: size }),
|
|
207
207
|
onWriteChunk: (path, offset, data) => this._onWriteChunk(path, offset, data),
|
|
208
|
-
onFileCompleted: (path) => this.
|
|
208
|
+
onFileCompleted: (path) => this._onFileCompleted(path),
|
|
209
209
|
onProgress: (done, total) => this._emit({ type: 'progress', done, total }),
|
|
210
210
|
loadState: (direction, path) => Idb.loadState(`${direction}:${path}`),
|
|
211
211
|
saveState: (direction, path, state) => Idb.saveState(`${direction}:${path}`, state),
|
|
@@ -259,6 +259,36 @@ export class LibfwClient {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
/**
|
|
262
|
+
* Download a single file from the server at `filePath` into a local
|
|
263
|
+
* directory chosen via `showDirectoryPicker()`.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} token bearer token
|
|
266
|
+
* @param {string} filePath virtual server path of the file to download
|
|
267
|
+
* @returns {Promise<number>} total bytes written
|
|
268
|
+
* @throws {LibfwError}
|
|
269
|
+
*/
|
|
270
|
+
async downloadFile(token, filePath) {
|
|
271
|
+
const engine = await this._ready();
|
|
272
|
+
if (typeof window === 'undefined' || typeof window.showDirectoryPicker !== 'function') {
|
|
273
|
+
throw new LibfwError('File System Access API is not available in this browser', 'unsupported');
|
|
274
|
+
}
|
|
275
|
+
if (!filePath) throw new LibfwError('downloadFile requires a file path', 'path');
|
|
276
|
+
this._dirHandle = await window.showDirectoryPicker();
|
|
277
|
+
this._fileHandles.clear();
|
|
278
|
+
try {
|
|
279
|
+
return await engine.download_file(this._options.baseUrl, token, filePath);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
throw toLibfwError(err);
|
|
282
|
+
} finally {
|
|
283
|
+
await this._flushWritables();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Stream a decompressed chunk straight to disk at its absolute byte
|
|
289
|
+
* offset, keeping memory bounded regardless of file size (no whole-file
|
|
290
|
+
* buffering). The engine awaits this callback, so writes for a file are
|
|
291
|
+
* applied strictly in order.
|
|
262
292
|
* @param {string} path virtual path
|
|
263
293
|
* @param {number} offset byte offset
|
|
264
294
|
* @param {Uint8Array} data decompressed chunk
|
|
@@ -268,15 +298,13 @@ export class LibfwClient {
|
|
|
268
298
|
async _onWriteChunk(path, offset, data) {
|
|
269
299
|
let writable = this._writables.get(path);
|
|
270
300
|
if (!writable) {
|
|
301
|
+
// Open the destination once per file. A true resume (first chunk at
|
|
302
|
+
// offset > 0) keeps the existing prefix on disk; a fresh download opens
|
|
303
|
+
// a truncating writable. Always close the writable on completion/abort
|
|
304
|
+
// so no orphaned `.crswap` swap files are left behind.
|
|
305
|
+
const isResume = offset > 0;
|
|
271
306
|
const handle = await this._ensureFileHandle(path);
|
|
272
307
|
this._fileHandles.set(path, handle);
|
|
273
|
-
// Only a true resume (first chunk arrives at offset > 0) needs to
|
|
274
|
-
// keep the existing file's prefix. For a fresh download we open the
|
|
275
|
-
// writable WITHOUT keepExistingData, which truncates and lets us
|
|
276
|
-
// write sequentially. In real Chromium the keepExistingData +
|
|
277
|
-
// position-write path can leave empty target files behind a trail of
|
|
278
|
-
// orphaned `.crswap` swap files, so we avoid it unless required.
|
|
279
|
-
const isResume = offset > 0;
|
|
280
308
|
writable = await handle.createWritable(
|
|
281
309
|
isResume ? { keepExistingData: true } : undefined
|
|
282
310
|
);
|
|
@@ -285,6 +313,35 @@ export class LibfwClient {
|
|
|
285
313
|
await writable.write({ type: 'write', position: offset, data });
|
|
286
314
|
}
|
|
287
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Close the destination writable once a file's transfer completes.
|
|
318
|
+
* @param {string} path virtual path
|
|
319
|
+
* @returns {Promise<void>}
|
|
320
|
+
* @private
|
|
321
|
+
*/
|
|
322
|
+
async _onFileCompleted(path) {
|
|
323
|
+
await this._closeWritable(path);
|
|
324
|
+
this._emit({ type: 'fileCompleted', path });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Close (and forget) a file's writable, flushing buffered bytes to disk.
|
|
329
|
+
* @param {string} path virtual path
|
|
330
|
+
* @returns {Promise<void>}
|
|
331
|
+
* @private
|
|
332
|
+
*/
|
|
333
|
+
async _closeWritable(path) {
|
|
334
|
+
const writable = this._writables.get(path);
|
|
335
|
+
if (writable) {
|
|
336
|
+
this._writables.delete(path);
|
|
337
|
+
try {
|
|
338
|
+
await writable.close();
|
|
339
|
+
} catch {
|
|
340
|
+
/* best-effort flush on failure/abort */
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
288
345
|
/**
|
|
289
346
|
* Resolve (and create, if needed) the file handle for a virtual path,
|
|
290
347
|
* creating any parent directories along the way.
|
|
@@ -305,13 +362,20 @@ export class LibfwClient {
|
|
|
305
362
|
}
|
|
306
363
|
|
|
307
364
|
/**
|
|
308
|
-
* Close all open writable streams (flush to disk).
|
|
365
|
+
* Close all still-open writable streams (flush to disk). Called on
|
|
366
|
+
* success, failure or cancellation of a transfer.
|
|
309
367
|
* @returns {Promise<void>}
|
|
310
368
|
* @private
|
|
311
369
|
*/
|
|
312
370
|
async _flushWritables() {
|
|
313
|
-
const pending = [...this._writables.
|
|
314
|
-
|
|
371
|
+
const pending = [...this._writables.entries()].map(async ([path, writable]) => {
|
|
372
|
+
this._writables.delete(path);
|
|
373
|
+
try {
|
|
374
|
+
await writable.close();
|
|
375
|
+
} catch {
|
|
376
|
+
/* best-effort flush */
|
|
377
|
+
}
|
|
378
|
+
});
|
|
315
379
|
await Promise.allSettled(pending);
|
|
316
380
|
}
|
|
317
381
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libfw-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "High-performance streaming file & folder transfer SDK for the browser (libfw WASM engine + File System Access API + IndexedDB resume).",
|
|
5
|
-
"license": "MIT
|
|
5
|
+
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"module": "index.js",
|
package/pkg/libfw_client.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ export class LibfwClient {
|
|
|
15
15
|
* Bytes transferred so far.
|
|
16
16
|
*/
|
|
17
17
|
done_bytes(): number;
|
|
18
|
+
/**
|
|
19
|
+
* Download a single file at `file_path` into the chosen local directory.
|
|
20
|
+
*
|
|
21
|
+
* Resolves with the number of bytes written.
|
|
22
|
+
*/
|
|
23
|
+
download_file(base_url: string, token: string, file_path: string): Promise<any>;
|
|
18
24
|
/**
|
|
19
25
|
* Download every file under the virtual `dirPath` (empty = root).
|
|
20
26
|
*
|
|
@@ -77,6 +83,7 @@ export interface InitOutput {
|
|
|
77
83
|
readonly js_option_string: (a: any, b: number, c: number) => [number, number];
|
|
78
84
|
readonly libfwclient_cancel: (a: number) => void;
|
|
79
85
|
readonly libfwclient_done_bytes: (a: number) => number;
|
|
86
|
+
readonly libfwclient_download_file: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => any;
|
|
80
87
|
readonly libfwclient_download_folder: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => any;
|
|
81
88
|
readonly libfwclient_has_callbacks: (a: number) => number;
|
|
82
89
|
readonly libfwclient_new: (a: any) => number;
|
package/pkg/libfw_client.js
CHANGED
|
@@ -28,6 +28,25 @@ export class LibfwClient {
|
|
|
28
28
|
const ret = wasm.libfwclient_done_bytes(this.__wbg_ptr);
|
|
29
29
|
return ret;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Download a single file at `file_path` into the chosen local directory.
|
|
33
|
+
*
|
|
34
|
+
* Resolves with the number of bytes written.
|
|
35
|
+
* @param {string} base_url
|
|
36
|
+
* @param {string} token
|
|
37
|
+
* @param {string} file_path
|
|
38
|
+
* @returns {Promise<any>}
|
|
39
|
+
*/
|
|
40
|
+
download_file(base_url, token, file_path) {
|
|
41
|
+
const ptr0 = passStringToWasm0(base_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
42
|
+
const len0 = WASM_VECTOR_LEN;
|
|
43
|
+
const ptr1 = passStringToWasm0(token, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
44
|
+
const len1 = WASM_VECTOR_LEN;
|
|
45
|
+
const ptr2 = passStringToWasm0(file_path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
46
|
+
const len2 = WASM_VECTOR_LEN;
|
|
47
|
+
const ret = wasm.libfwclient_download_file(this.__wbg_ptr, ptr0, len0, ptr1, len1, ptr2, len2);
|
|
48
|
+
return ret;
|
|
49
|
+
}
|
|
31
50
|
/**
|
|
32
51
|
* Download every file under the virtual `dirPath` (empty = root).
|
|
33
52
|
*
|
|
@@ -431,7 +450,7 @@ function __wbg_get_imports() {
|
|
|
431
450
|
return ret;
|
|
432
451
|
},
|
|
433
452
|
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
434
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx:
|
|
453
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 133, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
435
454
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___wasm_bindgen_1e05ddb24c0b7df4___JsValue__core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_1e05ddb24c0b7df4___JsError___true_);
|
|
436
455
|
return ret;
|
|
437
456
|
},
|
package/pkg/libfw_client_bg.wasm
CHANGED
|
Binary file
|
|
@@ -5,6 +5,7 @@ export const __wbg_libfwclient_free: (a: number, b: number) => void;
|
|
|
5
5
|
export const js_option_string: (a: any, b: number, c: number) => [number, number];
|
|
6
6
|
export const libfwclient_cancel: (a: number) => void;
|
|
7
7
|
export const libfwclient_done_bytes: (a: number) => number;
|
|
8
|
+
export const libfwclient_download_file: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => any;
|
|
8
9
|
export const libfwclient_download_folder: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => any;
|
|
9
10
|
export const libfwclient_has_callbacks: (a: number) => number;
|
|
10
11
|
export const libfwclient_new: (a: any) => number;
|