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

signalk-siparu

v0.2.15

Published

Kept aboard, proven ashore. Position, wind, depth and voyage history, recorded on the boat and readable from anywhere: sealed to the devices you name, signed on the way out. A read-only Signal K plugin with a built-in dashboard.

Readme

Siparu

Kept aboard, proven ashore. A Signal K plugin that records your boat's bridge data - position, speed, heading, wind, depth, and whatever engine, tank and generator gauges she exposes - into an on-board history with voyage detection, and serves it through a built-in dashboard and a read-only REST API.

The dashboard shows the bridge: speed, heading, wind, depth, position, the logbook, voyages and the chart. Engine, tank and generator gauges have panels of their own, built from what she reports rather than from a list: a boat with three engines gets three sets of readings, and a boat with no generator gets no generator tab. The same gauges are also served over the API (GET /live, GET /inventory).

Principles

  • Read-only. The plugin never writes to your boat: no deltas are emitted, no PUT handlers registered, no action handlers, no NMEA out. The CI proves it on every commit - grep this codebase and see for yourself.
  • Nothing leaves until you pair her, and pairing takes a deliberate tap at the helm. Unpair and it stops the same minute.
  • What she sends once paired. Her bridge, every two seconds under way and once a minute at rest: position, speed and course over ground, heading, rate of turn, magnetic variation and deviation, navigation state, apparent and true wind with gust and direction, depth, air and water temperature, barometric pressure, and GPS satellite count. Plus any engine, tank or generator gauges she exposes (propulsion.*, tanks.*, electrical.generators.*). That is what makes the remote screen a bridge instead of a dot on a map, and it is more than a position: if that is more than you want to share, do not pair her.
  • Your history stays on board. It is recorded on the boat as plain NDJSON with hourly and daily summaries. There is no bulk upload and the shore keeps no copy: a paired screen may ask her for one gauge's series in order to draw a graph, and she answers that from her own store. That question is the only thing she accepts from ashore. She takes no commands, because there are none to take.
  • The relay keeps her name and the time she was last seen. Nothing else. Live frames pass through it to whatever screen you have open and are stored by nobody, so when she is offline the shore has nothing to show and says so.
  • One runtime dependency, pure JavaScript. ws, which carries the uplink, and nothing else. It has no install script and no dependency of its own, and its two native helpers are optional peers - which is what lets the AppStore install this plugin with npm --ignore-scripts and have it work, on anything from a Raspberry Pi to a Victron Cerbo GX (Venus OS Large). Nothing here needs node-gyp.
  • Compatibility floor: Node 20 + signalk-server 2.18.

Not a safety system

Siparu is not a navigation, safety or alarm system, and it is not a substitute for seamanship, a watchkeeper, or going aboard to look. It reads instruments that drift, break and lie, and it carries what they say over networks that fail, starting with the boat's own uplink: a screen with nothing wrong on it is not the same thing as a boat that is fine, and the plugin raises no alarm to tell you otherwise. The chart is for looking, not for navigating, and a depth reading is whichever plane the boat's own sounder reports, which is not always the water under the keel. Do not rely on this software to protect life or property.

Before you pair: turn on Signal K security

Signal K ships with security switched off, and nothing in the setup makes you turn it on. With it off, every plugin's HTTP surface is open to anyone who can reach your boat's network, this one included. (The relay credential itself is out of reach: it lives in a mode-600 file under the plugin's data directory, not in the config. The relay address is not a plugin option at all - it comes from the server process's environment, so nothing that writes plugin options can redirect the boat.)

Because of that, the plugin locks its own writes while security is off: pairing, unpairing and log edits refuse until you either add an admin user in Signal K or tick "Allow pairing and log edits while Signal K security is off" in the plugin settings. The tick exists for installs that cannot easily turn security on; it is an acceptance of the risk, not a removal of it.

The lock cannot make an unsecured server safe. On such a server GET /skServer/plugins already discloses every plugin's configuration, the server's own config route can change any plugin's settings - including that very tick - and the App Store will install code. The consequence peculiar to Siparu is what a stranger on the marina wifi could do with an open door: link your boat to their account while your own screen goes on saying "paired".

So before you pair, add an admin user in Signal K (Security > Users). It takes a minute and it is the difference between a boat you share and a boat you leak.

The chart, and where its tiles come from

The dashboard's chart draws its coastline and place names from a basemap tile server. The default is a free, keyless, planet-wide host, and like any tile server it receives the requesting IP address and the tile coordinates being viewed. Those coordinates are roughly where the boat is: opening the chart reveals her approximate position to that host, whether or not remote viewing is on, and whether or not she is paired.

To keep the chart fully offline, drop a basemap.pmtiles file into the plugin's charts data folder; the chart then loads from it and asks no third party for anything. The basemap server can also be pointed at your own OpenMapTiles host in the plugin's advanced settings. The seamark overlay, fonts and sprites are served from Siparu's own host by default and can be redirected the same way.

REST API

Mounted at /plugins/siparu:

| Endpoint | Description | |---|---| | GET /live | Current state of all recorded paths + data age | | GET /inventory | Dynamic paths the boat exposes right now, narrowed to the families the dashboard understands, with units metadata | | GET /snapshots?bucket=1\|60\|360\|1440&from=&to=&limit=&offset=&order= | History rows. bucket=1 serves raw rows (today only); larger buckets read materialized rollups | | GET /health | Recording status, signature diagnosis, per-path freshness, storage usage, rollup state | | GET /voyages?limit= | Auto-detected voyages, newest first | | GET /voyages/current | Currently open voyage, or null | | GET /voyages/stats | Today / yesterday / rolling 7 days / season aggregates | | GET /voyages/:id/track | Minute-cadence GPS track of one voyage | | GET /ais/targets?max_nm=&max_age_min=&limit= | Nearby AIS targets, from the vessels the server already tracks | | GET /rollups/hourly?from=&to= | Raw hourly rollup lines the dashboard derives its history series from | | GET /map-config | Resolved chart asset URLs, so the dashboard loads local charts or the remote tile server without guessing | | GET /config/fuel-paths | Which engine fuel-rate paths feed the per-voyage fuel figure, and the paths available to choose from | | GET /voyages/edits | Which voyages were joined by hand, and so can be separated again |

The reading surface above is GET-only. Three flows move the plugin's own state and never touch the vessel's: pairing, the fuel-source picker, and correcting a voyage the detector split or joined in the wrong place. CI proves the point on every commit, names each write route, and an eighth one fails the build.

| Endpoint | Description | |---|---| | GET /pair/status | Current pairing state | | POST /pair/start | Ask the relay for a code to show at the helm | | POST /pair/approve · POST /pair/deny | Answer a claim - the tap at the helm | | POST /pair/reset | Unpair | | POST /config/fuel-paths | Choose which engine fuel-rate paths feed the per-voyage fuel figure | | POST /voyages/:id/merge-previous | Join this passage to the one before it, when one trip was recorded as two | | POST /voyages/:id/undo-merge | Separate a passage back into what it was made of |

Those seven, and GET /pair/status with them, answer only requests that came from the boat's own screen. A browser marks every request with its origin, and one from a page on another site gets 403: on a server with security off, which is the default, an advert frame in any tab on the boat's network could otherwise unpair her or lift the pairing code off the helm. A caller with no browser headers, curl or a script on her own network, is not affected. Neither is the reading surface above.

The two voyage edits are reachable from the boat's own network only. What the shore can ask her is a closed set of ten questions, all of them reads (history, snapshots, voyages, track, phases, rollups, stats, fuel, ais, health); nothing on that socket changes anything aboard, and the REST surface above is not proxied to it.

The uplink, when she is paired

She opens a WebSocket out to the relay and sends the live frame described above every two seconds under way, once a minute at rest; if that socket cannot hold - marina networks mangle WebSockets - she falls back to an HTTPS call once a minute that carries her clock and nothing else. There is no key exchange behind that call, so it could not encrypt a frame even if it were handed one: it says she is still there, not where she is. Either way the shore keeps none of it: when she drops off, all that remains ashore is her name and when she was last seen. Both are outbound: she dials the relay, the relay never dials her.

Five kinds of message travel the other way, and every one of them is a read of the store she wrote herself:

| Inbound | Description | |---|---| | { type: 'history', id, path, query } | One gauge's recorded series, shaped for a graph. Answered from the same NDJSON the local GET /snapshots serves. | | { type: 'snapshots', id, query } | Whole snapshot rows over a window: the logbook read, as the local GET /snapshots gives it. | | { type: 'voyages', id, limit } | Her recent voyages, newest first. She clamps the count, so a request cannot ask for more than she will give. | | { type: 'track', id, voyageId } | One voyage's recorded path, decimated aboard before it goes over the wire. | | { type: 'phases', id, limit } | The activity band beneath the voyages, newest first, clamped the same way. |

Anything else the shore sends is ignored, because a boat takes no command and so there is nothing else to hear.

She reports encrypted or she does not report. Until the account has named a device to seal to, nothing goes ashore at all: she keeps recording her own history, and her own screen says why nothing is leaving. There is no fallback that sends a position in the clear, because a boat whose privacy depends on the state of a list held elsewhere does not have any.

Once a device is named, the report is encrypted to it and the relay carries a block it cannot read. Two things stay legible beside it, and they are worth stating plainly because they are what a carrier can see: her identifier and the time she sent the frame, so it can be routed at all. Both are inside her signature, so neither can be rewritten in transit.

Voyage detection opens after sustained movement, closes after sustained stillness, and folds short docking manoeuvres into the preceding voyage. Its behavior is pinned by a golden-fixture test against a reference implementation on ten days of a procedurally generated track (plugin/test/fixtures/, laid down from a seeded PRNG; no vessel's real movements are involved).

All timestamps are epoch milliseconds (UTC). Values use Signal K's SI units (m/s, radians, Kelvin, Pascal).

Development

npm install
npm run build   # compile plugin TypeScript to plugin/dist
npm test        # unit tests (vitest)

License

Apache-2.0