@volter/supercode-teams 0.3.16 → 0.3.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/server/event-routes.mjs +9 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
"@volter/identity": "^0.4.1",
|
|
63
63
|
"@volter/supercode-calendar": "^0.1.0",
|
|
64
64
|
"@volter/supercode-frontend": "^0.2.2",
|
|
65
|
-
"@volter/supercode-harness-sdk": "^0.3.
|
|
65
|
+
"@volter/supercode-harness-sdk": "^0.3.58",
|
|
66
66
|
"@volter/supercode-health": "^0.1.0",
|
|
67
67
|
"@volter/supercode-terminal": "^0.2.54",
|
|
68
68
|
"@volter/supercode-trace": "^0.1.0",
|
|
69
|
-
"@volter/teams": "0.2.
|
|
69
|
+
"@volter/teams": "0.2.9",
|
|
70
70
|
"@xterm/addon-serialize": "^0.14.0",
|
|
71
71
|
"@xterm/headless": "^6.0.0",
|
|
72
72
|
"protobufjs": "8.8.0",
|
package/server/event-routes.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { appAuthentication } from '@volter/teams/server/apps';
|
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { validateListQuery } from '../contracts/index.mjs';
|
|
5
5
|
import { cursorHash, cursorToken, liveBindings } from './catalog.mjs';
|
|
6
|
-
import { writeSse } from '@volter/teams/server/events';
|
|
6
|
+
import { sseRoom, writeSse } from '@volter/teams/server/events';
|
|
7
7
|
|
|
8
8
|
function openStream(api, req, res, auth, requestId, fail) {
|
|
9
9
|
const count = api.openStreams.get(auth.credential_id) ?? 0;
|
|
@@ -32,9 +32,12 @@ function openStream(api, req, res, auth, requestId, fail) {
|
|
|
32
32
|
if (!closed) writeSse(res, { type, reason, terminal: true });
|
|
33
33
|
close();
|
|
34
34
|
},
|
|
35
|
-
|
|
35
|
+
// Waits for the response to flush past the backlog cap (sseRoom); only a reader that takes nothing is slow.
|
|
36
|
+
async write(event) {
|
|
36
37
|
if (closed) return false;
|
|
37
|
-
|
|
38
|
+
const room = await sseRoom(res, Buffer.byteLength(JSON.stringify(event)));
|
|
39
|
+
if (closed || room === 'closed') return false;
|
|
40
|
+
if (room === 'slow') {
|
|
38
41
|
this.terminal('resync_required', 'slow_reader');
|
|
39
42
|
return false;
|
|
40
43
|
}
|
|
@@ -122,14 +125,14 @@ export async function handleSessionEvents({ api, req, res, auth, id, url, reques
|
|
|
122
125
|
if (delivered > team.journal_revision || delivered < team.journal_revision && (retained.first_revision === null || delivered < retained.first_revision - 1)) fail(409, 'resync_required', 'event cursor is outside retained journal');
|
|
123
126
|
}
|
|
124
127
|
const stream = openStream(api, req, res, auth, requestId, fail);
|
|
125
|
-
if (after === null
|
|
128
|
+
if (after === null && !await stream.write({ type: 'snapshot', cursor: initial.event_cursor, revision: initial.revision, data: initial })) return;
|
|
126
129
|
const enqueue = streamQueue(stream, async () => {
|
|
127
130
|
if(!auth.bound&&!await appAuthentication(api,auth))fail(401,'authentication_required','app authorization changed');
|
|
128
131
|
// This worker operation reads current credential, membership, groups and every constituent ACL together.
|
|
129
132
|
await api.store.authorizedSession(sessionArgs(api, auth, sessionId));
|
|
130
133
|
const replay = await api.store.query('SELECT revision,kind FROM journal WHERE team_id=? AND session_id=? AND revision>? ORDER BY revision', [auth.team_id, sessionId, delivered]);
|
|
131
134
|
for (const event of replay) {
|
|
132
|
-
if (!stream.write(event.kind === 'delete'
|
|
135
|
+
if (!await stream.write(event.kind === 'delete'
|
|
133
136
|
? { type: 'session_deleted', cursor: String(event.revision), revision: event.revision, session_id: sessionId }
|
|
134
137
|
: { type: 'session_changed', cursor: String(event.revision), revision: event.revision, session_id: sessionId, reason: event.kind === 'metadata' ? 'metadata' : 'capture', read_required: true })) break;
|
|
135
138
|
delivered = event.revision;
|
|
@@ -226,7 +229,7 @@ export async function handleCatalogEvents({ api, req, res, auth, url, requestId,
|
|
|
226
229
|
// A binding's observed_at moves with every heartbeat; only a change of what the window says is a reset.
|
|
227
230
|
const digest = createHash('sha256').update(JSON.stringify(page.data, (key, value) => key === 'bindings' && Array.isArray(value) ? value.map(({ observed_at: _seen, ...binding }) => binding) : value)).digest('hex');
|
|
228
231
|
const cursor = `${page.revision}:${page.authEpoch}:${digest}`;
|
|
229
|
-
if (cursor !== lastCursor && stream.write({ type: 'catalog_reset', cursor, revision: page.revision, data: page.data })) lastCursor = cursor;
|
|
232
|
+
if (cursor !== lastCursor && await stream.write({ type: 'catalog_reset', cursor, revision: page.revision, data: page.data })) lastCursor = cursor;
|
|
230
233
|
});
|
|
231
234
|
// Subscribe before taking the first snapshot, and serialize resets so older windows cannot arrive last.
|
|
232
235
|
stream.cleanup(api.events.subscribe(`team:${auth.team_id}`, enqueue));
|