@preezie/widget-sdk
v2026.7.1
Published
Browser client + Node minting helper for the Preezie Widget secure endpoints.
Readme
@preezie/widget-sdk
Browser client + Node minting helper for the Preezie Widget secure operation endpoint
(POST /api/public/operation), Model 2 of the secure-public-widget-endpoints change.
The operation signing key never reaches the browser. Merchants mint short-lived (5 min) HS256 tokens
on their server with the ./server entry; the browser entry only ever carries and refreshes tokens.
Browser
import { createOperationClient } from "@preezie/widget-sdk";
const client = createOperationClient({
tenantId: TENANT_ID,
tokenUrl: "/widget-token", // or: getToken: async () => (await fetch("/widget-token")).text()
});
// First-party widget consumes the streamed response body:
const res = await client.operation({ intent, content /* ... */ });
for await (const chunk of res.body) render(chunk);The controller's specialized streamed operations use the same token and transport behavior:
await client.search(request); // POST /api/Operation/search
await client.similarTo(request); // POST /api/Operation/similar-to
await client.compare(request); // POST /api/Operation/compare
await client.bundle(request); // POST /api/Operation/bundleEach method returns the raw Response without consuming its body.
The client attaches Authorization: Bearer + Tenantid, refreshes before the 5-minute expiry
(single-flight), and retries once on a 401. The browser sets Origin; the Widget API validates it.
Server (Node)
import { tokenHandler } from "@preezie/widget-sdk/server";
// The ONLY place the signing key is used.
app.get("/widget-token", tokenHandler({
tenantId: TENANT_ID,
signingKey: process.env.PREEZIE_OP_SIGNING_KEY, // Base64, server-only
}));Or mint directly: mintToken({ tenantId, signingKey }).
Non-Node stacks: mint the token yourself to the contract in
openspec/changes/widget-sdk/merchant-integration.md.
