Publish the unified MIT-licensed Stuffbox SDK source
Hop-State: A_06FPAF0M83E7T0JJ7EP2WQ0 Hop-Proposal: R_06FPAF04S0D4G8TA8TNK3C0 Hop-Task: T_06FPADTV2YD4PD5GMTK3150 Hop-Attempt: AT_06FPADTV2ZYKEY8GH3JTK98
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
# `@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 (
|
||||
<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
|
||||
|
||||
- [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.
|
||||
Reference in New Issue
Block a user