openvibers/openvibe.network Commits

Network: realtime tickets name openvibe.events, connect-src follows; contracts 0.111.0, shared 2.13.0 (plan T7) (#76)

OP OpenVibers committed 7 Oct 2026 · e30b34b4d0 · 7 files, +21 −21

Changes

ddiff --git a/README.md b/README.md
iindex f93a8985..4edfafd2 100644
---- a/README.md
++++ b/README.md
@@@ -48,8 +48,8 @@ Identity and account service for the OpenVibe network. Manages user accounts, OA
## Depends on
--- `openvibe-contracts` v0.110.0 (registry manifests, capability checks), `openvibe-sdk` v0.28.0 (PostgreSQL
-- and Valkey adapters, events, per-actor limits, universal telemetry), `openvibe-shared` v2.10.1, pinned by release tarball
++- `openvibe-contracts` v0.111.0 (registry manifests, capability checks), `openvibe-sdk` v0.28.0 (PostgreSQL
++ and Valkey adapters, events, per-actor limits, universal telemetry), `openvibe-shared` v2.13.0, pinned by release tarball
- OpenVibe.Events (Network's outbox relay; the events it consumes), OpenVibe.AI (the `network.site_copy`
workflow), OpenVibe.Blog (the changelog feed it proxies)
- email (Resend), Discord and GitHub when their secrets are set ([Provider secrets](#provider-secrets))
@@@ -345,7 +345,7 @@ Every notification `NotificationService.create()` stores is announced as **`netw
- **Payload:** `notification_id`, `type`, `category`, `priority`, `service`, `created_at` and the recipient's `unread_count`. Never the title, message, link or sender.
- **Nothing announced:** a muted category, a blocked sender or a go-live dedupe stores nothing, so it announces nothing. Guests and accounts without a `usr_` subject get no event.
--The badge on other sites cannot use a cookie of events.openvibe.network, so it asks here for a **realtime ticket**:
++The badge on other sites cannot use a cookie of openvibe.events, so it asks here for a **realtime ticket**:
```
POST /api/v1/realtime/ticket (Bearer Network JWT, or the ov_token cookie here; 60/min per IP)
@@@ -356,7 +356,7 @@ It then opens `${stream_url}?topics=network.notification.*&ticket=…[&last_even
- **The ticket** (`server/auth/realtime-ticket.js`, `identity.realtime-ticket-claims@1`) is an RS256 JWT: `iss <issuer>/realtime`, `sub <usr_>`, `aud [openvibe.events]`, `typ` and `purpose` `realtime`, 120 s, `jti rtk_…`. Events accepts each one once.
- **Never a session:** its issuer, `typ` and audience each rule that out, and this session guard refuses it too.
- **Not stored, logged or audited:** it grants the person only their own stream.
--- **Refusals:** a guest gets 403 `realtime.guest`. `REALTIME_TICKETS=off` in `/etc/openvibe/network.env` answers 503 `realtime.disabled`, and every badge stays on polling. `OV_EVENTS_PUBLIC_URL` overrides the stream origin (default `https://events.openvibe.network`).
++- **Refusals:** a guest gets 403 `realtime.guest`. `REALTIME_TICKETS=off` in `/etc/openvibe/network.env` answers 503 `realtime.disabled`, and every badge stays on polling. `OV_EVENTS_PUBLIC_URL` overrides the stream origin (default `https://openvibe.events`).
- **Client:** openvibe-shared `notification-live.js`, on where a site sets `notificationsRealtime: true`.
A digest (a daily or weekly summary instead of one alert per notification) is designed in [docs/notification-digest.md](docs/notification-digest.md) for a later release.
ddiff --git a/package-lock.json b/package-lock.json
iindex e77a6bef..027b0b31 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -21,9 +21,9 @@
"iovalkey": "^0.4.0",
"jsonwebtoken": "^9.0.0",
"multer": "^1.4.5-lts.1",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.110.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.111.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.28.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.0",
"pg": "^8.23.0",
"uuid": "^11.1.0",
"web-push": "^3.6.7"
@@@ -1556,9 +1556,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.110.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.110.0",
-- "integrity": "sha512-+65KnRBVOzPN+MKSdaKzbvop6CT/wr8ZrO9mrQrRDdprRDmL4+CCOnykKYFOiMWPtoYkXlYg+GXmwmOw7eBL/g==",
++ "version": "0.111.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.111.0",
++ "integrity": "sha512-3F+SPc1m/8fvA8Gs/1mSn6/3FgbMltStl63vHKBioc6pAC3pAJov5M2E4qA3SBGvGo1fYCvDjwqS0CExHvOBFg==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
@@@ -1601,9 +1601,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "2.10.1",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
-- "integrity": "sha512-6fUOw22bSvxg0K33BefAyAkNRkETe9euLNT0CHs2mLIvzYR1q38KiHRUQIWj8FESbVANF0pXMojhBz5TeqpMtg==",
++ "version": "2.13.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.0",
++ "integrity": "sha512-0Lvo+bBWVGWREkp92tY4cGI32ZO124WTOjthDNTB96U161odl+npnYEWy3dV2IcgZC4HAX5DSzx4ThgNS2V/sA==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex e50fe528..1ee9039c 100644
---- a/package.json
++++ b/package.json
@@@ -30,9 +30,9 @@
"iovalkey": "^0.4.0",
"jsonwebtoken": "^9.0.0",
"multer": "^1.4.5-lts.1",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.110.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.111.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.28.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.0",
"pg": "^8.23.0",
"uuid": "^11.1.0",
"web-push": "^3.6.7"
ddiff --git a/server/admin/operator-checklist.js b/server/admin/operator-checklist.js
iindex a138b779..ee434284 100644
---- a/server/admin/operator-checklist.js
++++ b/server/admin/operator-checklist.js
@@@ -33,7 +33,7 @@ const AREAS = [
{ item: 'Media operations (jobs, tiers, repairs)', where: [{ kind: 'api', api: 'OpenVibe.Media /api/v1/jobs, /internal/*', note: 'no console yet' }, { kind: 'cli', cli: 'node scripts/h15-repair.js, object-drift-report.js (Media)' }], service: 'media' },
] },
{ area: 'Queues', items: [
-- { item: 'Events: subscriptions, dead letters, replay', where: [{ kind: 'tab', tab: 'eventsops' }, { kind: 'url', url: 'https://events.openvibe.network/' }], service: 'events' },
++ { item: 'Events: subscriptions, dead letters, replay', where: [{ kind: 'tab', tab: 'eventsops' }, { kind: 'url', url: 'https://openvibe.events/' }], service: 'events' },
{ item: 'AI runs, providers and routes', where: [{ kind: 'url', url: 'https://ai.openvibe.services/' }, { kind: 'tab', tab: 'ai' }], service: 'ai' },
] },
{ area: 'Moderation, money and loyalty', items: [
ddiff --git a/server/auth/realtime-ticket.js b/server/auth/realtime-ticket.js
iindex 17088aed..941b65f4 100644
---- a/server/auth/realtime-ticket.js
++++ b/server/auth/realtime-ticket.js
@@@ -7,7 +7,7 @@
*
* The notification badge on every OpenVibe site listens for the signed-in person's
* network.notification.created events on OpenVibe.Events' /realtime/stream. An EventSource cannot send
-- * a header, and events.openvibe.network's cookies are third-party on every other site, so the badge asks
++ * a header, and openvibe.events's cookies are third-party on every other site, so the badge asks
* here (Bearer Network JWT, or the ov_token cookie on openvibe.network) for a ticket and opens
* `${stream_url}?topics=network.notification.*&ticket=…`. A ticket opens one stream: every reconnect
* asks for a new one and resumes with last_event_id.
@@@ -29,9 +29,9 @@ const jwt = require('jsonwebtoken');
const TTL_S = 120;
const TOPICS = Object.freeze(['network.notification.*']);
--const DEFAULT_STREAM_URL = 'https://events.openvibe.network/realtime/stream';
++const DEFAULT_STREAM_URL = 'https://openvibe.events/realtime/stream';
--/** The stream URL browsers use: OV_EVENTS_PUBLIC_URL (an origin, or the stream URL itself) or events.openvibe.network. */
++/** The stream URL browsers use: OV_EVENTS_PUBLIC_URL (an origin, or the stream URL itself) or openvibe.events. */
function streamUrlFrom(value) {
const v = String(value || '').trim().replace(/\/+$/, '');
if (!v) return DEFAULT_STREAM_URL;
ddiff --git a/server/index.js b/server/index.js
iindex 47a6db2b..c1dc7ae9 100644
---- a/server/index.js
++++ b/server/index.js
@@@ -152,7 +152,7 @@ app.use(helmet({
"https://openvibe.blog",
"https://cloudflareinsights.com", // Cloudflare Web Analytics reports here (see script-src)
// Release notifications: release-watch's EventSource on the Events realtime stream (openvibe-shared 1.17)
-- "https://events.openvibe.network",
++ "https://openvibe.events",
],
frameSrc: ["'none'"],
scriptSrcAttr: ["'unsafe-inline'"],
ddiff --git a/test/notification-events.test.js b/test/notification-events.test.js
iindex 9e3d54f2..302a21fb 100644
---- a/test/notification-events.test.js
++++ b/test/notification-events.test.js
@@@ -133,7 +133,7 @@ const checkEnvelope = (env, subject) => {
assert.strictEqual(r.status, 200, JSON.stringify(r.body));
assert.strictEqual(r.headers.get('cache-control'), 'no-store');
assert.deepStrictEqual(Object.keys(r.body).sort(), ['expires_at', 'expires_in', 'stream_url', 'subject', 'ticket', 'topics']);
-- assert.deepStrictEqual([r.body.expires_in, r.body.stream_url, r.body.topics, r.body.subject], [120, 'https://events.openvibe.network/realtime/stream', ['network.notification.*'], ALICE]);
++ assert.deepStrictEqual([r.body.expires_in, r.body.stream_url, r.body.topics, r.body.subject], [120, 'https://openvibe.events/realtime/stream', ['network.notification.*'], ALICE]);
try { const v = contracts.validate('network.realtime-ticket-result@1', r.body); assert.ok(v.valid, JSON.stringify(v.errors)); } catch (err) { if (!/unknown contract/.test(err.message)) throw err; }
// What Events checks (OpenVibe.Events server/auth.js verifyRealtimeTicket).
const claims = jwt.verify(r.body.ticket, publicKey, { algorithms: ['RS256'], issuer: `${ISSUER}/realtime`, audience: 'openvibe.events' });
@@@ -166,7 +166,7 @@ const checkEnvelope = (env, subject) => {
assert.match(r.body.subject, /^usr_/);
assert.strictEqual((await db.prepare('SELECT subject_id FROM users WHERE id = 4').get()).subject_id, r.body.subject);
// The stream URL: the default, an operator's origin, or the default for anything odd.
-- assert.strictEqual(ticketMod.streamUrlFrom(''), 'https://events.openvibe.network/realtime/stream');
++ assert.strictEqual(ticketMod.streamUrlFrom(''), 'https://openvibe.events/realtime/stream');
assert.strictEqual(ticketMod.streamUrlFrom('https://events.example.test/'), 'https://events.example.test/realtime/stream');
assert.strictEqual(ticketMod.streamUrlFrom('https://events.example.test/realtime/stream'), 'https://events.example.test/realtime/stream');
for (const bad of ['javascript:alert(1)', 'https://x.test/?a=1', 'not a url', 'https://u:[email protected]']) assert.strictEqual(ticketMod.streamUrlFrom(bad), ticketMod.DEFAULT_STREAM_URL, bad);