openvibers/openvibe.chat Commits
openvibe-shared v1.29.2 (from v1.25.0): navbar dropdowns no longer clipped, themes repaint the canvas with readable button text, compact brand, async gauge collect (1.26), async Express handler errors reach the error handler (1.27). message-deleted's end-to-end half runs against today's OpenVibe.Events on PostgreSQL: Events gets a migrated database from its own test/db.js (PGlite, or the containers), and the store's lastSeq/scan/getEvent are awaited (they were read as values, so the replay asked from NaN and timed out). npm test 40/40.
Changes
@@@ -18,7 +18,7 @@"multer": "^2.0.2","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2","ws": "^8.18.0"},"engines": {@@@ -1087,9 +1087,9 @@}},"node_modules/openvibe-shared": {-- "version": "1.25.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0",-- "integrity": "sha512-oqZ+PvljrWi8IEbtg6bDYai17ZV2sALFx/ezMptniELSr10TgFUiGUQt+b1IWwiHv0T2Y1U2iSrkirJxMNgjSw==",++ "version": "1.29.2",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2",++ "integrity": "sha512-w5daRSzujokYT1/Q3fu0KdcSZUBEv1pm66H8vIAI2SY0xtOaLqgfkXJpfIz1NgvJuyK8dhXBbLjM8Ow9RZ5x+A==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -30,7 +30,7 @@"multer": "^2.0.2","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2","ws": "^8.18.0"},"engines": {@@@ -22,7 +22,7 @@ const t = suite('message-deleted');const EVENTS_DIR = process.env.OPENVIBE_EVENTS_DIR || path.join(__dirname, '..', '..', 'OpenVibe.Events');const haveEvents = ['server/index.js', 'server/redaction.js', 'node_modules/better-sqlite3'].every((f) => fs.existsSync(path.join(EVENTS_DIR, f)));const SECRET = 'my number is 555-0199';--let h, events, eventsPort, admin, alice, streamer, streamId;++let h, events, eventsDb, eventsPort, admin, alice, streamer, streamId;/** Outbox chat.message.deleted envelopes after `afterSeq`. */function deletions(afterSeq = 0) {@@@ -64,8 +64,13 @@ t('boot (a real OpenVibe.Events on a reserved port when one is checked out nextconst { load } = require(path.join(EVENTS_DIR, 'server', 'config'));const { start } = require(path.join(EVENTS_DIR, 'server', 'index'));const quiet = { log() {}, warn() {}, error() {} };++ // Events is on PostgreSQL (ADR-035): its own test/db.js hands out a migrated database (PGlite, or the++ // containers under EVENTS_TEST_STORE=pg); an older SQLite checkout ignores the handle and uses the path.++ const dbHelper = path.join(EVENTS_DIR, 'test', 'db.js');++ eventsDb = fs.existsSync(dbHelper) ? await require(dbHelper).testDb() : null;events = await start({config: load({ NODE_ENV: 'test', PORT: String(eventsPort), EVENTS_DB_PATH: path.join(h.tmp, 'events.db'), OV_NETWORK_PUBLIC_KEY: h.keys.publicKey, EVENTS_WORKER: 'off' }),++ db: eventsDb ? eventsDb.db : null,log: quiet,});} else {@@@ -206,7 +211,7 @@ t('e2e: the relay publishes the message, then its deletion; replay serves only th.db.run('UPDATE events_outbox SET sent_at = ? WHERE sent_at IS NULL', [new Date().toISOString()]); // earlier tests' rowsmsgId = say(SECRET, { user_id: alice.id, anon_id: null, username: 'alice' });assert.strictEqual((await h.eventsRelay.flush()).sent, 1);-- firstSeq = events.store.lastSeq();++ firstSeq = await events.store.lastSeq();const before = await sse(`/realtime/stream?topics=chat.message.*&last_event_id=${firstSeq - 1}`);await before.waitFor((c) => c.events().length === 1);assert.ok(before.body().includes('555-0199'), 'before the deletion the text is there (the leak)');@@@ -243,7 +248,7 @@ t('e2e: only svc:chat can redact Chat’s events', async () => {if (!haveEvents) return;const id = say('not yours to delete');await h.eventsRelay.flush();-- const created = events.store.scan(0, { patterns: ['chat.message.created'], limit: 1000 }).rows.find((r) => r.subject_id === String(id));++ const created = (await events.store.scan(0, { patterns: ['chat.message.created'], limit: 1000 })).rows.find((r) => r.subject_id === String(id));const env = deletions(0).at(-1); // a well-formed deletion, re-sourced by another serviceconst post = (sub, body) => fetch(`http://127.0.0.1:${eventsPort}/api/v1/events`, {method: 'POST', body: JSON.stringify(body),@@@ -254,10 +259,10 @@ t('e2e: only svc:chat can redact Chat’s events', async () => {assert.strictEqual((await r.json()).code, 'events.redaction_not_allowed');r = await post('svc:live', { ...env, event_id: 'evt_01J9DDDDDDDDDDDDDDDDDDDDDD', payload: { redacts: { subject_type: 'chat_message', subject_ids: [String(id)] } } });assert.strictEqual(r.status, 403, 'svc:live cannot publish as chat');-- assert.ok(events.store.getEvent(created.id).payload.includes('not yours to delete'), 'still intact');++ assert.ok((await events.store.getEvent(created.id)).payload.includes('not yours to delete'), 'still intact');h.db.deleteChatMessage(id, admin.id);await h.eventsRelay.flush();-- assert.ok(events.store.getEvent(created.id).redacted_at, 'Chat itself can');++ assert.ok((await events.store.getEvent(created.id)).redacted_at, 'Chat itself can');});--t.run(async () => { if (events) await events.close(); if (h) await h.close(); });++t.run(async () => { if (events) await events.close(); if (eventsDb) await eventsDb.close(); if (h) await h.close(); });
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
"multer": "^2.0.2",
"multer": "^2.0.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2",
"ws": "^8.18.0"
"ws": "^8.18.0"
},
},
"engines": {
"engines": {
@@ -1087,9 +1087,9 @@
@@ -1087,9 +1087,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "1.25.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0",
- "integrity": "sha512-oqZ+PvljrWi8IEbtg6bDYai17ZV2sALFx/ezMptniELSr10TgFUiGUQt+b1IWwiHv0T2Y1U2iSrkirJxMNgjSw==",
+ "version": "1.29.2",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2",
+ "integrity": "sha512-w5daRSzujokYT1/Q3fu0KdcSZUBEv1pm66H8vIAI2SY0xtOaLqgfkXJpfIz1NgvJuyK8dhXBbLjM8Ow9RZ5x+A==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -30,7 +30,7 @@
@@ -30,7 +30,7 @@
"multer": "^2.0.2",
"multer": "^2.0.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2",
"ws": "^8.18.0"
"ws": "^8.18.0"
},
},
"engines": {
"engines": {
@@ -22,7 +22,7 @@ const t = suite('message-deleted');
@@ -22,7 +22,7 @@ const t = suite('message-deleted');
const EVENTS_DIR = process.env.OPENVIBE_EVENTS_DIR || path.join(__dirname, '..', '..', 'OpenVibe.Events');
const EVENTS_DIR = process.env.OPENVIBE_EVENTS_DIR || path.join(__dirname, '..', '..', 'OpenVibe.Events');
const haveEvents = ['server/index.js', 'server/redaction.js', 'node_modules/better-sqlite3'].every((f) => fs.existsSync(path.join(EVENTS_DIR, f)));
const haveEvents = ['server/index.js', 'server/redaction.js', 'node_modules/better-sqlite3'].every((f) => fs.existsSync(path.join(EVENTS_DIR, f)));
const SECRET = 'my number is 555-0199';
const SECRET = 'my number is 555-0199';
-let h, events, eventsPort, admin, alice, streamer, streamId;
+let h, events, eventsDb, eventsPort, admin, alice, streamer, streamId;
/** Outbox chat.message.deleted envelopes after `afterSeq`. */
/** Outbox chat.message.deleted envelopes after `afterSeq`. */
function deletions(afterSeq = 0) {
function deletions(afterSeq = 0) {
@@ -64,8 +64,13 @@ t('boot (a real OpenVibe.Events on a reserved port when one is checked out next
@@ -64,8 +64,13 @@ t('boot (a real OpenVibe.Events on a reserved port when one is checked out next
const { load } = require(path.join(EVENTS_DIR, 'server', 'config'));
const { load } = require(path.join(EVENTS_DIR, 'server', 'config'));
const { start } = require(path.join(EVENTS_DIR, 'server', 'index'));
const { start } = require(path.join(EVENTS_DIR, 'server', 'index'));
const quiet = { log() {}, warn() {}, error() {} };
const quiet = { log() {}, warn() {}, error() {} };
+ // Events is on PostgreSQL (ADR-035): its own test/db.js hands out a migrated database (PGlite, or the
+ // containers under EVENTS_TEST_STORE=pg); an older SQLite checkout ignores the handle and uses the path.
+ const dbHelper = path.join(EVENTS_DIR, 'test', 'db.js');
+ eventsDb = fs.existsSync(dbHelper) ? await require(dbHelper).testDb() : null;
events = await start({
events = await start({
config: load({ NODE_ENV: 'test', PORT: String(eventsPort), EVENTS_DB_PATH: path.join(h.tmp, 'events.db'), OV_NETWORK_PUBLIC_KEY: h.keys.publicKey, EVENTS_WORKER: 'off' }),
config: load({ NODE_ENV: 'test', PORT: String(eventsPort), EVENTS_DB_PATH: path.join(h.tmp, 'events.db'), OV_NETWORK_PUBLIC_KEY: h.keys.publicKey, EVENTS_WORKER: 'off' }),
+ db: eventsDb ? eventsDb.db : null,
log: quiet,
log: quiet,
});
});
} else {
} else {
@@ -206,7 +211,7 @@ t('e2e: the relay publishes the message, then its deletion; replay serves only t
@@ -206,7 +211,7 @@ t('e2e: the relay publishes the message, then its deletion; replay serves only t
h.db.run('UPDATE events_outbox SET sent_at = ? WHERE sent_at IS NULL', [new Date().toISOString()]); // earlier tests' rows
h.db.run('UPDATE events_outbox SET sent_at = ? WHERE sent_at IS NULL', [new Date().toISOString()]); // earlier tests' rows
msgId = say(SECRET, { user_id: alice.id, anon_id: null, username: 'alice' });
msgId = say(SECRET, { user_id: alice.id, anon_id: null, username: 'alice' });
assert.strictEqual((await h.eventsRelay.flush()).sent, 1);
assert.strictEqual((await h.eventsRelay.flush()).sent, 1);
- firstSeq = events.store.lastSeq();
+ firstSeq = await events.store.lastSeq();
const before = await sse(`/realtime/stream?topics=chat.message.*&last_event_id=${firstSeq - 1}`);
const before = await sse(`/realtime/stream?topics=chat.message.*&last_event_id=${firstSeq - 1}`);
await before.waitFor((c) => c.events().length === 1);
await before.waitFor((c) => c.events().length === 1);
assert.ok(before.body().includes('555-0199'), 'before the deletion the text is there (the leak)');
assert.ok(before.body().includes('555-0199'), 'before the deletion the text is there (the leak)');
@@ -243,7 +248,7 @@ t('e2e: only svc:chat can redact Chat’s events', async () => {
@@ -243,7 +248,7 @@ t('e2e: only svc:chat can redact Chat’s events', async () => {
if (!haveEvents) return;
if (!haveEvents) return;
const id = say('not yours to delete');
const id = say('not yours to delete');
await h.eventsRelay.flush();
await h.eventsRelay.flush();
- const created = events.store.scan(0, { patterns: ['chat.message.created'], limit: 1000 }).rows.find((r) => r.subject_id === String(id));
+ const created = (await events.store.scan(0, { patterns: ['chat.message.created'], limit: 1000 })).rows.find((r) => r.subject_id === String(id));
const env = deletions(0).at(-1); // a well-formed deletion, re-sourced by another service
const env = deletions(0).at(-1); // a well-formed deletion, re-sourced by another service
const post = (sub, body) => fetch(`http://127.0.0.1:${eventsPort}/api/v1/events`, {
const post = (sub, body) => fetch(`http://127.0.0.1:${eventsPort}/api/v1/events`, {
method: 'POST', body: JSON.stringify(body),
method: 'POST', body: JSON.stringify(body),
@@ -254,10 +259,10 @@ t('e2e: only svc:chat can redact Chat’s events', async () => {
@@ -254,10 +259,10 @@ t('e2e: only svc:chat can redact Chat’s events', async () => {
assert.strictEqual((await r.json()).code, 'events.redaction_not_allowed');
assert.strictEqual((await r.json()).code, 'events.redaction_not_allowed');
r = await post('svc:live', { ...env, event_id: 'evt_01J9DDDDDDDDDDDDDDDDDDDDDD', payload: { redacts: { subject_type: 'chat_message', subject_ids: [String(id)] } } });
r = await post('svc:live', { ...env, event_id: 'evt_01J9DDDDDDDDDDDDDDDDDDDDDD', payload: { redacts: { subject_type: 'chat_message', subject_ids: [String(id)] } } });
assert.strictEqual(r.status, 403, 'svc:live cannot publish as chat');
assert.strictEqual(r.status, 403, 'svc:live cannot publish as chat');
- assert.ok(events.store.getEvent(created.id).payload.includes('not yours to delete'), 'still intact');
+ assert.ok((await events.store.getEvent(created.id)).payload.includes('not yours to delete'), 'still intact');
h.db.deleteChatMessage(id, admin.id);
h.db.deleteChatMessage(id, admin.id);
await h.eventsRelay.flush();
await h.eventsRelay.flush();
- assert.ok(events.store.getEvent(created.id).redacted_at, 'Chat itself can');
+ assert.ok((await events.store.getEvent(created.id)).redacted_at, 'Chat itself can');
});
});
-t.run(async () => { if (events) await events.close(); if (h) await h.close(); });
+t.run(async () => { if (events) await events.close(); if (eventsDb) await eventsDb.close(); if (h) await h.close(); });