Portable, Node-free NetSapiens toolkit. The same code runs unchanged in a Cloudflare Worker, in
Node, or in the browser — it uses only Web APIs (fetch, atob, TextDecoder, crypto.subtle),
never node:*.
Five capabilities, one dependency-free package:
- NS API v2 client (read + write) —
NsClient(read-only:get()+fetchDomainSnapshot(client, domain)which assembles a routing-relevant domain snapshot) plusNsWriteClient, a separate write client (device provisioning). Both are bearer-auth with an injectablefetch; holding the read client still cannot write. - JWT (
ns_t) validation —verify()(cheap local format gate → cached live/jwtcheck) andvalidateJwtFormat(). PluggableVerdictCache(inject the Workers Cache API / KV / DO;MemoryVerdictCachefor dev). Anti-overload by design — a bad/expired token never hits the server. - Call-flow resolver + renderers —
resolveFlow(snapshot, ref)walks a NetSapiens domain snapshot into a normalizedFlowGraph;toMermaid()renders it to a Mermaid flowchart;renderGalleryHtml()/renderFlowCards()return HTML strings the caller can place anywhere. - Identity + policy —
toPrincipal()normalizes a validated token into an effective identity (masking-aware: the effective user is the masked one, theoperatoris the reseller behind amask_chain), andcan()/isAllowed()gate features against it with a declarative, fail-closed policy. So "who is this, and may they?" isn't re-invented per consumer. - Themes —
THEMES, a vendor-neutral registry (node palettes + Mermaid base/look + app chrome) as plain data. Add one here and every host picks it up; nothing is bound to one deployment's brand.
npm install @dszp/netsapiens-lib # or: pnpm add / yarn add
ESM-only, zero runtime dependencies, ships its own types.
import { resolveFlow, toMermaid, renderGalleryHtml, verify, NsClient } from '@dszp/netsapiens-lib';
const graph = resolveFlow(snapshot, { kind: 'did', ref: '13175550100' });
const mermaid = toMermaid(graph);
const html = renderGalleryHtml(snapshot.meta.domain, [graph]);NsClient is deliberately not an enumeration of endpoints — it has exactly one method:
client.get<T>(path, query?) // any GET under https://{server}/ns-api/v2That's the whole surface. Any v2 read is reachable (/domains, /domains/{d}/users,
/domains/{d}/users/{ext}/devices, …) without this library needing to know about it, and one choke
point is what makes the read-only property below checkable rather than a promise. NetSapiens versions
drift; consult your server's own /ns-api/apidoc/ for the paths it offers.
Two composites are provided because they're multi-read and worth getting right once:
| Function | Reads |
|---|---|
listDomains(client) |
/domains → {domain, description, locked}[] |
fetchDomainSnapshot(client, domain, opts?) |
/domains/{d} plus, in parallel, timeframes, users, callqueues, phonenumbers, autoattendants — then per-user answerrules. Individual reads fail soft (a missing collection yields [], not a thrown snapshot). |
The snapshot is the routing subset — what resolveFlow() needs. It is not a full domain export.
NsClient exposes get() and nothing else, and verify() only ever issues GET /jwt. That is a
deliberate boundary, not a missing feature: this library is built for tools that visualize and audit a
NetSapiens domain, where "it cannot possibly write" is a property worth having structurally rather
than by convention. Writes live in a separate class — NsWriteClient, a small, explicitly-reviewed
surface (device provisioning) — never as new methods on NsClient. So a consumer that holds the read
client still cannot write; that guarantee holds by construction, not by convention.
synchronous: 'yes' asks the API to finish the write before replying, so you get 200 with the
resulting resource inline — including server-generated fields you could not otherwise learn without a
second read, a new device's SIP registration password being the worked example. Without it you get
202 Accepted and a bare {code, message}.
It is a per-operation capability, not a global one: exactly 17 operations declare it in the v2 specification (core 44.4.10), and almost all of them are creates. Sending it anywhere else is inert — NetSapiens ignores unrecognized body fields and still answers 202 — so code that adds it everywhere merely looks as though its writes are confirmed.
NsWriteClient therefore injects the flag only where it is accepted, and exports the table so other
NetSapiens clients can share one answer instead of each keeping a copy that drifts:
import { supportsSynchronous, SYNCHRONOUS_OPERATIONS } from '@dszp/netsapiens-lib';
supportsSynchronous('POST', '/domains/acme.example/users'); // true — user CREATE
supportsSynchronous('PUT', '/domains/acme.example/users/100'); // false — user UPDATEpath is the concrete request path relative to /ns-api/v2, dynamic segments already URI-encoded.
The most consequential absence is that user update is not on the list even though user create is:
there is no response that can confirm a user update, so confirm it by reading the record back.
Two values are required and have no defaults, on purpose — a default would silently bind you to someone else's portal:
NsClient({ server })— your NS API host, e.g.api.example.com.verify(token, { expectedIss })— the Manager Portal host that issues yourns_t, e.g.manage.example.com. Pass an array when one backend is fronted by several portal hostnames (exact match, no wildcards), orvalidateIss: falseto opt out deliberately.
aud defaults to "ns" because that value is fixed by the NetSapiens platform and true for everyone.
pnpm install
pnpm build # tsc → dist/
pnpm test # the offline suite — green with no credentials, no setup
The build (tsconfig.json) omits @types/node on purpose: a stray node:* import fails the build,
which is how the Node-free guarantee is enforced.
pnpm test:ns <snapshot.json> is separate and not part of pnpm test: it needs a real domain
snapshot, which is customer data and correctly absent from this repo.
- ARCHITECTURE.md — module boundaries, why the live
/jwtcall is the signature authority, the Mermaid rendering traps, and the NetSapiens routing model the resolver decodes. - CONTRIBUTING.md — the rules: fictional fixtures, no deployment-binding defaults, doc comments are published API, Node-free.
- CHANGELOG.md
MIT © David Szpunar