@volter/supercode-teams 0.3.8 → 0.3.9
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/machine-events.mjs +7 -3
- package/package.json +2 -2
- package/server/node-upgrade.mjs +4 -1
- package/workspaces.mjs +3 -1
package/machine-events.mjs
CHANGED
|
@@ -35,13 +35,17 @@ export class MachineEvents {
|
|
|
35
35
|
if(!response.ok||!response.headers.get('content-type')?.includes('text/event-stream'))throw new Error(`machine events returned HTTP ${response.status}, expected event stream`);
|
|
36
36
|
const reader=response.body.getReader();const decoder=new TextDecoder();let buffer='';
|
|
37
37
|
try{for(;;){const {value,done}=await reader.read();if(done)break;clearTimeout(deadline);deadline=setTimeout(()=>streamAbort.abort(new Error('machine event stream timed out')),45000);buffer+=decoder.decode(value,{stream:true});if(buffer.length>2*1024*1024)throw new Error('machine event exceeded stream bound');let boundary;while((boundary=buffer.indexOf('\n\n'))>=0){const frame=buffer.slice(0,boundary);buffer=buffer.slice(boundary+2);const data=frame.split('\n').filter(line=>line.startsWith('data:')).map(line=>line.slice(5).trimStart()).join('\n');if(data)await this.receive(JSON.parse(data));}}}finally{await reader.cancel().catch(()=>{});}
|
|
38
|
-
}catch(error){if(!this.abort.signal.aborted){if(this.error!==error.message)process.stderr.write(
|
|
38
|
+
}catch(error){if(!this.abort.signal.aborted){this.failingSince??=Date.now();if(this.error!==error.message)process.stderr.write(`${new Date().toISOString()} supercode teams machine events: ${error.message} (cursor ${this.state.cursor})\n`);this.error=error.message;}}
|
|
39
39
|
clearTimeout(deadline);this.connected=false;
|
|
40
40
|
if(!this.abort.signal.aborted)await new Promise(resolve=>{const done=()=>{clearTimeout(timer);this.abort.signal.removeEventListener('abort',done);resolve();};const timer=setTimeout(done,5000);this.abort.signal.addEventListener('abort',done,{once:true});});
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
async receive(event){
|
|
44
|
-
if(event.type==='machine_ready'){
|
|
44
|
+
if(event.type==='machine_ready'){
|
|
45
|
+
// A stream back after a failure says so, with how long it was down: a notice that arrives late is then traceable.
|
|
46
|
+
if(this.failingSince)process.stderr.write(`${new Date().toISOString()} supercode teams machine events: stream back at cursor ${event.cursor} after ${Math.round((Date.now()-this.failingSince)/1000)} s of: ${this.error}\n`);
|
|
47
|
+
this.connected=true;this.error=null;this.failingSince=null;return;
|
|
48
|
+
}
|
|
45
49
|
if(event.type==='access_revoked')throw new Error('machine event subscription authorization revoked');
|
|
46
50
|
if(!['machine_snapshot','machine_linked','machine_unlinked'].includes(event.type))return;
|
|
47
51
|
if(!/^\d+$/.test(event.cursor??''))throw new Error('invalid machine event cursor');
|
|
@@ -74,6 +78,6 @@ export class MachineEvents {
|
|
|
74
78
|
child.stdout.on('data',chunk=>{detail=(detail+chunk).slice(-1000);});child.stderr.on('data',chunk=>{detail=(detail+chunk).slice(-1000);});
|
|
75
79
|
child.once('error',error=>{clearTimeout(timer);reject(error);});child.once('close',code=>{clearTimeout(timer);let receipt;try{receipt=JSON.parse(detail.trim().split('\n').at(-1));}catch{}code===0&&receipt?.code===0?resolve():reject(new Error(receipt?.text||detail||`exit ${code}`));});
|
|
76
80
|
child.stdin.on('error',()=>{});
|
|
77
|
-
child.stdin.end(JSON.stringify({op:'send',from:`sc:${this.machine}:operator:teams-events`,from_name:'Teams machine events',to:item.to,id:item.id,queue:item.to.split(':')[2]==='board',
|
|
81
|
+
child.stdin.end(JSON.stringify({op:'send',from:`sc:${this.machine}:operator:teams-events`,from_name:'Teams machine events',to:item.to,id:item.id,queue:item.to.split(':')[2]==='board',notice:true,body:item.body}));
|
|
78
82
|
});}
|
|
79
83
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
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",
|
|
@@ -66,7 +66,7 @@
|
|
|
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.6",
|
|
70
70
|
"@xterm/addon-serialize": "^0.14.0",
|
|
71
71
|
"@xterm/headless": "^6.0.0",
|
|
72
72
|
"protobufjs": "8.8.0",
|
package/server/node-upgrade.mjs
CHANGED
|
@@ -22,11 +22,14 @@ export function createMachineUpgradeHandler(api, { authenticate }) {
|
|
|
22
22
|
socket.end(`HTTP/1.1 ${status} ${code}\r\nContent-Type: application/json\r\nContent-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r\n\r\n${body}`);
|
|
23
23
|
};
|
|
24
24
|
const handler = async (req, socket, head) => {
|
|
25
|
+
// A caller that drops its connection mid-handshake (or later) ends only its own socket: an unheard socket error
|
|
26
|
+
// would end the whole server (measured: a client's ECONNRESET during authorization stopped the Node host).
|
|
27
|
+
socket.on('error', () => socket.destroy());
|
|
25
28
|
try {
|
|
26
29
|
const decision = await authorizeMachineUpgrade(api, { authenticate, url: req.url, headers: req.headers });
|
|
27
30
|
if (decision.status) return refuse(socket, decision.status, decision.code, decision.message);
|
|
28
31
|
return wss.handleUpgrade(req, socket, head, (ws) => {
|
|
29
|
-
ws.alive = true; ws.on('pong', () => { ws.alive = true; });
|
|
32
|
+
ws.alive = true; ws.on('pong', () => { ws.alive = true; }); ws.on('error', () => ws.terminate());
|
|
30
33
|
void acceptMachineSocket(api, decision, createWebSocketStream(ws), (reason) => ws.close(1000, String(reason ?? 'closed').slice(0, 100)));
|
|
31
34
|
});
|
|
32
35
|
} catch (error) {
|
package/workspaces.mjs
CHANGED
|
@@ -20,7 +20,9 @@ export function repositoryId(value) {
|
|
|
20
20
|
export function inspectCheckout(path) {
|
|
21
21
|
const raw = git(path, ['rev-parse', '--path-format=absolute', '--show-toplevel', '--git-dir', '--git-common-dir']);
|
|
22
22
|
const [root, dir, common] = raw.split('\n').map(p => realpathSync(resolve(path, p)));
|
|
23
|
-
|
|
23
|
+
// The repository the checkout declares (remote.origin.url), not where a URL rewrite (insteadOf: a mirror, a proxy, a
|
|
24
|
+
// World's twin) sends its transport.
|
|
25
|
+
const remote = git(root, ['config', '--get', 'remote.origin.url'], true);
|
|
24
26
|
return { path: realpathSync(path), root, linked: dir !== common, repository: remote ? repositoryId(remote) : null, branch: git(root, ['branch', '--show-current']) || null };
|
|
25
27
|
}
|
|
26
28
|
export class Workspaces {
|