@volter/teams 0.1.37 → 0.1.38

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.
Files changed (2) hide show
  1. package/machine-core.mjs +18 -5
  2. package/package.json +1 -1
package/machine-core.mjs CHANGED
@@ -8,7 +8,8 @@
8
8
  // It decides nothing on anyone's behalf. Every act is logged.
9
9
  import { createServer, connect as netConnect } from 'node:net';
10
10
  import { EventEmitter } from 'node:events';
11
- import { existsSync, mkdirSync, rmSync, chmodSync } from 'node:fs';
11
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, chmodSync, statSync } from 'node:fs';
12
+ import { dirname, join } from 'node:path';
12
13
  import { randomBytes } from 'node:crypto';
13
14
  import { WebSocket, createWebSocketStream } from 'ws';
14
15
  import { Channel, NODE_PROTOCOL } from './channel.mjs';
@@ -89,13 +90,22 @@ export class MachineCore extends EventEmitter {
89
90
  await clearStaleSocket(this.socketPath);
90
91
  await this.beforeListen();
91
92
  this.server = createServer((socket) => this.#accept(socket));
93
+ // A Unix socket is bound under a name of this daemon's own and then renamed into place: Node unlinks the name it
94
+ // bound when the server closes, and that name must never be the shared path, which a later daemon may serve from a
95
+ // file of its own (a daemon stopping later deleted the serving daemon's socket and cut it off).
96
+ const bindPath = isNamedPipe(this.socketPath) ? this.socketPath : join(dirname(this.socketPath), `.m${process.pid.toString(36)}`);
97
+ if (bindPath !== this.socketPath) rmSync(bindPath, { force: true });
92
98
  await new Promise((resolveListen, reject) => {
93
99
  this.server.once('error', reject);
94
- this.server.listen(this.socketPath, () => { this.server.off('error', reject); resolveListen(); });
100
+ this.server.listen(bindPath, () => { this.server.off('error', reject); resolveListen(); });
95
101
  });
96
102
  // Owner-only: the socket is the local door, and whoever opens it is this OS user. A named pipe's default
97
103
  // access already admits only its owner (and SYSTEM and Administrators) to connect.
98
- if (!isNamedPipe(this.socketPath)) chmodSync(this.socketPath, 0o600);
104
+ if (!isNamedPipe(this.socketPath)) {
105
+ chmodSync(bindPath, 0o600);
106
+ this.socketInode = statSync(bindPath).ino;
107
+ renameSync(bindPath, this.socketPath);
108
+ }
99
109
  this.address = this.socketPath;
100
110
  writeJson(this.p.machine, { pid: process.pid, id: this.id, socket: this.socketPath, startedAt: Date.now(), version: MACHINE_VERSION, protocol: NODE_PROTOCOL, floor: this.floor, server: this.link?.server ?? null });
101
111
  this.emit('listening', this.socketPath);
@@ -116,8 +126,11 @@ export class MachineCore extends EventEmitter {
116
126
  for (const { channel } of this.connections.values()) channel.close();
117
127
  this.server?.close();
118
128
  await this.afterStop();
119
- if (!isNamedPipe(this.socketPath)) rmSync(this.socketPath, { force: true });
120
- if (existsSync(this.p.machine)) rmSync(this.p.machine);
129
+ // Only this daemon's own socket and record: a daemon that has since taken the path over keeps them.
130
+ if (!isNamedPipe(this.socketPath) && this.socketInode !== undefined) {
131
+ try { if (statSync(this.socketPath).ino === this.socketInode) rmSync(this.socketPath, { force: true }); } catch { /* already gone */ }
132
+ }
133
+ try { if (JSON.parse(readFileSync(this.p.machine, 'utf8')).pid === process.pid) rmSync(this.p.machine); } catch { /* absent or unreadable */ }
121
134
  }
122
135
 
123
136
  /** Before the connections close: a product finishes or abandons what is in flight. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/teams",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "type": "module",
5
5
  "description": "Volter Teams, the neutral team plane: machine identity and daemon core, the local door and server link, admission from machine grants, product doors, and the Teams server's neutral routes (company RFC 0008).",
6
6
  "license": "MIT",