openvibers/openvibe.shared Commits

release-watch subscribes to topics=host.release.published (1.17.0, WS-P task 9)

OP OpenVibers committed 26 Sept 2026 · 3290dc5f23 · 4 files, +14 −14

The release notification is its own event type now (openvibe-contracts 0.58.0: host.release.published@1, public, subject release); host.deploy.activated stays tenant hosting's activation. release-watch opens its one EventSource on topics=host.release.published and only acts on that event type; everything else about it is unchanged (service match, generations, 0-20 s jitter, one check per 30 s, hidden-tab close, backoff, CSP stop, polling as the fallback). test/release-watch-realtime.test.js expects the new topic in every stream URL and checks that a host.deploy.activated arriving on the stream is ignored. README and the 1.17.0 CHANGELOG entry use the new name. Not tagged.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 81a9d97..d3fad8f 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -6,8 +6,8 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
## 1.17.0 — 2026-09-26
--**Release notifications in `release-watch.js`** (roadmap WS-P task 9, ADR-016 amendment 1). When a release goes live, OpenVibe.Host publishes `host.deploy.activated` to OpenVibe.Events (contract `host.deploy.activated@1`, openvibe-contracts 0.58.0; `ovhost deploy` and `ovhost announce`). The event is public, has subject `release` and carries `service`, `release`, `commit` and `origin`. Tabs now hear about a release within seconds instead of at their next poll:
--- **One stream per tab.** release-watch opens one EventSource, without credentials, on `https://events.openvibe.network/realtime/stream?topics=host.deploy.activated`, once the page's release and service are known.
++**Release notifications in `release-watch.js`** (roadmap WS-P task 9, ADR-016 amendment 1). When a release goes live, OpenVibe.Host publishes `host.release.published` to OpenVibe.Events (contract `host.release.published@1`, openvibe-contracts 0.58.0; `ovhost deploy` and `ovhost announce`). The event is public, has subject `release` and carries `service`, `release`, `commit` and `origin`. Tabs now hear about a release within seconds instead of at their next poll:
++- **One stream per tab.** release-watch opens one EventSource, without credentials, on `https://events.openvibe.network/realtime/stream?topics=host.release.published`, once the page's release and service are known.
- **Which events count.** Only those for the page's service: `OVReleaseConfig.service`, the meta tag's `data-service`, else `/release.json`'s `service`.
- **Generations.** The tab ignores the release it runs, the one it already knows from `/release.json` and one already queued (a hex prefix of the other counts as the same release). It also ignores a repeated event id and a seq at or below the last one seen.
- **Coalescing.** A new release runs the usual `check(true)` after a random 0–20 s delay, so tabs do not all fetch at once. Everything arriving while that check is pending collapses into it, and after a check there are 30 s of quiet, after which whatever arrived meanwhile gets one more check. The update plan, the prompt and the safe-reload rules are unchanged.
ddiff --git a/README.md b/README.md
iindex 7fe3e45..2e36fd0 100644
---- a/README.md
++++ b/README.md
@@@ -193,9 +193,9 @@ release.mount(app, { registry: m.registry }); // GET /release.json, POST /rele
A stylesheet whose URL path is not the asset's logical path needs `data-ov-asset="/css/app.css"`.
Mark anything that must not be replaced with `data-ov-protected`.
- **Release notifications (1.17.0).** When OpenVibe.Host announces a release, it publishes
-- `host.deploy.activated` (public, `ovhost deploy` / `ovhost announce`). release-watch opens one
++ `host.release.published` (public, `ovhost deploy` / `ovhost announce`). release-watch opens one
EventSource per tab, without credentials, on
-- `https://events.openvibe.network/realtime/stream?topics=host.deploy.activated`.
++ `https://events.openvibe.network/realtime/stream?topics=host.release.published`.
- **Which events count.** Only events whose `payload.service` is the page's service: `OVReleaseConfig.service`,
the meta tag's `data-service`, else the `service` of `/release.json`. The tab ignores the release it runs
or already knows (a hex prefix counts as the same release) and repeats of an event, and runs its usual
ddiff --git a/release-watch.js b/release-watch.js
iindex 40178f9..cea3d11 100644
---- a/release-watch.js
++++ b/release-watch.js
@@@ -7,7 +7,7 @@
* 2 minutes, no focused field, nothing protected (form[data-dirty="true"], [data-ov-protected], playing
* media, a live camera/mic, window.OVProtected()). Outcomes are beaconed to the metrics URL (D46).
* Release notifications (1.17.0, WS-P task 9): one anonymous EventSource on the Events realtime stream
-- * (topic host.deploy.activated, public). An event naming this page's service with a release it neither
++ * (topic host.release.published, public). An event naming this page's service with a release it neither
* runs nor knows runs check(true) after a 0-20 s jitter, at most once per 30 s. Polling stays the fallback.
* OVReleaseConfig: { url, metricsUrl, updateUrl, inPlace: false, service, eventsUrl (false: off) }; the meta
* tag may carry data-service and data-events. See README "Releases".
@@@ -167,12 +167,12 @@
return m;
}
-- // ── Release notifications: OpenVibe.Host publishes host.deploy.activated (public) to OpenVibe.Events ──
++ // ── Release notifications: OpenVibe.Host publishes host.release.published (public) to OpenVibe.Events ──
// One EventSource per tab, without credentials: the events are public, so an account switch changes
// nothing (and a second load of this script returns early). Closed after 5 min hidden (the poll and the
// visibility check cover a hidden tab), reopened on return with last_event_id; errors back off 30 s to
// 15 min, and after 6 failures in a row only polling is left.
-- const TOPIC = 'host.deploy.activated';
++ const TOPIC = 'host.release.published';
const HEX = /^[0-9a-f]{7,40}$/;
const rt = { state: 'off', service: null, url: null, events: 0, ignored: 0, checks: 0, failures: 0, lastSeq: null };
let es = null; let queued = null; let again = false; let seen = [];
ddiff --git a/test/release-watch-realtime.test.js b/test/release-watch-realtime.test.js
iindex dd2b8e9..592254a 100644
---- a/test/release-watch-realtime.test.js
++++ b/test/release-watch-realtime.test.js
@@@ -1,6 +1,6 @@
'use strict';
// release-watch.js release notifications (1.17.0, WS-P task 9): one anonymous EventSource on the Events
--// realtime stream (topic host.deploy.activated); an event for this page's service with a new release runs
++// realtime stream (topic host.release.published); an event for this page's service with a new release runs
// the ordinary check after a 0-20 s jitter, bursts collapse into one check per 30 s, the release the tab
// runs or already knows is ignored, errors back off, hidden tabs close the stream, stop() closes it, and
// polling is unchanged. A fake EventSource stands in for the browser's.
@@@ -30,7 +30,7 @@ function fakeEventSource() {
let n = 0;
const released = (service, release, extra = {}) => ({
-- event_id: `evt_01JAB2C3D4E5F6G7H8J9K0M${String(++n).padStart(3, '0')}`, event_type: 'host.deploy.activated', version: 1, source: 'host',
++ event_id: `evt_01JAB2C3D4E5F6G7H8J9K0M${String(++n).padStart(3, '0')}`, event_type: 'host.release.published', version: 1, source: 'host',
visibility: 'public', subject: { type: 'release', id: `${service}:${release}` },
payload: { service, release, commit: null, origin: 'https://openvibe.live', deployed_at: '2026-09-26T08:00:00.000Z' }, ...extra,
});
@@@ -57,7 +57,7 @@ async function open({ url = 'https://openvibe.live/', html = PAGE(), config = nu
const p = await open();
assert.strictEqual(p.es.all.length, 1);
const s = p.es.all[0];
-- assert.strictEqual(s.url, `${DEFAULT}?topics=host.deploy.activated`);
++ assert.strictEqual(s.url, `${DEFAULT}?topics=host.release.published`);
assert.strictEqual(s.init, undefined, 'no withCredentials: public events need no account');
assert.strictEqual(p.rt().state, 'connecting');
s.opened();
@@@ -67,7 +67,7 @@ async function open({ url = 'https://openvibe.live/', html = PAGE(), config = nu
// Other services, tenant activations, the running release (or a hex prefix of it), other topics.
s.send(1, released('tools', B));
-- s.send(2, { ...released('live', B), payload: { project_id: 'prj_x', site_id: 'site_x', site: 'x', deploy_id: 'dpl_x', previous_deploy_id: null, rollback: false } });
++ s.send(2, { ...released('live', B), event_type: 'host.deploy.activated', subject: { type: 'deploy', id: 'dpl_x' }, payload: { project_id: 'prj_x', site_id: 'site_x', site: 'x', deploy_id: 'dpl_x', previous_deploy_id: null, rollback: false } });
s.send(3, released('live', A));
s.send(4, released('live', 'aaaaaaa'));
s.send(5, { ...released('live', B), event_type: 'live.stream.started' });
@@@ -155,7 +155,7 @@ async function open({ url = 'https://openvibe.live/', html = PAGE(), config = nu
assert.deepStrictEqual(p.timeouts(), [15000], 'the first retry after 15-30 s');
p.fireTimeouts();
assert.strictEqual(p.es.all.length, 2);
-- assert.strictEqual(p.es.all[1].url, `${DEFAULT}?topics=host.deploy.activated&last_event_id=41`, 'resumes after the last seq seen');
++ assert.strictEqual(p.es.all[1].url, `${DEFAULT}?topics=host.release.published&last_event_id=41`, 'resumes after the last seq seen');
const waits = [];
for (let i = 1; i < 6; i++) {
p.es.all[i].fail();
@@@ -227,7 +227,7 @@ async function open({ url = 'https://openvibe.live/', html = PAGE(), config = nu
off.release.stop();
const metaUrl = await open({ html: PAGE(' data-events="https://events.example.test/realtime/stream" data-service="docs"') });
-- assert.strictEqual(metaUrl.es.all[0].url, 'https://events.example.test/realtime/stream?topics=host.deploy.activated');
++ assert.strictEqual(metaUrl.es.all[0].url, 'https://events.example.test/realtime/stream?topics=host.release.published');
assert.strictEqual(metaUrl.rt().service, 'docs', 'data-service names the service');
metaUrl.release.stop();
@@@ -236,7 +236,7 @@ async function open({ url = 'https://openvibe.live/', html = PAGE(), config = nu
metaOff.release.stop();
const cfgService = await open({ config: { service: 'tools', eventsUrl: '/realtime/stream' } });
-- assert.strictEqual(cfgService.es.all[0].url, 'https://openvibe.live/realtime/stream?topics=host.deploy.activated');
++ assert.strictEqual(cfgService.es.all[0].url, 'https://openvibe.live/realtime/stream?topics=host.release.published');
cfgService.es.all[0].send(1, released('tools', B));
assert.deepStrictEqual(cfgService.timeouts(), [10000], 'OVReleaseConfig.service decides which events count');
cfgService.release.stop();