Client-side applications

Use the SDK directly in the browser when your application has no server session boundary.

A client-side application is an execution model, not a framework. Use it when the browser renders the application and calls Cavuno directly. Public catalog reads need only a publishable board key; authenticated calls require an explicit browser storage decision.

Create the browser client

Expose only the board key through your build tool:

dotenv
VITE_CAVUNO_BOARD=pk_your_32_character_hex_key
ts
import { createBoardClient } from '@cavuno/board';
export const board = createBoardClient({
board: import.meta.env.VITE_CAVUNO_BOARD,
auth: { storage: 'session' },
});

The SDK connects to https://api.cavuno.com by default. session keeps the login in the current tab. Use memory to clear it on reload. Use local only when cross-tab persistence justifies making the tokens available to JavaScript for longer.

Load public data with cancellation

ts
export async function loadJobs(signal?: AbortSignal) {
return board.jobs.list(
{ limit: 20, sort: 'newest' },
{ signal },
);
}

Abort a request when navigation or a newer search makes it irrelevant. An empty data array is a successful empty result; a thrown request belongs in the application’s error state.

Refresh one session at a time

Refresh tokens rotate and are single-use. Share one in-flight refresh across callers:

ts
import { isUnauthorized } from '@cavuno/board';
let refreshInFlight: Promise<void> | null = null;
export async function withCurrentSession<T>(run: () => Promise<T>) {
try {
return await run();
} catch (error) {
if (!isUnauthorized(error)) throw error;
refreshInFlight ??= board.auth.refresh().then(() => undefined).finally(() => {
refreshInFlight = null;
});
await refreshInFlight;
return run();
}
}

If refresh fails with 401, stop and show the signed-out state. Never recursively retry a spent refresh token.

Security boundary

  • Web Storage tokens are readable by JavaScript. Use a strict content security policy and avoid unsafe HTML injection.
  • Browser variables are public. A pk_… key may be exposed; bearer tokens and Cavuno operator credentials may not.
  • Gate optional navigation with board.context().features, while still handling an API denial if configuration changes.
  • Never put authenticated responses in a shared cache.

Verify the client-side boundary

  1. Reload after login and confirm session storage persists only in that tab.
  2. Open a second tab and confirm it does not inherit the login.
  3. Expire an access token, start parallel authenticated requests, and confirm only one refresh runs.
  4. Sign out and confirm board.me.retrieve() produces a typed unauthorized error.
  5. Test the deployed origin in a real browser so CORS and content-security behavior match production.

Prefer a framework-owned httpOnly cookie when your framework has a server boundary. Continue with Authentication and session ownership for the complete model.