Skip to content

Repository files navigation

@dszp/netsapiens-lib

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) plus NsWriteClient, a separate write client (device provisioning). Both are bearer-auth with an injectable fetch; holding the read client still cannot write.
  • JWT (ns_t) validationverify() (cheap local format gate → cached live /jwt check) and validateJwtFormat(). Pluggable VerdictCache (inject the Workers Cache API / KV / DO; MemoryVerdictCache for dev). Anti-overload by design — a bad/expired token never hits the server.
  • Call-flow resolver + renderersresolveFlow(snapshot, ref) walks a NetSapiens domain snapshot into a normalized FlowGraph; toMermaid() renders it to a Mermaid flowchart; renderGalleryHtml() / renderFlowCards() return HTML strings the caller can place anywhere.
  • Identity + policytoPrincipal() normalizes a validated token into an effective identity (masking-aware: the effective user is the masked one, the operator is the reseller behind a mask_chain), and can() / isAllowed() gate features against it with a declarative, fail-closed policy. So "who is this, and may they?" isn't re-invented per consumer.
  • ThemesTHEMES, 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.

Install

npm install @dszp/netsapiens-lib      # or: pnpm add / yarn add

ESM-only, zero runtime dependencies, ships its own types.

Usage

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]);

What NsClient covers

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/v2

That'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.

Read/write split by charter

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.

Which writes actually confirm: synchronous

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 UPDATE

path 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.

Configuration binds to your deployment

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 your ns_t, e.g. manage.example.com. Pass an array when one backend is fronted by several portal hostnames (exact match, no wildcards), or validateIss: false to opt out deliberately.

aud defaults to "ns" because that value is fixed by the NetSapiens platform and true for everyone.

Develop

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.

Docs

  • ARCHITECTURE.md — module boundaries, why the live /jwt call 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

License

MIT © David Szpunar

About

Portable, Node-free NetSapiens toolkit: read-only API client, ns_t validation, call-flow diagrams

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages