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

@deltabreen/help-server

v0.1.1

Published

Help by DeltaBreen for your server: sign the token for a logged-in user, and answer the calls Help makes to your URL for the side panel, buttons and webhooks. No dependencies.

Readme

Help by DeltaBreen for your server (Node)

Two things a site's server does for Help, in one file with no dependencies:

  • Logged-in users. Sign the short token that tells the form who is logged in, so they write in without typing their email and see their own tickets.
  • Your URL. Answer the calls Help makes to it: the side panel your team sees beside a ticket, the buttons they press, and your webhooks' events. The signature is checked before anything is read.

Runs on Node 22 and later, Next.js, Hono, Cloudflare Workers, Bun and Deno.

npm install @deltabreen/help-server

Logged-in users

Make the secret on your site's Install tab, under Recognise logged-in customers, and put it in your server's environment as HELP_IDENTITY_SECRET. Then answer the address the script asks:

import { identityResponse } from '@deltabreen/help-server'

// Next.js: app/help-token/route.js
export async function GET() {
  const user = await currentUser()            // yours: null when nobody is logged in
  return identityResponse(user && { id: user.id, email: user.email, facts: { plan: user.plan } })
}
<script src="https://help.deltabreen.eu/embed/v1.js" data-help-key="pk_live_…" data-help-identity-url="/help-token" async></script>

signIdentity(user, options) gives the token itself, for a page that hands it over with DeltaHelp.identify. A token lasts ten minutes and works once; the form asks for a new one when it needs one. In an app's web view, which hands one token over before the page loads, pass { once: false }.

| user | | |---|---| | id | Required. Your own id for them, text or a whole number, up to 128 characters. Your URL is sent it back. | | email | Optional. Replies go here too. | | facts | Optional. Up to ten short values your team sees on their tickets, like { plan: 'gold' }: names of lowercase letters, digits and _, values of text (up to 100 characters), a number, or true or false. |

Your URL

Add an integration on your site's Integrations page with your URL, and put the secret it shows you in your server's environment as HELP_CONNECTOR_SECRET (a webhook's as HELP_WEBHOOK_SECRET; one URL can take both).

import { createHandler } from '@deltabreen/help-server'

const help = createHandler({
  // The side panel: what your team sees beside this person's ticket, or null if you don't know them.
  async hydrate({ user, mode }) {
    const account = await accounts.find(user.id)
    return account && { name: account.name, plan: account.plan, renews: account.renewsAt }
  },
  // A button your team pressed, by the key you gave it. Done once per press, however often the call arrives.
  async action({ user, action }) {
    if (action.key === 'cancel_subscription') {
      const done = await billing.cancel(user.id)
      return { ok: true, message: 'Cancelled.', reference: done.id }
    }
    return { ok: false, message: 'Not a button we know.' }
  },
  // A webhook's event, once per event.
  async event(event) {
    if (event.type === 'ticket.created') await chat.post(`New ticket #${event.ticket.number}`)
  },
})

Then give it the request as your framework has it, with the body unread, since the signature is over its bytes:

// Next.js App Router: app/support/hook/route.js
export const POST = (request) => help.fetch(request)

// Hono
app.post('/support/hook', (c) => help.fetch(c.req.raw))

// Cloudflare Workers: there is no process.env, so pass the secret
export default { fetch: (request, env) => createHandler({ secrets: env.HELP_CONNECTOR_SECRET, hydrate }).fetch(request) }

// Express: the raw body, never express.json(), on this route
app.post('/support/hook', express.raw({ type: '*/*' }), help.node)

// Node's own server
http.createServer(help.node).listen(8787)

The connection test (Help's handshake) is answered for you. A call whose signature does not check out is answered 401 before its body is read. mode is live or test, the key the ticket came through; createHandler({ mode: 'live' }) answers only calls about live tickets.

Several copies of your server. Presses and events are remembered in memory, which is enough for one copy. With more, give a store they share:

const store = {
  async claim(key, ttl) {
    if (await redis.set(key, 'busy', { NX: true, EX: 60 })) return { state: 'new' }
    const had = await redis.get(key)
    return had === 'busy' ? { state: 'busy' } : { state: 'done', result: had }
  },
  finish: (key, result, ttl) => redis.set(key, result, { EX: ttl }),
  release: (key) => redis.del(key),
}

The contract

What goes over the wire is in Integrations and the form's guide. This library is held to the same published vectors as Help's own server: signature-vectors.json and identity-vectors.json.