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

@imperosoft/cris-webui-ch5-core

v1.6.2

Published

CRIS WebUI CH5 Core - Crestron CH5/CrComLib and custom-WebSocket integration for React

Readme

@imperosoft/cris-webui-ch5-core

CRIS WebUI Core — Crestron integration for React (Zustand stores + hooks) with two selectable transports:

  • ch5 — Crestron native stack: CrComLib on-panel, WebXPanel in-browser. For panels that run CH5 user projects (TS-x70, TS-1080, …).
  • ws — custom WebSocket protocol against the C# module on the processor. For panels that only load a plain web page (TSS-1080 scheduling panels, kiosks, any browser).

Same app, same hooks, same stores on both transports. Scope on ws: global joins + custom objects (smart objects are ch5-only — their single production use, the custom-object pipe on SO 10001, is native JSON over the websocket).

Initialization

import { initBridge } from '@imperosoft/cris-webui-ch5-core';

await initBridge({
  transport: 'auto',        // 'ch5' | 'ws' | 'auto' (default 'auto')
  debug: true,
});

Transport resolution ('auto')

  1. Explicit transport: 'ch5' | 'ws' in the config — always wins.
  2. URL query param ?transport=ws|ch5 — how a web-only panel opts in without an app rebuild: point it at http://processor:port/?transport=ws.
  3. Vite define __CRIS_TRANSPORT__ (dev only — applied when the page runs on localhost).
  4. Fallback ch5 — a plain browser cannot be distinguished from a web-only panel, so auto preserves the pre-1.5 behavior and every existing deployment upgrades unchanged.

Config fields

| Field | Transport | Meaning | |---|---|---| | transport | — | 'ch5' \| 'ws' \| 'auto' (default 'auto') | | processorHost | both | Processor host (fallbacks: ?host=__PROCESSOR_HOST__ → page hostname) | | ipId | both | IPID (ch5: CIP connection; ws: ?ipid= query param on the socket URL) | | debug | both | Console logging | | onConnectionChange | both | (connected: boolean) => void | | roomId, authToken, forceHttps | ch5 only | VC-4 / WebXPanel auth / https redirect guard | | appNumber | ws only | Dev-mode port rule: 10000 + appNumber (default 1; also ?app=, __PROCESSOR_APP_NUMBER__) | | wsPort | ws only | Explicit port override (also ?wsPort=) | | reconnectIntervalMs | ws only | Reconnect interval (default 5000) |

ws endpoint rules

  • Production (page served by the processor): ws://<page host>:<page port> — same origin.
  • Dev (page on localhost): ws://<processorHost>:<10000 + appNumber>.
  • Scheme follows the page: an https: page connects with wss: (the C# server must then offer TLS); plain-http pages use ws:.
  • 'connected' status is granted only after the backend answers the __CFG__ handshake — identical semantics to the ch5 transport.

Local testing without a processor:

npm run mock:ws          # protocol mock on ws://localhost:10001
npm run test:ws          # headless end-to-end test against the mock

Then open the consuming app with http://localhost:5173/?transport=ws&host=localhost&wsPort=10001.


Wire protocol (ws transport) — contract for the C# module

One WebSocket per panel. Text frames, UTF-8, one complete JSON object per frame. Envelope:

{ "header": "<channel>", "action": "<verb>", "parameters": { } }

Unknown headers/actions MUST be ignored by both sides (forward compatibility). All join values are strings on the wire.

joins channel (byte-compatible with the legacy cris-webui-core protocol)

Server → client, full snapshot. MUST be sent unsolicited immediately after every socket accept — this is the reconnect resync mechanism. Empty arrays allowed.

{ "header": "joins", "action": "status", "parameters": { "joins": {
    "digitals": [ { "number": 1, "value": "True" } ],
    "analogs":  [ { "number": 3, "value": "42" } ],
    "serials":  [ { "number": 2, "value": "hello" } ] } } }

Server → client, delta:

{ "header": "joins", "action": "changed", "parameters": {
    "join": { "type": "digital", "number": 1 }, "value": "False" } }

Client → server, write:

{ "header": "joins", "action": "change", "parameters": {
    "join": { "type": "analog", "number": 3 }, "value": "500" } }

typedigital | analog | serial. Digital: exactly "True" / "False" (the client tolerates lowercase inbound). Analog: decimal integer string 0–65535. The client never updates its local state on write — it waits for the server's changed feedback (state truth lives on the processor).

customs channel (new)

parameters is exactly the {oid, data} JSON that rides the SO-10001/join-1 serial pipe on the ch5 transport — the C# custom-object layer can route both transports into one handler. action is always "message"; the verb lives inside data, bit-identical with ch5.

Server → client:

{ "header": "customs", "action": "message", "parameters": {
    "oid": "DSP1_API", "data": { "status": { "...": "..." } } } }

data.status = full replace; data.update = partial deep-merge (arrays merged by id).

Client → server (same envelope): free-form app payloads, plus the control verbs:

{ "oid": "__CFG__",  "data": { "action": "init" } }
{ "oid": "DSP1_API", "data": { "action": "subscribe" } }
{ "oid": "DSP1_API", "data": { "action": "unsubscribe" } }

__CFG__ contract. The server MUST answer {action:"init"} with:

{ "header": "customs", "action": "message", "parameters": {
    "oid": "__CFG__", "data": { "status": {
      "re": { "wi": 1920, "he": 1080 }, "dl": "layoutA", "cc": { } } } } }

and MUST also push it unsolicited when the server itself (re)starts. The client shows 'connected' only after this status arrives; a backend that accepts sockets but never answers leaves panels in 'connecting' — this is deliberate. A new connection is a blank slate: the server drops prior opt-in subscriptions; the client re-sends subscribe for every active oid after each __CFG__ status.

Connection sequence

Client                                     Server (C# module)
  |-- WS CONNECT ws://host:port/?ipid=21 --->|
  |<-- accept --------------------------------|
  |<-- joins/status (full snapshot) ----------|   unsolicited, REQUIRED
  |-- customs __CFG__ {action:init} --------->|   retried 2s x10, then 10s
  |<-- customs __CFG__ {status:{re,dl,cc}} ---|   -> client status = 'connected'
  |-- customs {action:subscribe} x N -------->|   replay of active opt-in oids
  |<-- customs <oid> {status:...} ------------|   snapshot per subscribed oid
  |<== steady state: joins changed/change + customs both ways ==>
  ... on drop: client reconnects every 5s; sequence repeats from accept.

Heartbeat: none in v1. The header "sys" ({action:"ping"|"pong"}) is reserved for a future opt-in keepalive; the ignore-unknown rule makes adding it non-breaking.


Development

npm run build      # tsc + vite library build (ES + CJS + bundled d.ts)
npm run dev        # vite build --watch
npm run mock:ws    # dev mock of the C# ws module
npm run test:ws    # headless ws end-to-end test (build first)

Architecture notes

  • src/bridge.ts — ch5 transport (CrComLib/WebXPanel, https guard, online signal, __CFG__ loop).
  • src/ws-bridge.ts — ws transport (this protocol).
  • src/transport.ts — router: transport resolution + the public initBridge/cleanupBridge/isConnected/isInitialized/subscribeJoin/subscribeSmartJoin.
  • src/transport-shared.ts — transport-neutral state (active kind, logging, connected-notifier registry).
  • Writes are decoupled from transports via the window CustomEvent bus 'ch5:publish'; reads are fed into the Zustand stores by whichever bridge is active.
  • src/custom-object-store.ts — transport-blind message processor (deep-merge, ref-counted opt-in subscriptions, __CFG__ → GUI store push).