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

@dszp/888voip-lib

v0.1.1

Published

Portable, Node-free 888VoIP Channel Advantage toolkit: a read-only API client, an injected cache interface, and the normalisations their API's quirks require. Runs unchanged in a Cloudflare Worker, Node, or the browser.

Readme

@dszp/888voip-lib

Portable, Node-free toolkit for the 888VoIP Channel Advantage API: a read-only client, an injected cache interface, and the normalisations their API's quirks require. Zero dependencies. Runs unchanged in a Cloudflare Worker, Node, or the browser.

Install

pnpm add @dszp/888voip-lib

Use

import { VoipClient, memoryCache, poRefOf } from '@dszp/888voip-lib';

const client = new VoipClient({
  baseUrl: 'https://api.888voip.com',
  token: process.env.VOIP888_TOKEN!,
  cache: memoryCache(),
});

const page = await client.getOrders({ newestFirst: true });
for (const order of page.orders) {
  console.log(order.orderNumber, order.orderStatus, poRefOf(order));
}

One cache, two servers, and the keys must say which is which — the client scopes every key to the host of its baseUrl, so running staging beside production over a single KV namespace is safe. Pass cacheNamespace only to separate two accounts on one host, and never pass the token: keys get logged, listed and enumerated.

⚠️ If you use 888VoIP's provisioning service, a cached order carries live SIP credentials. An order placed with a provisioning block reads back with that block intact — your provisioning server's srvUser/srvPass, and a login/pin per extension — so anything that caches the order is now storing them. Back the cache with something private and short-lived, and do not log what it round-trips. Orders placed without a provisioning block carry no credentials, and the field is absent rather than empty.

Read only

VoipClient exposes only GETs, and this package ships no write client. Hold one and you know it cannot place an order. Credentials are minted and revoked by createToken / revokeToken in their own module, never as methods on the client.

Caching is not optional in practice

The upstream per-minute limits are tight — a single order is 5/min, the order and product lists 10/min. Pass a cache:

| Runtime | What to pass | |---|---| | Cloudflare Worker | a thin adapter over a KV namespace | | Node script, tests | memoryCache() | | No caching | omit it, and mind the limits |

VoipCache is two methods over strings — get(key) and put(key, value, ttlSeconds) — so nothing here is bound to any one runtime. The TTLs and key format are the client's business, not yours; pass onCacheRead if you want to see whether the cache answered a given call, which under limits this tight is the only view you have of your own headroom.

const kvCache = {
  get: (k: string) => env.VOIP_CACHE.get(k),
  put: (k: string, v: string, ttl: number) => env.VOIP_CACHE.put(k, v, { expirationTtl: ttl }),
};

Two behaviours worth knowing

An account with no orders answers HTTP 400, not an empty list. getOrders maps that one message to an empty page; any other 400 still throws.

getOrder returns null for a missing order rather than throwing, because "this account cannot see that order" is an ordinary answer — an order placed on a sister company's account, or one predating the API, reaches you as null. Upstream signals that with 400 {"message":"Order not found."} rather than a 404, so both are read as not-found. A 500, or a 400 saying anything else, still throws: not-found and upstream-broken are different facts.

API

| Export | What it does | |---|---| | VoipClient | getProducts, getProduct, getCategories, getOrders, getOrder, getPrivateWarehouses | | createToken / revokeToken / revokeAllTokens | Credential management. Tokens never expire; revocation is the only rotation. | | memoryCache | An in-process VoipCache, for Node scripts and tests. Not for a Worker — module state dies with the isolate. | | poRefOf | The purchase-order reference you gave the vendor, from either of the two field names it arrives under | | normalizeProduct, normalizeCategories, decodeHtmlEntities, htmlToMarkdown | The upstream quirks, handled | | VoipApiError | Carries status and body | | VoipShapeError | A 200 that is not the envelope the endpoint documents — upstream answered, just not with something readable |

License

MIT