npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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/browser

For 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/events
  • GET /v1/client/customer/me
  • GET /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 baseUrl only for a custom, staging or private endpoint.
  • Custom baseUrl values must use HTTPS unless they are localhost or allowInsecureHttp is set for local testing.
  • Runtime options named apiKey, serverApiKey, secret or signingSecret are rejected before any network request.
  • Browser tokens should be kept in memory and refreshed through getToken.
  • Customer streams require getStreamToken so a regular bearer token is not put into an EventSource URL.
  • Non-2xx API responses throw PerkamoApiError with status, parsed body, requestId, retryAfter and rateLimit metadata 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.