Dependency-free Node.js binding for FastFileLink.
The npm package bundles the portable ffl.com APE, so callers do not need a separate FFL installation or Python runtime.
The low-level command grammar is generated by APEBind from
binding/ffl.apebind.yaml. The public API in src/ffl/client.js is handwritten so
FFL-specific library semantics remain explicit and reviewable.
- Node.js 18 or newer
- An environment where child processes can execute the bundled APE
There are no runtime npm dependencies.
The package also exposes the bundled FFL command-line program. Run it without a project installation:
npx @nuwainfo/ffl-js --versionAfter npm install @nuwainfo/ffl-js, run the project-local command with:
npx --no-install ffl-js --versionimport { share } from '@nuwainfo/ffl-js';
const session = await share('release.zip', {
maxDownloads: 1,
timeoutSeconds: 1800,
});
console.log(session.link);
await session.stop();Multiple files can be shared directly:
const session = await share(['one.txt', 'two.txt'], {
name: 'files.zip',
});Text and bytes helpers own their temporary source until the share session closes:
import { shareText } from '@nuwainfo/ffl-js';
const session = await shareText('hello', { name: 'hello.txt' });
console.log(session.link);
await session.close();Optional-value FFL flags use natural JavaScript values. receipt: true emits
--receipt without a value, while receipt: 'me@example.com' emits the flag and
address.
Use shareStream() to send a Node Readable directly to FFL without creating a
temporary file:
import { createReadStream } from 'node:fs';
import { shareStream } from '@nuwainfo/ffl-js';
const session = await shareStream(createReadStream('database.sql'), {
name: 'database.sql',
});
session.on('ready', (event) => console.log(event.data));
session.on('progress', (event) => console.log(event.data));Semantic event names are ready, progress, transport, and completed.
For FFL protocol diagnostics, use session.onRaw('/hook/...', listener) or
session.rawEvents(). Hook servers and temporary sources are released when the APE
process ends, even if the caller does not explicitly close the session.
import { download } from '@nuwainfo/ffl-js';
const result = await download('https://example.fastfilelink/...', {
outputPath: 'download.bin',
});
console.log(result.outputPath);
console.log(result.transferMode);download() preserves the underlying process result while exposing parsed transfer
mode and output path information.
For cancellation or binary streaming, use a download session:
import { downloadStream, startDownload } from '@nuwainfo/ffl-js';
const controller = new AbortController();
const transfer = await startDownload('https://example.fastfilelink/...', {
outputPath: 'download.bin',
signal: controller.signal,
});
await transfer.done;
const streamed = await downloadStream('https://example.fastfilelink/...');
streamed.stdout.pipe(process.stdout);
await streamed.done;downloadStream() preserves arbitrary bytes from FFL stdout; it does not decode the
download payload as UTF-8.
For shares protected with HTTP Basic Auth, set FFL_AUTH_PASSWORD in the application
environment and pass only authUser. The bundled FFL subprocess inherits the
environment, so the password stays out of its command line:
export FFL_AUTH_PASSWORD='use-your-secret-manager'$env:FFL_AUTH_PASSWORD = 'use-your-secret-manager'const session = await share('release.zip', { authUser: 'deploy' });Do not also pass authPassword: FFL gives the explicit CLI option precedence over
FFL_AUTH_PASSWORD. The environment variable applies to the sharing side; pass
download credentials explicitly when downloading a protected link.
If an operation fails with TLSError([0x6300]), WSL may be routing the bundled
.com APE through Windows interop. Run the following in WSL, then restart the WSL
session:
sudo sh -c 'echo -1 > /proc/sys/fs/binfmt_misc/WSLInterop'import { keygen } from '@nuwainfo/ffl-js';
const result = await keygen('alice');
console.log(result.publicKeyPath);
console.log(result.privateKeyPath);import { raw, version } from '@nuwainfo/ffl-js';
console.log(await version());
const result = await raw(['download', '--help']);The repository is a normal npm source project. The .tgz produced by npm pack is a
build artifact, not the source of truth.
npm test
npm run test:packaging
npm run test:real
npm packThe test groups are kept separate because the real and packaging suites execute the bundled APE.
Install the matching APEBind source project. When adopting a new ffl.com, first
refresh the raw CLI discovery evidence:
python scripts/inspect.py --ape /path/to/ffl.comReview binding/ffl.discovered.apebind.yaml, then manually merge intentional changes
into the canonical semantic contract binding/ffl.apebind.yaml. Automatic inspection
never overwrites the semantic contract.
Regenerate only the machine-owned low-level files:
python scripts/regenerate.py --ape /path/to/ffl.comRegeneration updates:
src/ffl/_generated.jssrc/ffl/_generated.d.tssrc/ffl/_runtime.jssrc/ffl/_runtime.d.tssrc/ffl/bin/ffl.com
It deliberately does not overwrite the handwritten client, result parsers, public type declarations, tests, or semantic schema.