@perkamo/browser
v0.8.0
Published
Browser-safe Perkamo client and widget helpers.
Readme
@perkamo/browser
Browser-safe Perkamo client and lightweight widget helpers.
Use this package only with short-lived tokens returned by your own backend after it calls Perkamo. Never put a Perkamo server API key in browser, mobile, or embedded widget code. This package is preview and requires Perkamo client routes to be enabled for the integration.
Full SDK documentation: https://www.perkamo.com/docs/v1/sdk
npm install @perkamo/browserFor storefronts or widgets without a bundler, load the standalone browser build from the free jsDelivr npm CDN. Pin the package version so production pages load a reviewed browser bundle:
<script src="https://cdn.jsdelivr.net/npm/@perkamo/[email protected]/dist/perkamo-browser.global.min.js"></script>The CDN build exposes window.PerkamoBrowser. For production storefronts that
need an alternative CDN, UNPKG serves the same npm package:
https://unpkg.com/@perkamo/[email protected]/dist/perkamo-browser.global.min.js.
Client
Browser integrations are always a backend plus frontend implementation. The
frontend calls your own backend token route first; that backend route verifies
the user's application session, calls Perkamo with a server key and returns a
short-lived browser token.
/api/perkamo/token in the examples is your application route, not a Perkamo API
route.
The Symfony bundle provides this backend token route out of the box. Other
backends should call POST /v1/browser-tokens from trusted server code.
The browser client then calls preview /v1/client/* routes with the short-lived
token. Until those routes are enabled for an integration, return already-filtered
customer state from your own backend instead.
Frontend setup with a bundler:
import { createPerkamoBrowserClient } from "@perkamo/browser";
const perkamo = createPerkamoBrowserClient({
getToken: async () => {
const response = await fetch("/api/perkamo/token", {
method: "POST",
credentials: "include",
});
if (!response.ok) throw new Error("Unable to create Perkamo token");
return (await response.json()).token;
},
});
await perkamo.emit("page.viewed", { path: location.pathname });
const customer = await perkamo.getCustomerJson();
document.querySelector("#points").textContent = String(customer.wallets.points ?? 0);Frontend setup with the CDN build:
<script src="https://cdn.jsdelivr.net/npm/@perkamo/[email protected]/dist/perkamo-browser.global.min.js"></script>
<script>
const perkamo = PerkamoBrowser.createPerkamoBrowserClient({
getToken: () =>
fetch("/api/perkamo/token", { method: "POST", credentials: "include" })
.then((response) => response.json())
.then((body) => body.token),
});
perkamo.getCustomerJson().then((customer) => {
document.querySelector("#points").textContent = String(
customer.wallets.points ?? 0,
);
});
</script>The client calls browser-token routes:
POST /v1/client/eventsGET /v1/client/customer/meGET /v1/client/customer/me/stream
These routes require a short-lived bearer token issued by the customer's backend.
They are preview routes, not a replacement for the stable backend-first v1 API.
The browser package rejects server-authoritative event context fields such as
xp, wallet, wallets, level, perks, rewards and achievements.
Customer helpers:
| Method | Purpose |
| ------------------- | -------------------------------------------------------------------------- |
| getCustomer() | Returns the raw typed customer response from GET /v1/client/customer/me. |
| customer() | Alias for getCustomer(). |
| getCustomerJson() | Returns a JSON-safe customer snapshot for app/storefront rendering. |
| customerJson() | Alias for getCustomerJson(). |
getCustomerJson() omits traits by default because customer traits can contain
personal data. Opt in only when the current page needs them:
const customer = await perkamo.getCustomerJson({ includeTraits: true });Security defaults:
- The client defaults to the hosted Perkamo API. Set
baseUrlonly for a custom, staging or private endpoint. - Custom
baseUrlvalues must use HTTPS unless they are localhost orallowInsecureHttpis set for local testing. - Runtime options named
apiKey,serverApiKey,secretorsigningSecretare rejected before any network request. - Browser tokens should be kept in memory and refreshed through
getToken. - Customer streams require
getStreamTokenso a regular bearer token is not put into an EventSource URL. - Non-2xx API responses throw
PerkamoApiErrorwithstatus, parsedbody,requestId,retryAfterandrateLimitmetadata when returned by the API or gateway.
const perkamo = createPerkamoBrowserClient({
getToken: () => fetchJson("/api/perkamo/token").then((body) => body.token),
getStreamToken: () =>
fetchJson("/api/perkamo/stream-token").then((body) => body.token),
});
const subscription = perkamo.subscribeCustomer((customer) => {
renderCustomer(customer);
});Progress Widget
import {
createPerkamoBrowserClient,
mountPerkamoProgressWidget,
} from "@perkamo/browser";
const client = createPerkamoBrowserClient({
getToken: () =>
fetch("/api/perkamo/token", { method: "POST" })
.then((r) => r.json())
.then((r) => r.token),
});
mountPerkamoProgressWidget({
client,
target: "#perkamo-progress",
});The widget is intentionally small and unstyled. Customers should own the final presentation in their app or storefront theme.
