cyph3rasi bb04d47a29 Release automatic callback setup in SDK 0.2.0
Hop-State: A_06FPARPGMNVEAN3XBVH0QD8
Hop-Proposal: R_06FPARNXMP3DGP5VQGGX4Z8
Hop-Task: T_06FPAR9KH2V5ED7V65JEN00
Hop-Attempt: AT_06FPAR9KH3NZ1CV8RGPJDSG
2026-07-15 03:46:42 -07:00

@gnosyslabs/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

npm install @gnosyslabs/stuffbox-sdk

The core entry point has no runtime dependencies. If you use @gnosyslabs/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.

import {
  StuffboxClient,
  createPkcePair,
  generateState,
} from "@gnosyslabs/stuffbox-sdk";

const stuffbox = new StuffboxClient({ baseUrl: "https://stuffbox.xyz" });
const pkce = await createPkcePair();
const state = generateState();
const savedClientId = await loadStuffboxClientId();

const connection = await stuffbox.createConnectionRequest({
  ...(savedClientId ? { clientId: savedClientId } : {}),
  callbackUrl: "https://yourapp.example/api/stuffbox/callback",
  codeChallenge: pkce.challenge,
  scopes: ["assets:read"],
  state,
});

// No dashboard registration is needed. Persist the returned identity and
// pending PKCE state before redirecting the user's browser.
await saveStuffboxClientId(connection.clientId);
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:

const pending = await loadPendingConnection();

const tokens = await stuffbox.exchangeAuthorizationCode({
  clientId: pending.clientId,
  code: callbackCode,
  codeVerifier: pending.codeVerifier,
  redirectUri: pending.callbackUrl,
});

Stuffbox creates or reuses an identity for the exact callback automatically. Pass the returned clientId on later connections when available. The client ID is public routing information, not a secret.

Read a user's media library

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:

import { useStuffboxUpload, type UploadTransport } from "@gnosyslabs/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 (
    <div>
      <input
        type="file"
        onChange={(event) => {
          const file = event.currentTarget.files?.[0];
          if (file) void upload(file);
        }}
      />
      {state.status === "uploading" && (
        <button type="button" onClick={abort}>
          Cancel {Math.round(state.progress.percent)}%
        </button>
      )}
    </div>
  );
}

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

License

MIT © Gnosys Labs. The Stuffbox service implementation is proprietary and is not included in this package or its public source repository.

S
Description
No description provided
Readme MIT 98 KiB
Languages
TypeScript 99.7%
JavaScript 0.3%