import { StuffboxClient, createPkcePair, generateState, type Asset, type StuffboxScope, type TokenSet, } from "@gnosyslabs/stuffbox-sdk"; const stuffbox = new StuffboxClient({ baseUrl: "https://stuffbox.xyz" }); export interface PendingConnection { clientId: string; callbackUrl: string; codeVerifier: string; state: string; } interface BeginConnectionInput { callbackUrl: string; scopes: readonly StuffboxScope[]; clientId?: string; savePending(value: PendingConnection): Promise; } export async function beginConnection( input: BeginConnectionInput, ): Promise { const pkce = await createPkcePair(); const state = generateState(); const common = { callbackUrl: input.callbackUrl, codeChallenge: pkce.challenge, scopes: input.scopes, state, }; const connection = await stuffbox.createConnectionRequest( input.clientId ? { ...common, clientId: input.clientId } : common, ); await input.savePending({ clientId: connection.clientId, callbackUrl: connection.callbackUrl, codeVerifier: pkce.verifier, state, }); return connection.authorizationUrl; } interface FinishConnectionInput { code: string; returnedState: string; pending: PendingConnection; } export async function finishConnection( input: FinishConnectionInput, ): Promise { if (input.returnedState !== input.pending.state) { throw new Error("Stuffbox connection state did not match"); } return stuffbox.exchangeAuthorizationCode({ clientId: input.pending.clientId, code: input.code, codeVerifier: input.pending.codeVerifier, redirectUri: input.pending.callbackUrl, }); } export async function loadEntireLibrary(accessToken: string): Promise { const media = new StuffboxClient({ baseUrl: "https://stuffbox.xyz", accessToken, }); const assets: Asset[] = []; let cursor: string | undefined; do { const page = await media.listAssets({ limit: 100, ...(cursor ? { cursor } : {}), }); assets.push(...page.items); cursor = page.nextCursor; } while (cursor); return assets; }