@volter/teams 0.1.6 → 0.1.7
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/home.mjs +8 -2
- package/machine-core.mjs +8 -6
- package/package.json +1 -1
package/home.mjs
CHANGED
|
@@ -31,14 +31,19 @@ export function teamsHome(env = process.env) {
|
|
|
31
31
|
return dir;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** A Windows named pipe (`\\.\pipe\…`): no file to make, chmod or remove; it vanishes with its last handle. */
|
|
35
|
+
export const isNamedPipe = (path) => /^\\\\[.?]\\pipe\\/i.test(path);
|
|
36
|
+
|
|
34
37
|
/**
|
|
35
38
|
* A Unix socket path for `name` in `dir`: the path itself when it fits a socket
|
|
36
39
|
* address (104 bytes on macOS, 108 on Linux); otherwise, as tmux does, a
|
|
37
40
|
* per-user directory under /tmp that only this user may enter, with the socket
|
|
38
|
-
* named by a hash of the path it stands for.
|
|
41
|
+
* named by a hash of the path it stands for. Node on Windows listens only on
|
|
42
|
+
* named pipes, so there it is always a pipe named by that same hash.
|
|
39
43
|
*/
|
|
40
|
-
export function shortSocketPath(dir, name) {
|
|
44
|
+
export function shortSocketPath(dir, name, platform = process.platform) {
|
|
41
45
|
const inside = join(dir, name);
|
|
46
|
+
if (platform === 'win32') return `\\\\.\\pipe\\supercode-${createHash('sha256').update(inside).digest('hex').slice(0, 24)}`;
|
|
42
47
|
if (Buffer.byteLength(inside) <= 100) return inside;
|
|
43
48
|
const uid = typeof process.getuid === 'function' ? process.getuid() : 'user';
|
|
44
49
|
return join('/tmp', `supercode-machine-${uid}`, `${createHash('sha256').update(inside).digest('hex').slice(0, 24)}.sock`);
|
|
@@ -49,6 +54,7 @@ export const machineSocketPath = (home) => shortSocketPath(home, 'machine.sock')
|
|
|
49
54
|
|
|
50
55
|
/** The socket's directory exists; the shared /tmp fallback must be ours and admit only us, and anything else there is refused, never adopted. */
|
|
51
56
|
export function ensureSocketDirectory(socketPath) {
|
|
57
|
+
if (isNamedPipe(socketPath)) return;
|
|
52
58
|
const dir = dirname(socketPath);
|
|
53
59
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
54
60
|
if (!basename(dir).startsWith('supercode-machine-') || dirname(dir) !== '/tmp') return;
|
package/machine-core.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { Channel, NODE_PROTOCOL } from './channel.mjs';
|
|
|
15
15
|
import { holds, admitFromGrants, LOCAL_OPERATOR } from './admission.mjs';
|
|
16
16
|
import { findProduct, DAEMON_KINDS, PRODUCT_MAX_MESSAGE, productProof, sameSecret } from './products.mjs';
|
|
17
17
|
import { loadOrCreateKeyPair, publicKeyId } from './identity.mjs';
|
|
18
|
-
import { teamsHome, paths, writeJson, readLimits, AccountLog, ensureSocketDirectory } from './home.mjs';
|
|
18
|
+
import { teamsHome, paths, writeJson, readLimits, AccountLog, ensureSocketDirectory, isNamedPipe } from './home.mjs';
|
|
19
19
|
|
|
20
20
|
export const MACHINE_VERSION = 2;
|
|
21
21
|
const LINK_BACKOFF_MIN_MS = 1_000;
|
|
@@ -93,8 +93,9 @@ export class MachineCore extends EventEmitter {
|
|
|
93
93
|
this.server.once('error', reject);
|
|
94
94
|
this.server.listen(this.socketPath, () => { this.server.off('error', reject); resolveListen(); });
|
|
95
95
|
});
|
|
96
|
-
// Owner-only: the socket is the local door, and whoever opens it is this OS user.
|
|
97
|
-
|
|
96
|
+
// Owner-only: the socket is the local door, and whoever opens it is this OS user. A named pipe's default
|
|
97
|
+
// access already admits only its owner (and SYSTEM and Administrators) to connect.
|
|
98
|
+
if (!isNamedPipe(this.socketPath)) chmodSync(this.socketPath, 0o600);
|
|
98
99
|
this.address = this.socketPath;
|
|
99
100
|
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 });
|
|
100
101
|
this.emit('listening', this.socketPath);
|
|
@@ -115,7 +116,7 @@ export class MachineCore extends EventEmitter {
|
|
|
115
116
|
for (const { channel } of this.connections.values()) channel.close();
|
|
116
117
|
this.server?.close();
|
|
117
118
|
await this.afterStop();
|
|
118
|
-
rmSync(this.socketPath, { force: true });
|
|
119
|
+
if (!isNamedPipe(this.socketPath)) rmSync(this.socketPath, { force: true });
|
|
119
120
|
if (existsSync(this.p.machine)) rmSync(this.p.machine);
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -385,13 +386,14 @@ export class MachineCore extends EventEmitter {
|
|
|
385
386
|
}
|
|
386
387
|
|
|
387
388
|
async function clearStaleSocket(path) {
|
|
388
|
-
|
|
389
|
+
// A named pipe has no file to leave behind: only a live daemon holds its name.
|
|
390
|
+
if (!isNamedPipe(path) && !existsSync(path)) return;
|
|
389
391
|
const live = await new Promise((done) => {
|
|
390
392
|
const probe = netConnect(path);
|
|
391
393
|
probe.once('connect', () => { probe.destroy(); done(true); });
|
|
392
394
|
probe.once('error', () => done(false));
|
|
393
395
|
});
|
|
394
396
|
if (live) throw Object.assign(new Error(`a machine daemon is already serving ${path}`), { code: 'EADDRINUSE' });
|
|
395
|
-
rmSync(path, { force: true });
|
|
397
|
+
if (!isNamedPipe(path)) rmSync(path, { force: true });
|
|
396
398
|
}
|
|
397
399
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/teams",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|