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

@payhook/extension

v0.11.0

Published

Payhook client for browser extensions — hook lifecycle, hosted checkout, and entitlements

Readme

@payhook/extension

Payhook's browser-extension SDK for hosted checkout, Stripe subscription management, trials, metered feature usage, and paid feature access without license keys.

Install

npm install @payhook/extension

Create a publishable phk_live_… or phk_test_… key in the Payhook dashboard. The prefix selects the Stripe environment; bind the key to your published Chrome extension ID.

Recommended Manifest V3 architecture

Use one headless PayhookSession in the background service worker. Popups, options pages, and content scripts should ask the background to perform payment actions and read the persisted access snapshot instead of creating competing SDK clients.

import { PayhookSession } from '@payhook/extension'

const payhook = new PayhookSession()

await payhook.config('phk_live_xxx', {
  version: chrome.runtime.getManifest().version
})

payhook.on('change', (entitlement) => {
  console.log('Paid access changed', entitlement.active)
})

const access = await payhook.getAccessState()

if (access.active) {
  // Paid entitlement or active trial.
}

The dashboard is authoritative for product release channels, trial policy, branding, labels, and eligible plans. The SDK caches this configuration and revalidates it with ETag.

Open hosted payment flows

Keep window creation in the background session:

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  let action

  if (message.command === 'payhook:upgrade') {
    action = payhook.openUnlock({ waitForClose: false })
  } else if (message.command === 'payhook:manage') {
    action = payhook.openManagePlan({ waitForClose: false })
  }

  if (!action) return

  action
    .then(sendResponse)
    .catch((error) => sendResponse({ error: error.message }))
  return true
})

openUnlock() opens unlock.payhook.link with the install hook, extension ID, version, environment, and optional email. Closing the hosted popup triggers an entitlement refresh.

DOM-aware integration

For a simple persistent page with one SDK owner, use the DOM singleton:

import { payhook } from '@payhook/extension'
import '@payhook/extension/upgrade-button.css'

await payhook.config('phk_live_xxx')
payhook.mount(document.querySelector('#upgrade'))

The singleton also automatically mounts buttons on [data-payhook-unlock] elements.

Do not configure the singleton independently in multiple short-lived popup or content-script contexts. Use PayhookSession when access is shared across extension contexts.

Access state

const {
  active,
  entitlement,
  trial,
  config
} = await payhook.getAccessState()

payhook.isEntitled() // Paid access only.
payhook.isInTrial()  // Active SDK trial only.
payhook.hasAccess()  // Paid access or active trial.

Usage-based fallback

Configure rolling limits for one or more features. Paid customers and active trials bypass these counters; after the trial ends, consumeFeature() grants access until either configured weekly or monthly limit is reached.

await payhook.config('phk_live_xxx', {
  featureUsage: {
    features: {
      pro: { weekly: 100, monthly: 300 }
    }
  }
})

const result = await payhook.consumeFeature('pro')
if (!result.allowed) {
  // Show the upgrade UI. result.usage identifies the period that was hit.
}

payhook.getFeatureUsage('pro')
payhook.canUseFeature('pro')

// Duration-based products can consume multiple units at once, then open a
// Payhook-hosted usage breakdown that also links to the unlock flow.
await payhook.consumeFeature('pro', { amount: elapsedWholeMinutes })
await payhook.openFeatureUsage({ feature: 'pro', unit: 'minute' })

The counters are intentionally approximate and client-side. Events are stored under the existing live/test Payhook storage slots. Product telemetry is also buffered in that storage and sent only when usage exists: after 15 minutes, after 15 accumulated units, when a cap is hit, or when explicitly flushed. Failed deliveries stay queued with a five-minute retry cooldown. Each tracking request carries an aggregate amount, so Payhook records actual usage units instead of counting network requests.

Usage during an active trial or paid entitlement is buffered the same way for product analytics, but it never consumes feature credits. Payhook segments the three usage modes as feature_usage_used_<feature>, feature_usage_trial_<feature>, and feature_usage_entitled_<feature>.

When Chrome exposes a signed-in profile id, the SDK hashes it locally with the extension id and includes a rolling recovery checkpoint in the same batched request. Payhook never receives the raw profile id or email. A fresh reinstall does one restore lookup and seeds an empty local ledger from that checkpoint; during normal use the local ledger remains authoritative and offline-capable.

Flush a short remaining session when your product becomes inactive or the extension service worker is about to suspend:

await payhook.flushFeatureUsageTelemetry({ force: true })

The dashboard /v1/config response can provide the same featureUsage shape when limits should be managed remotely.

For development, support, or a deliberate grant, resetCredits() additively stamps a reset point for the usage history and restarts the configured trial:

await payhook.resetCredits()

The SDK persists sanitized live/test entitlement snapshots for fast startup and offline continuity. Stripe remains authoritative for subscription lifecycle; Payhook selects the modern entitlement exactly once. Initialization loads the snapshot before refreshing; network errors, 408, 429, and 5xx responses keep the last good snapshot without advancing checkedAt. Explicit pull() calls still reject, allowing the extension to show stale/offline state when useful. Stored snapshots contain only entitlement fields and Stripe product/price identifiers—never email or Checkout Session data.

Chrome manifest

{
  "permissions": ["storage", "identity", "identity.email"],
  "host_permissions": ["https://api.payhook.link/*"],
  "externally_connectable": {
    "matches": ["https://payhook.link/*", "https://*.payhook.link/*"]
  }
}

The native Chrome adapter reads and writes only the payhook sync-storage key, reducing cross-feature storage races.

Storage and compatibility

  • The payment hook ID is canonical and separated into live/test slots.
  • The Customer Portal endpoint is derived as ${apiUrl}/v1/payment_hooks/${paymentHookId}/manage_plan.
  • Historical managePlanUrl and billingPortalUrl fields remain readable during compatibility windows but are not canonical state.
  • Trial configuration and clocks are persisted independently for live/test.
  • Feature usage events and reset points are persisted independently for live/test. Old events remain readable after a reset for rollback/debugging.
  • Recovery checkpoints are keyed by an extension-scoped SHA-256 profile hash; raw Chrome identity values are never persisted by Payhook.
  • Migrations should copy older state additively and dual-write rollback shadows for a bounded window.

Main exports

  • PayhookSession — headless background owner.
  • payhook — DOM-aware singleton.
  • PayhookClient — lower-level client for custom lifecycle control.
  • UpgradeButton and mountUpgradeButton() — custom rendering.
  • createChromeAdapters() — native key-scoped Chrome storage, identity, and window adapters.
  • Trial, hook-storage, and entitlement-storage helpers for explicit migrations.
  • Feature-usage helpers for rolling caps and additive credit resets.

Documentation

License

ISC