# `@stuffbox/sdk` The official TypeScript SDK for connecting applications to Stuffbox, a user-owned media library. The package includes a framework-neutral client, shared API types, PKCE helpers, typed errors, and optional React upload helpers. ## Install ```sh npm install @stuffbox/sdk ``` The core entry point has no runtime dependencies. If you use `@stuffbox/sdk/react`, install React 18.2 or React 19 in your application. ## Connect a user Run the connection flow on your server. Access and refresh tokens must not be embedded in browser JavaScript. ```ts import { StuffboxClient, createPkcePair, generateState, } from "@stuffbox/sdk"; const stuffbox = new StuffboxClient({ baseUrl: "https://stuffbox.xyz" }); const pkce = await createPkcePair(); const state = generateState(); const connection = await stuffbox.createConnectionRequest({ selfHosted: true, callbackUrl: "https://yourapp.example/api/stuffbox/callback", codeChallenge: pkce.challenge, scopes: ["assets:read"], state, }); // Persist all four values before redirecting the user's browser. await savePendingConnection({ clientId: connection.clientId, callbackUrl: connection.callbackUrl, codeVerifier: pkce.verifier, state, }); redirect(connection.authorizationUrl); ``` After the exact callback, compare the returned state with the persisted value and exchange the one-time code: ```ts const pending = await loadPendingConnection(); const tokens = await stuffbox.exchangeAuthorizationCode({ clientId: pending.clientId, code: callbackCode, codeVerifier: pending.codeVerifier, redirectUri: pending.callbackUrl, }); ``` Hosted applications register once in the Stuffbox developer dashboard and send their assigned `clientId` instead of `selfHosted: true`. A first-time self-hosted installation receives a stable public client ID automatically; persist it for later connections. ## Read a user's media library ```ts const media = new StuffboxClient({ baseUrl: "https://stuffbox.xyz", accessToken: () => loadCurrentAccessToken(), }); let cursor: string | undefined; do { const page = await media.listAssets({ limit: 50, ...(cursor ? { cursor } : {}), }); addToMediaPicker(page.items); cursor = page.nextCursor; } while (cursor); ``` Request only the permissions your feature uses: `assets:read`, `assets:write`, and `assets:delete`. ## React uploads The optional React entry point exports `useStuffboxUpload`, `uploadFile`, and their types: ```tsx import { useStuffboxUpload, type UploadTransport } from "@stuffbox/sdk/react"; const transport: UploadTransport = { async createUpload(input, options) { const response = await fetch("/api/stuffbox/uploads", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(input), signal: options?.signal, }); if (!response.ok) throw new Error("Could not create upload"); return response.json(); }, async completeUpload(uploadId, input, options) { const response = await fetch(`/api/stuffbox/uploads/${uploadId}/complete`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(input ?? {}), signal: options?.signal, }); if (!response.ok) throw new Error("Could not complete upload"); return response.json(); }, }; export function UploadButton() { const { state, upload, abort } = useStuffboxUpload(transport); return (
{ const file = event.currentTarget.files?.[0]; if (file) void upload(file); }} /> {state.status === "uploading" && ( )}
); } ``` The browser should call same-origin endpoints owned by your application. Those endpoints call Stuffbox with the user's access token and return only the short-lived upload session or completed asset. File bytes then travel directly from the browser to object storage. ## Documentation - [Developer guide](https://stuffbox.xyz/developers) - [Protocol reference](docs/protocol.md) - [Integration examples](examples) - [Source and issues](https://githop.xyz/GnosysLabs/stuffbox-sdk) ## License MIT © Gnosys Labs. The Stuffbox service implementation is proprietary and is not included in this package or its public source repository.