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:
2026-07-15 03:04:24 -07:00
committed by Hop
parent 0718af136c
commit 9e1ed2c9cb
26 changed files with 4128 additions and 0 deletions
+400
View File
@@ -0,0 +1,400 @@
import { describe, expect, it, vi } from "vitest";
import {
StuffboxClient,
StuffboxError,
type FetchLike,
} from "../src";
function json(value: unknown, init: ResponseInit = {}): Response {
const headers = new Headers(init.headers);
headers.set("content-type", "application/json");
return new Response(JSON.stringify(value), {
...init,
headers,
});
}
describe("StuffboxClient", () => {
it("creates a PKCE connection request using the protocol wire names", async () => {
const fetcher = vi.fn<FetchLike>().mockResolvedValue(
json({
request_id: "cr_1",
client_id: "app_0123456789abcdefghijklmnop",
callback_url: "https://synapsis.test/stuffbox/callback",
authorization_url: "https://stuffbox.test/authorize/cr_1",
expires_at: "2026-01-01T00:05:00.000Z",
}),
);
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test/",
fetch: fetcher,
accessToken: "must-not-leak",
});
await expect(
client.createConnectionRequest({
clientId: "app_0123456789abcdefghijklmnop",
callbackUrl: "https://synapsis.test/stuffbox/callback",
codeChallenge: "c".repeat(43),
scopes: ["assets:read", "assets:write"],
state: "state-with-enough-entropy",
}),
).resolves.toEqual({
id: "cr_1",
clientId: "app_0123456789abcdefghijklmnop",
callbackUrl: "https://synapsis.test/stuffbox/callback",
authorizationUrl: "https://stuffbox.test/authorize/cr_1",
expiresAt: "2026-01-01T00:05:00.000Z",
});
const [url, init] = fetcher.mock.calls[0];
expect(url).toBe("https://stuffbox.test/api/v1/connection-requests");
expect(init?.method).toBe("POST");
expect(init?.headers).not.toHaveProperty("Authorization");
expect(JSON.parse(String(init?.body))).toEqual({
client_id: "app_0123456789abcdefghijklmnop",
callback_url: "https://synapsis.test/stuffbox/callback",
code_challenge: "c".repeat(43),
code_challenge_method: "S256",
scopes: ["assets:read", "assets:write"],
state: "state-with-enough-entropy",
});
});
it("automatically registers a self-hosted callback and returns its client ID", async () => {
const fetcher = vi.fn<FetchLike>().mockResolvedValue(
json({
request_id: "cr_self_hosted",
client_id: "app_self_hosted_0123456789",
callback_url: "https://synapsis.test/stuffbox/callback",
authorization_url: "https://stuffbox.test/authorize/cr_self_hosted",
expires_at: "2026-01-01T00:05:00.000Z",
}),
);
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test",
fetch: fetcher,
});
await expect(
client.createConnectionRequest({
selfHosted: true,
callbackUrl: "https://synapsis.test/stuffbox/callback",
codeChallenge: "c".repeat(43),
scopes: ["assets:read", "assets:write"],
state: "state-with-enough-entropy",
}),
).resolves.toEqual({
id: "cr_self_hosted",
clientId: "app_self_hosted_0123456789",
callbackUrl: "https://synapsis.test/stuffbox/callback",
authorizationUrl: "https://stuffbox.test/authorize/cr_self_hosted",
expiresAt: "2026-01-01T00:05:00.000Z",
});
expect(JSON.parse(String(fetcher.mock.calls[0][1]?.body))).toEqual({
registration_mode: "self_hosted",
callback_url: "https://synapsis.test/stuffbox/callback",
code_challenge: "c".repeat(43),
code_challenge_method: "S256",
scopes: ["assets:read", "assets:write"],
state: "state-with-enough-entropy",
});
});
it("rejects a connection response that omits the client ID", async () => {
const fetcher = vi.fn<FetchLike>().mockResolvedValue(
json({
request_id: "cr_missing_client",
callback_url: "https://synapsis.test/stuffbox/callback",
authorization_url: "https://stuffbox.test/authorize/cr_missing_client",
expires_at: "2026-01-01T00:05:00.000Z",
}),
);
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test",
fetch: fetcher,
});
const error = await client
.createConnectionRequest({
selfHosted: true,
callbackUrl: "https://synapsis.test/stuffbox/callback",
codeChallenge: "c".repeat(43),
scopes: ["assets:read"],
state: "state-with-enough-entropy",
})
.catch((cause: unknown) => cause);
expect(error).toBeInstanceOf(StuffboxError);
expect(error).toMatchObject({
code: "invalid_response",
message: "Missing string field: clientId",
});
});
it("rejects a connection response that omits the canonical callback URL", async () => {
const fetcher = vi.fn<FetchLike>().mockResolvedValue(
json({
request_id: "cr_missing_callback",
client_id: "app_self_hosted_0123456789",
authorization_url: "https://stuffbox.test/authorize/cr_missing_callback",
expires_at: "2026-01-01T00:05:00.000Z",
}),
);
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test",
fetch: fetcher,
});
const error = await client
.createConnectionRequest({
selfHosted: true,
callbackUrl: "https://synapsis.test/stuffbox/callback",
codeChallenge: "c".repeat(43),
scopes: ["assets:read"],
state: "state-with-enough-entropy",
})
.catch((cause: unknown) => cause);
expect(error).toBeInstanceOf(StuffboxError);
expect(error).toMatchObject({
code: "invalid_response",
message: "Missing string field: callbackUrl",
});
});
it("dispatches authorization-code and refresh grants through one endpoint", async () => {
const fetcher = vi.fn<FetchLike>().mockImplementation(async () =>
json({
access_token: "at_new",
token_type: "bearer",
expires_in: 600,
refresh_token: "rt_new",
refresh_token_expires_in: 2_592_000,
scope: "assets:read assets:write",
}),
);
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test",
fetch: fetcher,
});
const exchanged = await client.exchangeAuthorizationCode({
clientId: "app_0123456789abcdefghijklmnop",
code: "code",
codeVerifier: "verifier",
redirectUri: "https://node.test/callback",
});
const refreshed = await client.refreshTokens("rt_old");
expect(exchanged).toEqual(refreshed);
expect(JSON.parse(String(fetcher.mock.calls[0][1]?.body))).toEqual({
grant_type: "authorization_code",
client_id: "app_0123456789abcdefghijklmnop",
code: "code",
code_verifier: "verifier",
redirect_uri: "https://node.test/callback",
});
expect(JSON.parse(String(fetcher.mock.calls[1][1]?.body))).toEqual({
grant_type: "refresh_token",
refresh_token: "rt_old",
});
expect(fetcher.mock.calls[0][0]).toBe("https://stuffbox.test/api/v1/token");
expect(fetcher.mock.calls[1][0]).toBe("https://stuffbox.test/api/v1/token");
});
it("adds a lazy bearer token only to protected requests", async () => {
const accessToken = vi.fn().mockResolvedValue("at_secret");
const fetcher = vi.fn<FetchLike>().mockResolvedValue(
json({ items: [], next_cursor: "next" }),
);
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test",
fetch: fetcher,
accessToken,
});
await expect(client.listAssets({ limit: 10 })).resolves.toEqual({
items: [],
nextCursor: "next",
});
expect(accessToken).toHaveBeenCalledOnce();
expect(fetcher.mock.calls[0][0]).toBe(
"https://stuffbox.test/api/v1/assets?limit=10",
);
expect(fetcher.mock.calls[0][1]?.headers).toHaveProperty(
"Authorization",
"Bearer at_secret",
);
});
it("rejects an asset page size outside the public API range before fetching", async () => {
const fetcher = vi.fn<FetchLike>();
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test",
fetch: fetcher,
accessToken: "at_secret",
});
await expect(client.listAssets({ limit: 101 })).rejects.toThrow(
"Asset list limit must be between 1 and 100",
);
expect(fetcher).not.toHaveBeenCalled();
});
it("creates and completes a direct upload without handling file bytes", async () => {
const fetcher = vi
.fn<FetchLike>()
.mockResolvedValueOnce(
json({
upload_id: "up_1",
upload_url: "https://objects.test/signed",
method: "PUT",
required_headers: { "Content-Type": "image/png" },
expires_at: "2026-01-01T00:10:00.000Z",
completion_token: "uct_1",
}),
)
.mockResolvedValueOnce(
json({
asset: {
id: "as_1",
public_id: "pub_1",
canonical_url: "https://stuffbox.test/f/pub_1",
original_filename: "image.png",
mime_type: "image/png",
byte_size: 12,
status: "active",
created_at: "2026-01-01T00:00:00.000Z",
},
}),
);
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test",
fetch: fetcher,
accessToken: "at_1",
});
const session = await client.createUpload({
filename: "image.png",
mimeType: "image/png",
size: 12,
});
const asset = await client.completeUpload(session.id, {
completionToken: session.completionToken,
});
expect(session.requiredHeaders).toEqual({ "Content-Type": "image/png" });
expect(asset.url).toBe("https://stuffbox.test/f/pub_1");
expect(JSON.parse(String(fetcher.mock.calls[1][1]?.body))).toEqual({
completion_token: "uct_1",
});
});
it("throws typed structured errors with retry metadata", async () => {
const fetcher: FetchLike = async () =>
json(
{
error: {
code: "quota_exceeded",
message: "Storage quota exceeded",
details: { remaining_bytes: 4 },
request_id: "req_1",
},
},
{ status: 409, headers: { "retry-after": "12" } },
);
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test",
fetch: fetcher,
accessToken: "at_1",
});
const error = await client
.createUpload({ filename: "large.mp4", mimeType: "video/mp4", size: 100 })
.catch((cause: unknown) => cause);
expect(error).toBeInstanceOf(StuffboxError);
expect(error).toMatchObject({
code: "quota_exceeded",
status: 409,
requestId: "req_1",
retryAfter: 12,
details: { remaining_bytes: 4 },
});
});
it("supports endpoint overrides and per-request bearer suppression", async () => {
const fetcher = vi.fn<FetchLike>().mockResolvedValue(json({ items: [] }));
const client = new StuffboxClient({
baseUrl: "https://example.test/custom",
fetch: fetcher,
accessToken: "at_default",
endpoints: { assets: "/assets" },
});
await client.listAssets({}, { accessToken: null });
expect(fetcher.mock.calls[0][0]).toBe("https://example.test/custom/assets");
expect(fetcher.mock.calls[0][1]?.headers).not.toHaveProperty("Authorization");
});
it("revokes opaque tokens without adding another bearer credential", async () => {
const fetcher = vi
.fn<FetchLike>()
.mockResolvedValue(new Response(null, { status: 204 }));
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test",
fetch: fetcher,
accessToken: "at_configured",
});
await client.revokeToken({
token: "rt_revoked",
tokenTypeHint: "refresh_token",
});
expect(fetcher.mock.calls[0][0]).toBe("https://stuffbox.test/api/v1/revoke");
expect(fetcher.mock.calls[0][1]?.headers).not.toHaveProperty("Authorization");
expect(JSON.parse(String(fetcher.mock.calls[0][1]?.body))).toEqual({
token: "rt_revoked",
token_type_hint: "refresh_token",
});
});
it("gets and deletes an owner-scoped asset by encoded ID", async () => {
const fetcher = vi
.fn<FetchLike>()
.mockResolvedValueOnce(
json({
asset: {
id: "asset/one",
public_asset_id: "pub_1",
url: "https://stuffbox.test/f/pub_1",
filename: "photo.jpg",
mimeType: "image/jpeg",
byteSize: 20,
status: "active",
createdAt: "2026-01-01T00:00:00.000Z",
},
}),
)
.mockResolvedValueOnce(new Response(null, { status: 204 }));
const client = new StuffboxClient({
baseUrl: "https://stuffbox.test",
fetch: fetcher,
accessToken: "at_1",
});
await expect(client.getAsset("asset/one")).resolves.toMatchObject({
id: "asset/one",
publicId: "pub_1",
});
await client.deleteAsset("asset/one");
expect(fetcher.mock.calls[0][0]).toBe(
"https://stuffbox.test/api/v1/assets/asset%2Fone",
);
expect(fetcher.mock.calls[1][1]?.method).toBe("DELETE");
});
});
+41
View File
@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";
import {
createPkcePair,
deriveCodeChallenge,
generateCodeVerifier,
generateState,
isValidCodeVerifier,
} from "../src";
describe("PKCE helpers", () => {
it("derives the RFC 7636 S256 example", async () => {
const verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
await expect(deriveCodeChallenge(verifier)).resolves.toBe(
"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
);
});
it("generates valid verifier lengths", () => {
for (const length of [43, 64, 128]) {
const verifier = generateCodeVerifier(length);
expect(verifier).toHaveLength(length);
expect(isValidCodeVerifier(verifier)).toBe(true);
}
});
it("rejects invalid verifier lengths and characters", async () => {
expect(() => generateCodeVerifier(42)).toThrow(RangeError);
expect(isValidCodeVerifier("a".repeat(42))).toBe(false);
await expect(deriveCodeChallenge(`${"a".repeat(42)}!`)).rejects.toThrow(
"Invalid RFC 7636 code verifier",
);
});
it("creates an S256 pair and a high-entropy state", async () => {
const pair = await createPkcePair();
expect(pair.method).toBe("S256");
expect(pair.challenge).toBe(await deriveCodeChallenge(pair.verifier));
expect(generateState()).toMatch(/^[A-Za-z0-9_-]{43}$/);
});
});
+109
View File
@@ -0,0 +1,109 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
uploadFile,
type UploadProgress,
type UploadTransport,
} from "../src/react/index.js";
class FakeUploadTarget {
onprogress: ((event: ProgressEvent) => void) | null = null;
}
class FakeXMLHttpRequest {
static latest: FakeXMLHttpRequest | undefined;
readonly upload = new FakeUploadTarget();
readonly headers: Record<string, string> = {};
status = 0;
method = "";
url = "";
body: unknown;
onload: (() => void) | null = null;
onerror: (() => void) | null = null;
onabort: (() => void) | null = null;
constructor() {
FakeXMLHttpRequest.latest = this;
}
open(method: string, url: string): void {
this.method = method;
this.url = url;
}
setRequestHeader(name: string, value: string): void {
this.headers[name.toLowerCase()] = value;
}
send(body: unknown): void {
this.body = body;
const size = (body as { size?: number }).size ?? 0;
this.upload.onprogress?.({ loaded: size } as ProgressEvent);
this.status = 200;
this.onload?.();
}
abort(): void {
this.onabort?.();
}
}
describe("direct React upload helper", () => {
const original = globalThis.XMLHttpRequest;
afterEach(() => {
globalThis.XMLHttpRequest = original;
FakeXMLHttpRequest.latest = undefined;
});
it("PUTs the file to object storage, reports progress, then completes", async () => {
globalThis.XMLHttpRequest = FakeXMLHttpRequest as unknown as typeof XMLHttpRequest;
const file = { name: "photo.png", type: "image/png", size: 4 } as File;
const progress: UploadProgress[] = [];
const transport: UploadTransport = {
createUpload: vi.fn().mockResolvedValue({
id: "upl_1",
uploadUrl: "https://objects.example/signed-put",
method: "PUT",
requiredHeaders: { "content-type": "image/png", "if-none-match": "*" },
expiresAt: "2026-07-15T00:10:00.000Z",
}),
completeUpload: vi.fn().mockResolvedValue({
id: "ast_1",
publicId: "file_1",
url: "https://stuffbox.example/f/file_1",
filename: "photo.png",
mimeType: "image/png",
byteSize: 4,
status: "active",
createdAt: "2026-07-15T00:00:00.000Z",
}),
};
const asset = await uploadFile(transport, file, {
onProgress: (value) => progress.push(value),
});
expect(asset.id).toBe("ast_1");
expect(transport.createUpload).toHaveBeenCalledWith(
{ filename: "photo.png", mimeType: "image/png", size: 4 },
{},
);
expect(FakeXMLHttpRequest.latest).toMatchObject({
method: "PUT",
url: "https://objects.example/signed-put",
body: file,
headers: { "content-type": "image/png", "if-none-match": "*" },
});
expect(transport.completeUpload).toHaveBeenCalledWith("upl_1", undefined, {});
expect(progress.map((value) => value.phase)).toEqual([
"creating",
"uploading",
"uploading",
"uploading",
"completing",
"complete",
]);
expect(progress.at(-1)?.percent).toBe(100);
});
});