openvibers/openvibe.sites Commits
openvibe-shared v3.0.1 (was v3.0.0) (#46)
Changes
@@@ -72,7 +72,7 @@ The pinned `openvibe-contracts` release supplies the catalog `sites.json` mirror## Depends on- `openvibe-contracts` v0.129.0 — `scripts/sync-catalog.js` reads `openvibe-contracts/lib/products` (`catalog()`) to generate `sites.json`.--- `openvibe-shared` v3.0.0 — `build.js` uses `legal` for the terms, privacy and dmca pages, `files` for the `BROWSER` list copied to `dist/_shared/`, and `app-icon` for the manifest and head tags; `test/run.js` uses `test-runner`.++- `openvibe-shared` v3.0.1 — `build.js` uses `legal` for the terms, privacy and dmca pages, `files` for the `BROWSER` list copied to `dist/_shared/`, and `app-icon` for the manifest and head tags; `test/run.js` uses `test-runner`.- OpenVibe.Host — the deploy wrapper calls `ovhost deploy sites --restart`, or `ovhost plan sites --restart` with `DRY_RUN=1`.- OpenVibe.Network at `https://openvibe.network` — the frozen pages and shared browser files load its assets and API in the browser; `NET.networkUrl` in `build.js` is that base.@@@ -108,5 +108,5 @@ Report security issues through [SECURITY.md](SECURITY.md).<!-- versions:start -->- openvibe-contracts: v0.129.0--- openvibe-shared: v3.0.0++- openvibe-shared: v3.0.1<!-- versions:end -->@@@ -23,7 +23,7 @@"status.openvibe.network (status notice)"],"contracts": "openvibe-contracts v0.129.0: sites.json mirrors products.catalog() (scripts/sync-catalog.js)",-- "shared": "openvibe-shared v3.0.0",++ "shared": "openvibe-shared v3.0.1","deployed": true,"deployment": "Deployed with `sudo ovhost deploy sites --restart` (OpenVibe.Host strategy static-build since the 2026-09-27 cutover): /opt/openvibe.sites, nginx serves dist/<domain>/ through deploy/nginx/<domain>.conf; no unit, no port, no env file","features": [@@@ -3,10 +3,11 @@* ADR-005 amendment 2). notification-ui.js loads it on demand when a site turns it on (navbar notificationsRealtime).* For every (re)connect it asks Network for a realtime ticket (POST <api>/api/v1/realtime/ticket: Bearer token, or the* cookie on openvibe.network itself) and opens Events' stream with it, without credentials:-- * <stream_url>?topics=network.notification.*&ticket=<ticket>[&last_event_id=<seq>]++ * <stream_url>?topics=network.notification.*&ticket=<ticket>[&last_event_id=<cursor>]* Events streams only the person's own network.notification.created events (subject visibility); one whose subject is* not the ticket's is ignored all the same. Each calls onNotification(payload); `event: gap` calls onGap.-- * A ticket opens one stream: errors close it and reconnect with a fresh ticket from the cursor, backing off 2 s to 15 min++ * The cursor is the last SSE id Events sent (opaque, handed back as it came); an event already heard (by event_id) is++ * skipped. A ticket opens one stream: errors close it and reconnect with a fresh ticket from the cursor, backing off 2 s to 15 min* with jitter. Stops (the caller's polling is all there is) after 10 failures in a row, on a Content-Security-Policy* refusal, when signed out (401/403) or when Network has realtime off (404/503). Hidden 5 min: closed; shown: resumed.* OVNotificationLive.create({ ticketUrl, token, credentials, onNotification, onGap, onState }) → { start, stop, restart, state }@@@ -17,10 +18,12 @@const TYPE_RE = /^network\.notification\./;const SUBJECT_RE = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;const MAX_FAILURES = 10; // 2 s, 4 s, … 512 s: about 9 to 17 minutes of retries before polling only++ const SEEN_MAX = 256; // event ids remembered for dedupefunction create(o) {-- const rt = { state: 'off', failures: 0, lastSeq: null, subject: null, origin: null, events: 0, ignored: 0, gaps: 0, tickets: 0 };++ const rt = { state: 'off', failures: 0, lastId: null, subject: null, origin: null, events: 0, ignored: 0, gaps: 0, tickets: 0 };let es = null; let stopped = true; let gen = 0;++ const seen = new Set();const t = {}; // timers: retry, hideconst later = (k, f, ms) => { root.clearTimeout(t[k]); t[k] = root.setTimeout(() => { t[k] = null; f(); }, ms); };const call = (f, ...a) => { try { if (typeof f === 'function') f(...a); } catch { /* the page's handler must not break the feed */ } };@@@ -54,7 +57,7 @@if (!/^https?:$/.test(u.protocol)) throw new Error('url');u.searchParams.set('topics', tk.topics.join(','));u.searchParams.set('ticket', tk.ticket);-- if (rt.lastSeq != null) u.searchParams.set('last_event_id', String(rt.lastSeq));++ if (rt.lastId != null) u.searchParams.set('last_event_id', rt.lastId);} catch { fail(); return; }rt.tickets++; rt.origin = u.origin;rt.subject = tk.subject;@@@ -73,9 +76,14 @@}function heard(e) {let m; try { m = JSON.parse(e.data); } catch { return; }-- const seq = m && typeof m.seq === 'number' ? m.seq : Number(e.lastEventId);-- if (Number.isFinite(seq)) { if (rt.lastSeq != null && seq <= rt.lastSeq) return; rt.lastSeq = seq; }const ev = m && m.event;++ const id = ev && typeof ev.event_id === 'string' ? ev.event_id : null;++ if (id) {++ if (seen.has(id)) return; // already heard (a replay after a reconnect)++ seen.add(id);++ if (seen.size > SEEN_MAX) seen.delete(seen.values().next().value);++ }++ if (e.lastEventId) rt.lastId = String(e.lastEventId);// Events already sends only this person's own; anything else is never shown.if (!ev || !TYPE_RE.test(String(ev.event_type)) || !ev.subject || ev.subject.type !== 'user' || ev.subject.id !== rt.subject) { rt.ignored++; return; }rt.events++;@@@ -107,7 +115,7 @@start() { if (!stopped) return api; stopped = false; set('off'); open(); return api; },stop() { stopped = true; gen++; close(); Object.keys(t).forEach((k) => { root.clearTimeout(t[k]); t[k] = null; }); set('off'); return api; },/** Another account (or a new token): forget the cursor and the subject, start again. */-- restart() { api.stop(); rt.lastSeq = null; rt.subject = null; rt.failures = 0; return api.start(); },++ restart() { api.stop(); rt.lastId = null; seen.clear(); rt.subject = null; rt.failures = 0; return api.start(); },state: () => ({ ...rt }),};return api;@@@ -181,7 +181,7 @@// back off 30 s to 15 min, after 6 failures only polling is left; online reconnects at once.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 };++ const rt = { state: 'off', service: null, url: null, events: 0, ignored: 0, checks: 0, failures: 0, lastId: null };let es = null; let queued = null; let again = false; let seen = [];const t = {}; // timers: run (the jittered check), cool (30 s after it), retry, hideconst later = (k, f, ms) => { root.clearTimeout(t[k]); t[k] = root.setTimeout(() => { t[k] = null; f(); }, ms); };@@@ -229,7 +229,6 @@let m; try { m = JSON.parse(e.data); } catch { return; }const ev = m && m.event; const p = ev && ev.payload;if (!p || ev.event_type !== TOPIC) return;-- if (typeof m.seq === 'number') { if (rt.lastSeq != null && m.seq <= rt.lastSeq) return; rt.lastSeq = m.seq; }rt.lastId = e.lastEventId || rt.lastId;if (p.service !== rt.service || typeof p.release !== 'string') return;// Ids repeat across origins (Sites' placeholders): a named origin must be ours.@@@ -7,7 +7,7 @@"name": "openvibe-sites","dependencies": {"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0"++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.1"},"engines": {"node": ">=20"@@@ -197,9 +197,9 @@}},"node_modules/openvibe-shared": {-- "version": "3.0.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0",-- "integrity": "sha512-wR/4g5G2tD/s4sysi5ZIGlsKMZHMrXl9SSeohto+Z1rSP9qugZGJNwGdVSUOhgdZ75S7D9eg68jsIy/OoM9LhA==",++ "version": "3.0.1",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.1",++ "integrity": "sha512-7EvVTZFzhvUQw1r2zB1Lpg2lTnyU6rQl7OqkDS2ESsmvRxoKsH/ztquFtVTNSRtwhI5NR2kmW1PADkCwiBJcZA==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -8,7 +8,7 @@},"dependencies": {"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0"++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.1"},"engines": {"node": ">=20"
@@ -72,7 +72,7 @@ The pinned `openvibe-contracts` release supplies the catalog `sites.json` mirror
@@ -72,7 +72,7 @@ The pinned `openvibe-contracts` release supplies the catalog `sites.json` mirror
## Depends on
## Depends on
- `openvibe-contracts` v0.129.0 — `scripts/sync-catalog.js` reads `openvibe-contracts/lib/products` (`catalog()`) to generate `sites.json`.
- `openvibe-contracts` v0.129.0 — `scripts/sync-catalog.js` reads `openvibe-contracts/lib/products` (`catalog()`) to generate `sites.json`.
-- `openvibe-shared` v3.0.0 — `build.js` uses `legal` for the terms, privacy and dmca pages, `files` for the `BROWSER` list copied to `dist/_shared/`, and `app-icon` for the manifest and head tags; `test/run.js` uses `test-runner`.
+- `openvibe-shared` v3.0.1 — `build.js` uses `legal` for the terms, privacy and dmca pages, `files` for the `BROWSER` list copied to `dist/_shared/`, and `app-icon` for the manifest and head tags; `test/run.js` uses `test-runner`.
- OpenVibe.Host — the deploy wrapper calls `ovhost deploy sites --restart`, or `ovhost plan sites --restart` with `DRY_RUN=1`.
- OpenVibe.Host — the deploy wrapper calls `ovhost deploy sites --restart`, or `ovhost plan sites --restart` with `DRY_RUN=1`.
- OpenVibe.Network at `https://openvibe.network` — the frozen pages and shared browser files load its assets and API in the browser; `NET.networkUrl` in `build.js` is that base.
- OpenVibe.Network at `https://openvibe.network` — the frozen pages and shared browser files load its assets and API in the browser; `NET.networkUrl` in `build.js` is that base.
@@ -108,5 +108,5 @@ Report security issues through [SECURITY.md](SECURITY.md).
@@ -108,5 +108,5 @@ Report security issues through [SECURITY.md](SECURITY.md).
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.129.0
- openvibe-contracts: v0.129.0
-- openvibe-shared: v3.0.0
+- openvibe-shared: v3.0.1
<!-- versions:end -->
<!-- versions:end -->
@@ -23,7 +23,7 @@
@@ -23,7 +23,7 @@
"status.openvibe.network (status notice)"
"status.openvibe.network (status notice)"
],
],
"contracts": "openvibe-contracts v0.129.0: sites.json mirrors products.catalog() (scripts/sync-catalog.js)",
"contracts": "openvibe-contracts v0.129.0: sites.json mirrors products.catalog() (scripts/sync-catalog.js)",
- "shared": "openvibe-shared v3.0.0",
+ "shared": "openvibe-shared v3.0.1",
"deployed": true,
"deployed": true,
"deployment": "Deployed with `sudo ovhost deploy sites --restart` (OpenVibe.Host strategy static-build since the 2026-09-27 cutover): /opt/openvibe.sites, nginx serves dist/<domain>/ through deploy/nginx/<domain>.conf; no unit, no port, no env file",
"deployment": "Deployed with `sudo ovhost deploy sites --restart` (OpenVibe.Host strategy static-build since the 2026-09-27 cutover): /opt/openvibe.sites, nginx serves dist/<domain>/ through deploy/nginx/<domain>.conf; no unit, no port, no env file",
"features": [
"features": [
@@ -3,10 +3,11 @@
@@ -3,10 +3,11 @@
* ADR-005 amendment 2). notification-ui.js loads it on demand when a site turns it on (navbar notificationsRealtime).
* ADR-005 amendment 2). notification-ui.js loads it on demand when a site turns it on (navbar notificationsRealtime).
* For every (re)connect it asks Network for a realtime ticket (POST <api>/api/v1/realtime/ticket: Bearer token, or the
* For every (re)connect it asks Network for a realtime ticket (POST <api>/api/v1/realtime/ticket: Bearer token, or the
* cookie on openvibe.network itself) and opens Events' stream with it, without credentials:
* cookie on openvibe.network itself) and opens Events' stream with it, without credentials:
- * <stream_url>?topics=network.notification.*&ticket=<ticket>[&last_event_id=<seq>]
+ * <stream_url>?topics=network.notification.*&ticket=<ticket>[&last_event_id=<cursor>]
* Events streams only the person's own network.notification.created events (subject visibility); one whose subject is
* Events streams only the person's own network.notification.created events (subject visibility); one whose subject is
* not the ticket's is ignored all the same. Each calls onNotification(payload); `event: gap` calls onGap.
* not the ticket's is ignored all the same. Each calls onNotification(payload); `event: gap` calls onGap.
- * A ticket opens one stream: errors close it and reconnect with a fresh ticket from the cursor, backing off 2 s to 15 min
+ * The cursor is the last SSE id Events sent (opaque, handed back as it came); an event already heard (by event_id) is
+ * skipped. A ticket opens one stream: errors close it and reconnect with a fresh ticket from the cursor, backing off 2 s to 15 min
* with jitter. Stops (the caller's polling is all there is) after 10 failures in a row, on a Content-Security-Policy
* with jitter. Stops (the caller's polling is all there is) after 10 failures in a row, on a Content-Security-Policy
* refusal, when signed out (401/403) or when Network has realtime off (404/503). Hidden 5 min: closed; shown: resumed.
* refusal, when signed out (401/403) or when Network has realtime off (404/503). Hidden 5 min: closed; shown: resumed.
* OVNotificationLive.create({ ticketUrl, token, credentials, onNotification, onGap, onState }) → { start, stop, restart, state }
* OVNotificationLive.create({ ticketUrl, token, credentials, onNotification, onGap, onState }) → { start, stop, restart, state }
@@ -17,10 +18,12 @@
@@ -17,10 +18,12 @@
const TYPE_RE = /^network\.notification\./;
const TYPE_RE = /^network\.notification\./;
const SUBJECT_RE = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;
const SUBJECT_RE = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;
const MAX_FAILURES = 10; // 2 s, 4 s, … 512 s: about 9 to 17 minutes of retries before polling only
const MAX_FAILURES = 10; // 2 s, 4 s, … 512 s: about 9 to 17 minutes of retries before polling only
+ const SEEN_MAX = 256; // event ids remembered for dedupe
function create(o) {
function create(o) {
- const rt = { state: 'off', failures: 0, lastSeq: null, subject: null, origin: null, events: 0, ignored: 0, gaps: 0, tickets: 0 };
+ const rt = { state: 'off', failures: 0, lastId: null, subject: null, origin: null, events: 0, ignored: 0, gaps: 0, tickets: 0 };
let es = null; let stopped = true; let gen = 0;
let es = null; let stopped = true; let gen = 0;
+ const seen = new Set();
const t = {}; // timers: retry, hide
const t = {}; // timers: retry, hide
const later = (k, f, ms) => { root.clearTimeout(t[k]); t[k] = root.setTimeout(() => { t[k] = null; f(); }, ms); };
const later = (k, f, ms) => { root.clearTimeout(t[k]); t[k] = root.setTimeout(() => { t[k] = null; f(); }, ms); };
const call = (f, ...a) => { try { if (typeof f === 'function') f(...a); } catch { /* the page's handler must not break the feed */ } };
const call = (f, ...a) => { try { if (typeof f === 'function') f(...a); } catch { /* the page's handler must not break the feed */ } };
@@ -54,7 +57,7 @@
@@ -54,7 +57,7 @@
if (!/^https?:$/.test(u.protocol)) throw new Error('url');
if (!/^https?:$/.test(u.protocol)) throw new Error('url');
u.searchParams.set('topics', tk.topics.join(','));
u.searchParams.set('topics', tk.topics.join(','));
u.searchParams.set('ticket', tk.ticket);
u.searchParams.set('ticket', tk.ticket);
- if (rt.lastSeq != null) u.searchParams.set('last_event_id', String(rt.lastSeq));
+ if (rt.lastId != null) u.searchParams.set('last_event_id', rt.lastId);
} catch { fail(); return; }
} catch { fail(); return; }
rt.tickets++; rt.origin = u.origin;
rt.tickets++; rt.origin = u.origin;
rt.subject = tk.subject;
rt.subject = tk.subject;
@@ -73,9 +76,14 @@
@@ -73,9 +76,14 @@
}
}
function heard(e) {
function heard(e) {
let m; try { m = JSON.parse(e.data); } catch { return; }
let m; try { m = JSON.parse(e.data); } catch { return; }
- const seq = m && typeof m.seq === 'number' ? m.seq : Number(e.lastEventId);
- if (Number.isFinite(seq)) { if (rt.lastSeq != null && seq <= rt.lastSeq) return; rt.lastSeq = seq; }
const ev = m && m.event;
const ev = m && m.event;
+ const id = ev && typeof ev.event_id === 'string' ? ev.event_id : null;
+ if (id) {
+ if (seen.has(id)) return; // already heard (a replay after a reconnect)
+ seen.add(id);
+ if (seen.size > SEEN_MAX) seen.delete(seen.values().next().value);
+ }
+ if (e.lastEventId) rt.lastId = String(e.lastEventId);
// Events already sends only this person's own; anything else is never shown.
// Events already sends only this person's own; anything else is never shown.
if (!ev || !TYPE_RE.test(String(ev.event_type)) || !ev.subject || ev.subject.type !== 'user' || ev.subject.id !== rt.subject) { rt.ignored++; return; }
if (!ev || !TYPE_RE.test(String(ev.event_type)) || !ev.subject || ev.subject.type !== 'user' || ev.subject.id !== rt.subject) { rt.ignored++; return; }
rt.events++;
rt.events++;
@@ -107,7 +115,7 @@
@@ -107,7 +115,7 @@
start() { if (!stopped) return api; stopped = false; set('off'); open(); return api; },
start() { if (!stopped) return api; stopped = false; set('off'); open(); return api; },
stop() { stopped = true; gen++; close(); Object.keys(t).forEach((k) => { root.clearTimeout(t[k]); t[k] = null; }); set('off'); return api; },
stop() { stopped = true; gen++; close(); Object.keys(t).forEach((k) => { root.clearTimeout(t[k]); t[k] = null; }); set('off'); return api; },
/** Another account (or a new token): forget the cursor and the subject, start again. */
/** Another account (or a new token): forget the cursor and the subject, start again. */
- restart() { api.stop(); rt.lastSeq = null; rt.subject = null; rt.failures = 0; return api.start(); },
+ restart() { api.stop(); rt.lastId = null; seen.clear(); rt.subject = null; rt.failures = 0; return api.start(); },
state: () => ({ ...rt }),
state: () => ({ ...rt }),
};
};
return api;
return api;
@@ -181,7 +181,7 @@
@@ -181,7 +181,7 @@
// back off 30 s to 15 min, after 6 failures only polling is left; online reconnects at once.
// back off 30 s to 15 min, after 6 failures only polling is left; online reconnects at once.
const TOPIC = 'host.release.published';
const TOPIC = 'host.release.published';
const HEX = /^[0-9a-f]{7,40}$/;
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 };
+ const rt = { state: 'off', service: null, url: null, events: 0, ignored: 0, checks: 0, failures: 0, lastId: null };
let es = null; let queued = null; let again = false; let seen = [];
let es = null; let queued = null; let again = false; let seen = [];
const t = {}; // timers: run (the jittered check), cool (30 s after it), retry, hide
const t = {}; // timers: run (the jittered check), cool (30 s after it), retry, hide
const later = (k, f, ms) => { root.clearTimeout(t[k]); t[k] = root.setTimeout(() => { t[k] = null; f(); }, ms); };
const later = (k, f, ms) => { root.clearTimeout(t[k]); t[k] = root.setTimeout(() => { t[k] = null; f(); }, ms); };
@@ -229,7 +229,6 @@
@@ -229,7 +229,6 @@
let m; try { m = JSON.parse(e.data); } catch { return; }
let m; try { m = JSON.parse(e.data); } catch { return; }
const ev = m && m.event; const p = ev && ev.payload;
const ev = m && m.event; const p = ev && ev.payload;
if (!p || ev.event_type !== TOPIC) return;
if (!p || ev.event_type !== TOPIC) return;
- if (typeof m.seq === 'number') { if (rt.lastSeq != null && m.seq <= rt.lastSeq) return; rt.lastSeq = m.seq; }
rt.lastId = e.lastEventId || rt.lastId;
rt.lastId = e.lastEventId || rt.lastId;
if (p.service !== rt.service || typeof p.release !== 'string') return;
if (p.service !== rt.service || typeof p.release !== 'string') return;
// Ids repeat across origins (Sites' placeholders): a named origin must be ours.
// Ids repeat across origins (Sites' placeholders): a named origin must be ours.
@@ -7,7 +7,7 @@
@@ -7,7 +7,7 @@
"name": "openvibe-sites",
"name": "openvibe-sites",
"dependencies": {
"dependencies": {
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0"
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.1"
},
},
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"
@@ -197,9 +197,9 @@
@@ -197,9 +197,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "3.0.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0",
- "integrity": "sha512-wR/4g5G2tD/s4sysi5ZIGlsKMZHMrXl9SSeohto+Z1rSP9qugZGJNwGdVSUOhgdZ75S7D9eg68jsIy/OoM9LhA==",
+ "version": "3.0.1",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.1",
+ "integrity": "sha512-7EvVTZFzhvUQw1r2zB1Lpg2lTnyU6rQl7OqkDS2ESsmvRxoKsH/ztquFtVTNSRtwhI5NR2kmW1PADkCwiBJcZA==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -8,7 +8,7 @@
@@ -8,7 +8,7 @@
},
},
"dependencies": {
"dependencies": {
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0"
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.1"
},
},
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"