@thenewlabs/entangle-serve 2.6.2 → 2.8.0

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.
@@ -0,0 +1,167 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ const REGISTRY_FILE = 'sessions.json';
5
+ /**
6
+ * Resolve the runtime directory holding sockets, logs and the registry.
7
+ *
8
+ * Precedence:
9
+ * 1. `ENTANGLE_RUN_DIR` — explicit override (used by tests, and handy for the
10
+ * daemon to pin a location);
11
+ * 2. `$XDG_RUNTIME_DIR/entangle` — the standard per-user runtime dir;
12
+ * 3. `~/.entangle/run` — fallback when XDG is unset.
13
+ */
14
+ export function resolveRunDir() {
15
+ const override = process.env.ENTANGLE_RUN_DIR;
16
+ if (override)
17
+ return override;
18
+ const xdg = process.env.XDG_RUNTIME_DIR;
19
+ if (xdg)
20
+ return path.join(xdg, 'entangle');
21
+ return path.join(os.homedir(), '.entangle', 'run');
22
+ }
23
+ /** Ensure the runtime directory exists with 0700 perms, returning its path. */
24
+ export function ensureRunDir() {
25
+ const dir = resolveRunDir();
26
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
27
+ return dir;
28
+ }
29
+ /** Reduce an arbitrary name to a filesystem-safe token ([A-Za-z0-9._-]). */
30
+ export function sanitizeName(name) {
31
+ const safe = name.replace(/[^A-Za-z0-9._-]/g, '-');
32
+ // Avoid an empty basename or a name that resolves to a directory entry.
33
+ return safe.length > 0 && safe !== '.' && safe !== '..' ? safe : 'session';
34
+ }
35
+ /** Absolute path of the unix socket for a session. */
36
+ export function socketPath(name) {
37
+ return path.join(resolveRunDir(), `${sanitizeName(name)}.sock`);
38
+ }
39
+ /** Absolute path of the log file for a session. */
40
+ export function logPath(name) {
41
+ return path.join(resolveRunDir(), `${sanitizeName(name)}.log`);
42
+ }
43
+ /**
44
+ * Stable short session name derived from a capability id, so an unnamed session
45
+ * is keyed on its capability. Deterministic: the same capId always yields the
46
+ * same name. Uses the sanitized leading characters of the capId (capIds are
47
+ * already url-safe base-ish tokens) to stay human-recognizable.
48
+ */
49
+ export function defaultSessionName(capId) {
50
+ const token = sanitizeName(capId).replace(/[._-]/g, '').slice(0, 10) || 'anon';
51
+ return `cap-${token}`;
52
+ }
53
+ // --- registry file I/O (best-effort, tolerant of missing/corrupt data) -----
54
+ function registryPath() {
55
+ return path.join(resolveRunDir(), REGISTRY_FILE);
56
+ }
57
+ function isSessionInfo(value) {
58
+ if (typeof value !== 'object' || value === null)
59
+ return false;
60
+ const v = value;
61
+ return (typeof v.name === 'string' &&
62
+ typeof v.socket === 'string' &&
63
+ typeof v.logFile === 'string' &&
64
+ typeof v.pid === 'number' &&
65
+ typeof v.capId === 'string' &&
66
+ typeof v.url === 'string' &&
67
+ typeof v.createdAt === 'number');
68
+ }
69
+ /** Read the registry. A missing or corrupt file is treated as empty. */
70
+ export function listSessions() {
71
+ let raw;
72
+ try {
73
+ raw = fs.readFileSync(registryPath(), 'utf8');
74
+ }
75
+ catch {
76
+ return []; // missing file (or unreadable) → no sessions
77
+ }
78
+ try {
79
+ const parsed = JSON.parse(raw);
80
+ if (!Array.isArray(parsed))
81
+ return [];
82
+ return parsed.filter(isSessionInfo);
83
+ }
84
+ catch {
85
+ return []; // corrupt JSON → treat as empty
86
+ }
87
+ }
88
+ function writeSessions(sessions) {
89
+ ensureRunDir();
90
+ const tmp = `${registryPath()}.${process.pid}.tmp`;
91
+ try {
92
+ fs.writeFileSync(tmp, JSON.stringify(sessions, null, 2), { mode: 0o600 });
93
+ fs.renameSync(tmp, registryPath()); // atomic replace
94
+ }
95
+ catch {
96
+ try {
97
+ fs.unlinkSync(tmp);
98
+ }
99
+ catch { /* nothing to clean up */ }
100
+ }
101
+ }
102
+ /** Add (or replace by name) a session in the registry. */
103
+ export function addSession(info) {
104
+ const sessions = listSessions().filter((s) => s.name !== info.name);
105
+ sessions.push(info);
106
+ writeSessions(sessions);
107
+ }
108
+ /** Remove a session from the registry by name. No-op if absent. */
109
+ export function removeSession(name) {
110
+ const sessions = listSessions();
111
+ const next = sessions.filter((s) => s.name !== name);
112
+ if (next.length !== sessions.length)
113
+ writeSessions(next);
114
+ }
115
+ /** Find a registered session by name. */
116
+ export function findSession(name) {
117
+ return listSessions().find((s) => s.name === name);
118
+ }
119
+ // --- liveness --------------------------------------------------------------
120
+ /** True if the pid is alive AND the session's socket file still exists. */
121
+ export function isAlive(info) {
122
+ return pidAlive(info.pid) && fs.existsSync(info.socket);
123
+ }
124
+ /** True if a process with `pid` exists and is signalable by us. */
125
+ function pidAlive(pid) {
126
+ if (!Number.isInteger(pid) || pid <= 0)
127
+ return false;
128
+ try {
129
+ process.kill(pid, 0);
130
+ return true;
131
+ }
132
+ catch (err) {
133
+ // EPERM means the process exists but we may not signal it → still alive.
134
+ return err.code === 'EPERM';
135
+ }
136
+ }
137
+ /**
138
+ * Prune dead state: drop registry entries whose pid is gone, and unlink any
139
+ * `.sock` file in the run dir that no live registered session owns.
140
+ */
141
+ export function cleanupStale() {
142
+ const dir = resolveRunDir();
143
+ const sessions = listSessions();
144
+ const live = sessions.filter((s) => pidAlive(s.pid));
145
+ if (live.length !== sessions.length)
146
+ writeSessions(live);
147
+ const liveSockets = new Set(live.map((s) => s.socket));
148
+ let entries;
149
+ try {
150
+ entries = fs.readdirSync(dir);
151
+ }
152
+ catch {
153
+ return; // dir gone → nothing to unlink
154
+ }
155
+ for (const entry of entries) {
156
+ if (!entry.endsWith('.sock'))
157
+ continue;
158
+ const full = path.join(dir, entry);
159
+ if (liveSockets.has(full))
160
+ continue; // owned by a live session
161
+ try {
162
+ fs.unlinkSync(full);
163
+ }
164
+ catch { /* raced / already gone */ }
165
+ }
166
+ }
167
+ //# sourceMappingURL=session-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../src/session-registry.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thenewlabs/entangle-serve",
3
- "version": "2.6.2",
3
+ "version": "2.8.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {
@@ -17,9 +17,9 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
20
- "@thenewlabs/entangle-crypto": "^2.6.2",
21
- "@thenewlabs/entangle-protocol": "^2.6.2",
22
- "@thenewlabs/entangle-utils": "^2.6.2",
20
+ "@thenewlabs/entangle-crypto": "^2.8.0",
21
+ "@thenewlabs/entangle-protocol": "^2.8.0",
22
+ "@thenewlabs/entangle-utils": "^2.8.0",
23
23
  "cborg": "^4.5.8",
24
24
  "commander": "^15.0.0",
25
25
  "ws": "^8.21.0"