khalidadil
  • Joined on 2025-08-25

@khalidadil/filebot-client (0.2.0)

Published 2026-09-26 08:22:52 +00:00 by khalidadil

Installation

@khalidadil:registry=
npm install @khalidadil/filebot-client@0.2.0
"@khalidadil/filebot-client": "0.2.0"

About this package

@khalidadil/filebot-client

Browser Filebot client: a plan-driven Uppy uploader (direct presigned multipart PUTs, resume, live status), the host-route client, an upload API, an SSE subscriber, a list + gallery store, <img> srcset helpers, and a QR desktop helper. It holds no server secrets — it only ever sees host-minted grants, per-upload fuk_ keys and server-issued signed bases.

Publishes to the private Gitea registry https://git.kadil.dev/api/packages/khalidadil/npm/ (never GitHub), installable with no token.

pnpm add @khalidadil/filebot-client

Node >=20.19 for the toolchain; ships browser ESM. Runtime deps: @uppy/core, @uppy/aws-s3, @uppy/golden-retriever (all v5), uqr, plus the two internal packages.

The uploader

createUploader wires an Uppy instance to your host's file router: a pre-processor batches an init call, @uppy/aws-s3 runs the presigned part PUTs from the returned plan, a post-processor finalizes, and Golden Retriever + a reconciliation pass resume interrupted uploads across reloads. Pause / resume / retry / remove stay on Uppy itself (uppy.pauseAll(), resumeAll(), retryUpload(), removeFile(); removing a file aborts its Filebot upload).

import { createUploader, routeTokenSource } from "@khalidadil/filebot-client";

const uploader = createUploader({
  getToken: routeTokenSource({ mount: "/api/filebot", route: "avatars" }), // POSTs your host route
  storeName: "avatars",   // Golden Retriever namespace (= Uppy id); ^[A-Za-z0-9_:.-]{1,64}$, default "filebot"
  maxFilesPerInit: 1,     // integer 1..20, default 20; 1 = one Idempotency-Key per file
});

uploader.onUploadAccepted((e) => console.log("accepted", e.fileId, e.uploadId));
uploader.onRejected((e) => console.warn("rejected", e.uppyFileId, e.code, e.retryable));
uploader.onRestored((files) => reconcile(files)); // after a reload, ghosts included
uploader.onFileStatus((e) => setStatus(e.uppyFileId, e.status)); // ready / failed / quarantined

uploader.uppy.addFile({ name: file.name, type: file.type, data: file });

// later
uploader.destroy(); // idempotent: removes listeners, stops timers + SSE, destroys an Uppy it created

token-tier uploads cannot choose preserveQuality — the bucket default applies. HEIC and other files a browser reports with an empty type are typed by inferMime(name, type) before init.

The embedder surface (web-apps X-4)

This is the exact surface the apparel-designer capture flow (upload-engine.ts / uploader-adapter.ts) is built on — no host-specific extensions were needed. The mapping:

Uploader concept X-4 embedder use
storeName one namespace per capture surface (isolates Golden Retriever state)
maxFilesPerInit: 1 one file per init, one Idempotency-Key per file
getToken routeTokenSource (host route) or staticTokenSource (a pre-minted token)
onUploadAccepted(e) Filebot accepted the file — fires once per (uppyFileId, uploadId); a completing retry, resume or replayed init never double-fires
onRejected(e) init refused the file (code, retryable); a Filebot ErrorCode, client_outdated or invalid_state
onRestored(files) resume reconciliation result: each file's outcome is resumable / completed / reinit / pending_init, ghost: true when the blob must be re-selected
onFileStatus(e) post-ready lifecycle: ready / failed / quarantined
Uppy pauseAll / resumeAll / retryUpload / removeFile the host's own pause / resume / retry / remove controls

Every on* returns an unsubscribe; a throwing listener never breaks an upload.

Live status without a host route

The token-tier init response carries a sess.<sessionId> events ticket, so the uploader streams its own session over SSE with no host /events-ticket call (security-14). Node has no global EventSource; inject one where needed:

import { EventSource } from "eventsource";
createUploader({ getToken, EventSource });

Subscribe directly with subscribeEvents({ url, ticket, EventSource }) for custom flows.

Lists, galleries and media URLs

import { listFiles, createGalleryStore, mediaSrcset, mediaUrl } from "@khalidadil/filebot-client";

const page = await listFiles({ mount, identifier: "album:42" });      // one host-minted list token
const store = createGalleryStore({ mount, identifier: "album:42" });  // paging + subscribe

const { src, srcSet } = mediaSrcset(signedBase, oid, { preset: "card" }); // falls back to a ladder width
const full = mediaUrl(signedBase, oid, { rendition: "o" });

QR desktop (phone → desktop)

import { startQrSession } from "@khalidadil/filebot-client";

const qr = await startQrSession({ mount, route: "avatars" });
render(qr.url);                              // the phone scans this
qr.subscribe((state) => update(state.counts)); // uploaded / ready counts stream over the QR ticket

Errors

FilebotClientError carries a numeric HTTP-ish status plus a code, detail and retryable; parseErrorResponse(res) builds one from a host/API error body. Secrets are never placed in message text.

Testing

pnpm --filter @khalidadil/filebot-client test runs the unit suite under happy-dom (no services). test/pack.test.ts asserts the published tarball ships dist/** + README.md + package.json only with the exact runtime deps. uppy-pin.test.ts fails loudly if an Uppy bump changes the chunk loop or processor API. See Features/SDKs.md in the vault for the full testing and journey-1 story.

Release

Part of the five-package lockstep set; packages/filebot-server/scripts/lockstep-check.ts prints (never runs) the gated publish. Publishing needs the lead's go and Khalid's approval.

Dependencies

Dependencies

ID Version
@khalidadil/filebot-contracts 0.2.0
@khalidadil/filebot-signing 0.2.0
@uppy/aws-s3 ^5.1.0
@uppy/core ^5.2.0
@uppy/golden-retriever ^5.2.1
uqr 0.1.3

Development Dependencies

ID Version
@types/node ^22.10.0
happy-dom ^20.14.5
tsx ^4.19.2
typescript ^5.7.0
Details
npm
2026-09-26 08:22:52 +00:00
1
latest
61 KiB
Assets (1)
Versions (2) View all
0.2.0 2026-09-26
0.1.0 2026-09-18