Out-of-tree port of JSS src/notifications/ (the legacy SolidOS
solid-0.1 WebSocket protocol). The deliberate seam-forcer of this
repo — chosen because core's version leans hardest on internals.
plugins: [{ module: 'notifications/plugin.js', prefix: '/.notifications',
config: {
podsRoot: './data', // REQUIRED (finding 1)
baseUrl: 'https://pod.example', // REQUIRED (finding 2)
loopbackUrl: 'http://127.0.0.1:3000', // optional (defaults to baseUrl)
} }]Protocol parity: protocol solid-0.1 greeting, sub/ack/err … forbidden,
pub with ancestor-container fan-out, unsub, subscription limits. Status
endpoint at <prefix>/status.
- No way to learn the data root — core watches
rootDirhanded to it by server.js; a plugin must have the operator repeat the path in config (drift risk: point it at the wrong dir and notifications silently cover nothing). Candidate seam:api.events.onResourceChange(cb)(best — no filesystem coupling at all, and core already has theresourceEventsemitter internally) or, weaker,api.storage.serverRootread-only. - No way to learn the server's public origin — needed for constructing
pubURLs, refusing cross-origin subscriptions, and the loopback check. The operator repeats it in config; a plugin cannot even discover it atactivate()time (listen hasn't happened). Candidate seam:api.serverInfo = { baseUrl, port }resolved at listen. - No response-header injection on core routes — core advertises the
websocket via an
Updates-Viaheader on every LDP response. A plugin cannot add headers to routes it doesn't own; SolidOS clients relying on discovery won't find a plugin-hosted endpoint. Candidate seam: a scopedapi.hooks.onSend((request, reply) => …), policy question included (letting plugins touch every response is a bigger grant than the rest of the api). - WAC checking from outside — solved, pleasantly. Core calls internal
checkAccess(). The port authorizes a subscription by asking the server itself: a loopbackHEADto the resource carrying the subscriber's ownAuthorizationheader. Slower (one HTTP round-trip persub, cached nothing), but it cannot disagree with the server's real policy — the test suite proves it against a real WAC.acl. This pattern generalizes: any plugin needing "would the server allow X?" can ask over loopback rather than needing aapi.wac.check()seam. A seam would still be nicer (no TCP round-trip, no loopbackUrl config), but it's a convenience seam, not a possibility seam. fs.watchparity note — core's watcher and this one share the same blind spot (writes that bypass the filesystem, e.g. future non-fs storage backends) and the same strength (catches out-of-band edits). Onlyapi.eventsfixes the blind spot for both.