baja-client
v0.4.0
Published
Browser SDK for the Baja Fulfillment public API (OAuth 2.1 Authorization Code + PKCE).
Readme
baja-client
Browser SDK for the Baja Fulfillment public API. Handles the OAuth 2.1 Authorization Code + PKCE flow end-to-end — login redirect, token exchange, storage, silent refresh (with rotation) — and exposes typed methods that mirror the public API.
Framework-agnostic and zero runtime dependencies (native fetch + Web
Crypto).
Install
npm install baja-clientQuick start
import { BajaClient } from "baja-client";
const bf = new BajaClient({
clientId: "app_…", // from the BF developer portal
redirectUri: `${location.origin}/callback`, // must be registered on the app
scopes: ["openid", "profile", "public_api:read"],
baseUrl: "https://api.bajafulfillment.com", // omit in prod; set for local dev
});
// 1. Kick off login (e.g. from a "Sign in" button) — navigates to BF.
await bf.loginWithRedirect();
// 2. On your /callback route, finish the exchange:
await bf.handleRedirectCallback();
// 3. Call the API — the access token is attached and refreshed automatically:
const me = await bf.whoami(); // { tokenId, name, scopes }
const hello = await bf.ping(); // "hello world"
// Check-in report (Day › Employee › movement) for a date range:
const report = await bf.checkIn.report({
from: "2026-05-10",
to: "2026-05-17",
plantId: 1,
// employeeNumber: "100245", // optional
});
// Rename history for an area/activity (current name + recorded renames):
const history = await bf.checkIn.nameHistory({ type: "area", id: 5 });
// Live "today" board: full snapshot first, then poll deltas with the cursor.
let board = await bf.checkIn.live({ plantId: 1 });
// …later, on refresh — returns only what changed:
board = await bf.checkIn.live({ plantId: 1, since: board.cursor });
// board.changes: status 'active' → upsert by checkInId; 'ended' → remove.
const res = await bf.fetch("/public/v1/whoami"); // low-level escape hatchAPI
| Method | Description |
| ----------------------------- | ----------------------------------------------------------------------------------------------- |
| new BajaClient(config) | Configure with clientId, redirectUri, scopes, baseUrl, storage. |
| loginWithRedirect() | Generate PKCE + state and navigate to the authorize endpoint. |
| handleRedirectCallback() | Validate state, exchange the code, store tokens, clean the URL. |
| isAuthenticated() | Whether a usable session exists. |
| getAccessToken() | A valid access token, refreshing silently if needed. |
| whoami() / ping() | Typed calls mirroring GET /public/v1/whoami and GET /public/v1. |
| checkIn.report(params) | GET /public/v1/check-in/report — Day › Employee › movement for a range. Needs checkin:read. |
| checkIn.nameHistory(params) | Rename history + current name for an area/activity. Needs checkin:read. |
| checkIn.live(params) | Live "today" board — full snapshot, or deltas via since cursor. Needs checkin:read. |
| fetch(path, init?) | Call any public API path with auth + one-shot refresh-on-401. |
| logout() | Clear the local session. |
Token storage
By default tokens live in localStorage (survive reloads). Pass
storage: "memory" for in-memory only, or a custom TokenStore:
new BajaClient({ /* … */, storage: "memory" });Security notes
- PKCE (
S256) is always used; the SDK never holds a client secret (public client). - Refresh tokens rotate on every use and the server applies reuse-detection, so a leaked refresh token is single-use.
stateis validated on the callback to defeat CSRF.- Access tokens are short-lived; the SDK refreshes them transparently.
Keeping the SDK in sync with the public API
The auth core is stable; the typed API methods mirror PublicApiController.
As the public API grows, regenerate types from bf-api's OpenAPI spec (served at
/public/docs-json, the same surface as /public/docs):
# bf-api must be running; override the URL for staging/prod
pnpm codegen
# or: npx openapi-typescript https://api.bajafulfillment.com/public/docs-json \
# -o src/generated/public-api.d.tsThen add a thin typed method for the new endpoint (typed from the generated
src/generated/public-api.d.ts) and ship a new SDK version. The generation step
is what keeps the SDK honest as endpoints are added — the hand-written methods
just provide the ergonomic surface (bf.whoami() vs bf.fetch(path)).
Publishing
pnpm version patch # or minor / major — bumps version + git tag
pnpm publish # prepublishOnly runs the build firstprepublishOnlybuildsdist/automatically; onlydist/is published (filesfield).- The package is unscoped (
baja-client); to publish to a private registry or under a scope (@bajafulfillment/client), set the name +publishConfig.registryaccordingly and update consumers' install/importpaths. - Consumers then
npm install baja-client@^<version>(see the demo app).
