claude-code-session-manager 0.38.2 → 0.38.3
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/dist/assets/{TiptapBody-UWL5KIJG.js → TiptapBody-R9igz9Iz.js} +1 -1
- package/dist/assets/{index-CZPF8Qve.js → index-BJRUaZ96.js} +145 -145
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/pr-review-sweep/1-classify/SKILL.md +11 -0
- package/plugins/session-manager-dev/skills/pr-review-sweep/3-queue/SKILL.md +8 -1
- package/src/main/__tests__/browserAgentServer.test.cjs +50 -1
- package/src/main/browserAgentServer.cjs +26 -5
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +72 -1
- package/src/main/lib/localAdminHttp.cjs +78 -5
package/dist/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
+
<script type="module" crossorigin src="./assets/index-BJRUaZ96.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="./assets/monaco-editor-BW5C4Iv1.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="./assets/monaco-editor-BTnBOi8r.css">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="./assets/index-DyOGjslF.css">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-session-manager",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.3",
|
|
4
4
|
"description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main/index.cjs",
|
|
@@ -38,6 +38,17 @@ there's a concrete failing case (a real input that breaks today); otherwise **fe
|
|
|
38
38
|
Record the reasoning either way — `pr-review-sweep:queue` uses it to pick the PRD template,
|
|
39
39
|
and a future reader should be able to see why a borderline call went the way it did.
|
|
40
40
|
|
|
41
|
+
## Known-pattern check (if the project curates one)
|
|
42
|
+
|
|
43
|
+
Before finalizing type/reasoning, check whether the thread matches an already-documented
|
|
44
|
+
recurring pattern. Some projects keep a curated, evolving corpus of past review findings
|
|
45
|
+
(commonly `docs/review-*.md`, indexed from `docs/README.md`, fed by a project-local
|
|
46
|
+
`project-status-local` mining step — sigma is the reference example). If such docs exist in
|
|
47
|
+
this repo and the thread matches a named pattern there, say so in the reasoning and cite the
|
|
48
|
+
doc/section — it strengthens the bug-vs-feature call (a documented recurring bug is bug,
|
|
49
|
+
full stop) and carries forward to `:queue`. If no such docs exist, or the thread doesn't
|
|
50
|
+
match anything in them, this is a no-op — don't go hunting for docs a project doesn't have.
|
|
51
|
+
|
|
41
52
|
## Output
|
|
42
53
|
|
|
43
54
|
Per thread: `{disposition, type | null, reasoning}`. Needs-my-decision threads carry the
|
|
@@ -34,7 +34,14 @@ existing PR's branch), and the standard two independent verification passes befo
|
|
|
34
34
|
- **`bug` bundle** — additionally require: reproduce each defect described in the bundled
|
|
35
35
|
threads, write a regression test per defect that fails before the fix and passes after,
|
|
36
36
|
then fix. The PRD body should quote the reviewer's exact wording per thread so the
|
|
37
|
-
executor root-causes the actual reported defect, not a guessed one.
|
|
37
|
+
executor root-causes the actual reported defect, not a guessed one. If `:classify` flagged
|
|
38
|
+
a thread as matching a known documented pattern (see its "Known-pattern check"), the PRD
|
|
39
|
+
must say so explicitly and instruct the executor to **grep the rest of the codebase for
|
|
40
|
+
the same pattern, not just fix the one flagged instance** — several of sigma's own
|
|
41
|
+
documented patterns (e.g. an SSR-unsafe `useLayoutEffect`, an un-deduped array into a D1
|
|
42
|
+
`IN (...)`) turned out to be the exact same bug copy-pasted across 5+ sibling files, each
|
|
43
|
+
caught as a separate review comment on a separate PR because the first fix never swept the
|
|
44
|
+
rest. One PRD closing all occurrences beats N PRDs closing them one review cycle at a time.
|
|
38
45
|
- **`feature` bundle** — additionally require: scope the change to exactly what each
|
|
39
46
|
bundled thread asked for (no piggybacked scope creep across threads even though they're
|
|
40
47
|
bundled together), match existing patterns/conventions in the touched files, and add a
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
const { test } = require('node:test');
|
|
11
11
|
const assert = require('node:assert/strict');
|
|
12
12
|
const http = require('node:http');
|
|
13
|
-
const
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const path = require('node:path');
|
|
15
|
+
const { createBrowserAgentServer, TOKEN_PATH, resolveTokenPath } = require('../browserAgentServer.cjs');
|
|
14
16
|
|
|
15
17
|
function request(port, { method = 'GET', path: reqPath, token, body }) {
|
|
16
18
|
return new Promise((resolve, reject) => {
|
|
@@ -184,3 +186,50 @@ test('unknown route is a 404', async () => {
|
|
|
184
186
|
await server.stop();
|
|
185
187
|
}
|
|
186
188
|
});
|
|
189
|
+
|
|
190
|
+
test('SM_DEV=1 writes to a dev-suffixed path, never the production browser-agent-api.json', async () => {
|
|
191
|
+
const devPath = path.join(path.dirname(TOKEN_PATH), 'browser-agent-api.dev.json');
|
|
192
|
+
const prevMtime = fs.existsSync(TOKEN_PATH) ? fs.statSync(TOKEN_PATH).mtimeMs : null;
|
|
193
|
+
const prevEnv = process.env.SM_DEV;
|
|
194
|
+
const remote = makeFakeRemote();
|
|
195
|
+
let server;
|
|
196
|
+
try {
|
|
197
|
+
process.env.SM_DEV = '1';
|
|
198
|
+
server = createBrowserAgentServer(remote);
|
|
199
|
+
const { port, token } = await server.start();
|
|
200
|
+
assert.equal(resolveTokenPath(), devPath);
|
|
201
|
+
const parsed = JSON.parse(fs.readFileSync(devPath, 'utf8'));
|
|
202
|
+
assert.equal(parsed.port, port);
|
|
203
|
+
assert.equal(parsed.token, token);
|
|
204
|
+
if (prevMtime !== null) {
|
|
205
|
+
assert.equal(fs.statSync(TOKEN_PATH).mtimeMs, prevMtime);
|
|
206
|
+
} else {
|
|
207
|
+
assert.equal(fs.existsSync(TOKEN_PATH), false);
|
|
208
|
+
}
|
|
209
|
+
} finally {
|
|
210
|
+
if (server) await server.stop();
|
|
211
|
+
if (prevEnv === undefined) delete process.env.SM_DEV; else process.env.SM_DEV = prevEnv;
|
|
212
|
+
fs.rmSync(devPath, { force: true });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('with neither SM_DEV nor SM_E2E set, writes to the original browser-agent-api.json path', async () => {
|
|
217
|
+
const prevDev = process.env.SM_DEV;
|
|
218
|
+
const prevE2e = process.env.SM_E2E;
|
|
219
|
+
const remote = makeFakeRemote();
|
|
220
|
+
let server;
|
|
221
|
+
try {
|
|
222
|
+
delete process.env.SM_DEV;
|
|
223
|
+
delete process.env.SM_E2E;
|
|
224
|
+
assert.equal(resolveTokenPath(), TOKEN_PATH);
|
|
225
|
+
server = createBrowserAgentServer(remote);
|
|
226
|
+
const { port, token } = await server.start();
|
|
227
|
+
const parsed = JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf8'));
|
|
228
|
+
assert.equal(parsed.port, port);
|
|
229
|
+
assert.equal(parsed.token, token);
|
|
230
|
+
} finally {
|
|
231
|
+
if (server) await server.stop();
|
|
232
|
+
if (prevDev === undefined) delete process.env.SM_DEV; else process.env.SM_DEV = prevDev;
|
|
233
|
+
if (prevE2e === undefined) delete process.env.SM_E2E; else process.env.SM_E2E = prevE2e;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
* or derived from the scheduler's token.
|
|
22
22
|
* - Token compared with crypto.timingSafeEqual to avoid a timing
|
|
23
23
|
* side-channel on the comparison itself.
|
|
24
|
+
* - Mode-scoped token file, same rationale/fix as localAdminHttp.cjs's
|
|
25
|
+
* resolveTokenPath() (confirmed-live incident 2026-07-29): a dev/e2e
|
|
26
|
+
* launch (SM_DEV=1 / SM_E2E=1) writes to browser-agent-api.dev.json /
|
|
27
|
+
* browser-agent-api.e2e.json instead of the production file, so it can
|
|
28
|
+
* never silently orphan a concurrently-running production instance.
|
|
24
29
|
* - Four routes: list tabs (read-only), screenshot, single action
|
|
25
30
|
* (click/type/key/scroll/navigate), DOM/text snapshot.
|
|
26
31
|
*
|
|
@@ -44,6 +49,20 @@ const TOKEN_PATH = path.join(os.homedir(), '.claude', 'session-manager', 'browse
|
|
|
44
49
|
// still bounded so a malformed/malicious body can't exhaust memory.
|
|
45
50
|
const BODY_MAX_BYTES = 8 * 1024 * 1024;
|
|
46
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Mode-aware token file path, resolved fresh on every call — see
|
|
54
|
+
* localAdminHttp.cjs's resolveTokenPath() for the full rationale.
|
|
55
|
+
*/
|
|
56
|
+
function resolveTokenPath() {
|
|
57
|
+
if (process.env.SM_DEV === '1') {
|
|
58
|
+
return path.join(os.homedir(), '.claude', 'session-manager', 'browser-agent-api.dev.json');
|
|
59
|
+
}
|
|
60
|
+
if (process.env.SM_E2E === '1') {
|
|
61
|
+
return path.join(os.homedir(), '.claude', 'session-manager', 'browser-agent-api.e2e.json');
|
|
62
|
+
}
|
|
63
|
+
return TOKEN_PATH;
|
|
64
|
+
}
|
|
65
|
+
|
|
47
66
|
function timingSafeEqualStrings(a, b) {
|
|
48
67
|
const bufA = Buffer.from(String(a ?? ''));
|
|
49
68
|
const bufB = Buffer.from(String(b ?? ''));
|
|
@@ -103,14 +122,16 @@ function createBrowserAgentServer(remote) {
|
|
|
103
122
|
|
|
104
123
|
async function ensureToken() {
|
|
105
124
|
token = crypto.randomBytes(32).toString('hex');
|
|
106
|
-
|
|
107
|
-
|
|
125
|
+
const tokenPath = resolveTokenPath();
|
|
126
|
+
await config.writeJson(tokenPath, { port: null, token });
|
|
127
|
+
try { await fsp.chmod(tokenPath, 0o600); } catch { /* best-effort on platforms without POSIX perms */ }
|
|
108
128
|
return token;
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
async function persistPort(port) {
|
|
112
|
-
|
|
113
|
-
|
|
132
|
+
const tokenPath = resolveTokenPath();
|
|
133
|
+
await config.writeJson(tokenPath, { port, token });
|
|
134
|
+
try { await fsp.chmod(tokenPath, 0o600); } catch { /* */ }
|
|
114
135
|
}
|
|
115
136
|
|
|
116
137
|
function authorized(req) {
|
|
@@ -200,4 +221,4 @@ function createBrowserAgentServer(remote) {
|
|
|
200
221
|
return { start, stop, get token() { return token; }, get server() { return server; } };
|
|
201
222
|
}
|
|
202
223
|
|
|
203
|
-
module.exports = { createBrowserAgentServer, TOKEN_PATH };
|
|
224
|
+
module.exports = { createBrowserAgentServer, TOKEN_PATH, resolveTokenPath };
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
import { test, expect } from 'vitest';
|
|
15
15
|
const http = require('node:http');
|
|
16
16
|
const fs = require('node:fs');
|
|
17
|
-
const
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { createAdminHttp, TOKEN_PATH, resolveTokenPath } = require('../localAdminHttp.cjs');
|
|
18
19
|
|
|
19
20
|
function request(port, { method = 'GET', path: reqPath, token, body }) {
|
|
20
21
|
return new Promise((resolve, reject) => {
|
|
@@ -118,3 +119,73 @@ test('start() persists token file with port + token, mode 0600', async () => {
|
|
|
118
119
|
await admin.stop();
|
|
119
120
|
}
|
|
120
121
|
});
|
|
122
|
+
|
|
123
|
+
test('SM_DEV=1 writes to a dev-suffixed path, never the production admin-api.json', async () => {
|
|
124
|
+
const devPath = path.join(path.dirname(TOKEN_PATH), 'admin-api.dev.json');
|
|
125
|
+
const prevMtime = fs.existsSync(TOKEN_PATH) ? fs.statSync(TOKEN_PATH).mtimeMs : null;
|
|
126
|
+
const prevEnv = process.env.SM_DEV;
|
|
127
|
+
process.env.SM_DEV = '1';
|
|
128
|
+
const admin = createAdminHttp();
|
|
129
|
+
try {
|
|
130
|
+
const { port, token } = await admin.start();
|
|
131
|
+
expect(resolveTokenPath()).toBe(devPath);
|
|
132
|
+
const parsed = JSON.parse(fs.readFileSync(devPath, 'utf8'));
|
|
133
|
+
expect(parsed.port).toBe(port);
|
|
134
|
+
expect(parsed.token).toBe(token);
|
|
135
|
+
// Production file must be untouched by the dev-mode instance.
|
|
136
|
+
if (prevMtime !== null) {
|
|
137
|
+
expect(fs.statSync(TOKEN_PATH).mtimeMs).toBe(prevMtime);
|
|
138
|
+
} else {
|
|
139
|
+
expect(fs.existsSync(TOKEN_PATH)).toBe(false);
|
|
140
|
+
}
|
|
141
|
+
} finally {
|
|
142
|
+
await admin.stop();
|
|
143
|
+
if (prevEnv === undefined) delete process.env.SM_DEV; else process.env.SM_DEV = prevEnv;
|
|
144
|
+
fs.rmSync(devPath, { force: true });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('with neither SM_DEV nor SM_E2E set, writes to the original admin-api.json path', async () => {
|
|
149
|
+
const prevDev = process.env.SM_DEV;
|
|
150
|
+
const prevE2e = process.env.SM_E2E;
|
|
151
|
+
delete process.env.SM_DEV;
|
|
152
|
+
delete process.env.SM_E2E;
|
|
153
|
+
let admin;
|
|
154
|
+
try {
|
|
155
|
+
expect(resolveTokenPath()).toBe(TOKEN_PATH);
|
|
156
|
+
admin = createAdminHttp();
|
|
157
|
+
const { port, token } = await admin.start();
|
|
158
|
+
const parsed = JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf8'));
|
|
159
|
+
expect(parsed.port).toBe(port);
|
|
160
|
+
expect(parsed.token).toBe(token);
|
|
161
|
+
} finally {
|
|
162
|
+
if (admin) await admin.stop();
|
|
163
|
+
if (prevDev === undefined) delete process.env.SM_DEV; else process.env.SM_DEV = prevDev;
|
|
164
|
+
if (prevE2e === undefined) delete process.env.SM_E2E; else process.env.SM_E2E = prevE2e;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('dev and production token paths never collide', () => {
|
|
169
|
+
const prevEnv = process.env.SM_DEV;
|
|
170
|
+
try {
|
|
171
|
+
process.env.SM_DEV = '1';
|
|
172
|
+
const devPath = resolveTokenPath();
|
|
173
|
+
delete process.env.SM_DEV;
|
|
174
|
+
const prodPath = resolveTokenPath();
|
|
175
|
+
expect(devPath).not.toBe(prodPath);
|
|
176
|
+
} finally {
|
|
177
|
+
if (prevEnv === undefined) delete process.env.SM_DEV; else process.env.SM_DEV = prevEnv;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('start()s self-check answers on the health path with the fresh bearer token', async () => {
|
|
182
|
+
const admin = createAdminHttp();
|
|
183
|
+
const { port, token } = await admin.start();
|
|
184
|
+
try {
|
|
185
|
+
const res = await request(port, { path: '/__admin/health', token });
|
|
186
|
+
expect(res.status).toBe(200);
|
|
187
|
+
expect(res.json).toEqual({ ok: true });
|
|
188
|
+
} finally {
|
|
189
|
+
await admin.stop();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
@@ -15,6 +15,18 @@
|
|
|
15
15
|
* ~/.claude/session-manager/admin-api.json with 0600 perms.
|
|
16
16
|
* - Token compared with crypto.timingSafeEqual to avoid a timing
|
|
17
17
|
* side-channel on the comparison itself.
|
|
18
|
+
*
|
|
19
|
+
* Mode-scoped token file (confirmed-live incident 2026-07-29): a dev-mode or
|
|
20
|
+
* e2e Electron launch (SM_DEV=1 / SM_E2E=1) intentionally skips the
|
|
21
|
+
* single-instance lock (index.cjs), so it can run concurrently with a
|
|
22
|
+
* production instance. Without a mode-specific path, that dev/e2e instance's
|
|
23
|
+
* boot silently overwrote the production instance's admin-api.json with its
|
|
24
|
+
* own (soon-to-be-dead) port+token, orphaning scheduler-mcp-server.cjs's
|
|
25
|
+
* access to the still-running production app with no error anywhere. Dev/e2e
|
|
26
|
+
* instances now write to admin-api.dev.json / admin-api.e2e.json instead —
|
|
27
|
+
* see resolveTokenPath(). Production (neither env var set) is unaffected:
|
|
28
|
+
* it still writes/reads admin-api.json, matching scheduler-mcp-server.cjs's
|
|
29
|
+
* hardcoded read path.
|
|
18
30
|
*/
|
|
19
31
|
|
|
20
32
|
'use strict';
|
|
@@ -28,6 +40,29 @@ const config = require('../config.cjs');
|
|
|
28
40
|
|
|
29
41
|
const TOKEN_PATH = path.join(os.homedir(), '.claude', 'session-manager', 'admin-api.json');
|
|
30
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Mode-aware token file path, resolved fresh on every call (not cached at
|
|
45
|
+
* module load) so a dev/e2e launch never collides with a production
|
|
46
|
+
* instance's admin-api.json. index.cjs treats SM_DEV and SM_E2E as one OR'd
|
|
47
|
+
* `isDev` boolean with no ordering between them; if both happen to be set,
|
|
48
|
+
* this resolves to the SM_DEV path (checked first below) — an arbitrary but
|
|
49
|
+
* harmless tie-break, since real launches only ever set one or the other.
|
|
50
|
+
*/
|
|
51
|
+
function resolveTokenPath() {
|
|
52
|
+
if (process.env.SM_DEV === '1') {
|
|
53
|
+
return path.join(os.homedir(), '.claude', 'session-manager', 'admin-api.dev.json');
|
|
54
|
+
}
|
|
55
|
+
if (process.env.SM_E2E === '1') {
|
|
56
|
+
return path.join(os.homedir(), '.claude', 'session-manager', 'admin-api.e2e.json');
|
|
57
|
+
}
|
|
58
|
+
return TOKEN_PATH;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Reserved health-check path, handled directly (not via registerRoute) so
|
|
62
|
+
// start()'s post-boot self-check never depends on route-registration order
|
|
63
|
+
// relative to start() across callers (chatRunner/scheduler/prdCreate).
|
|
64
|
+
const HEALTH_PATH = '/__admin/health';
|
|
65
|
+
|
|
31
66
|
function timingSafeEqualStrings(a, b) {
|
|
32
67
|
const bufA = Buffer.from(String(a ?? ''));
|
|
33
68
|
const bufB = Buffer.from(String(b ?? ''));
|
|
@@ -79,14 +114,16 @@ function createAdminHttp() {
|
|
|
79
114
|
|
|
80
115
|
async function ensureToken() {
|
|
81
116
|
token = crypto.randomBytes(32).toString('hex');
|
|
82
|
-
|
|
83
|
-
|
|
117
|
+
const tokenPath = resolveTokenPath();
|
|
118
|
+
await config.writeJson(tokenPath, { port: null, token });
|
|
119
|
+
try { await fsp.chmod(tokenPath, 0o600); } catch { /* best-effort on platforms without POSIX perms */ }
|
|
84
120
|
return token;
|
|
85
121
|
}
|
|
86
122
|
|
|
87
123
|
async function persistPort(port) {
|
|
88
|
-
|
|
89
|
-
|
|
124
|
+
const tokenPath = resolveTokenPath();
|
|
125
|
+
await config.writeJson(tokenPath, { port, token });
|
|
126
|
+
try { await fsp.chmod(tokenPath, 0o600); } catch { /* */ }
|
|
90
127
|
}
|
|
91
128
|
|
|
92
129
|
function authorized(req) {
|
|
@@ -105,6 +142,10 @@ function createAdminHttp() {
|
|
|
105
142
|
sendJson(res, 401, { ok: false, error: 'unauthorized' });
|
|
106
143
|
return;
|
|
107
144
|
}
|
|
145
|
+
if (req.method === 'GET' && req.url === HEALTH_PATH) {
|
|
146
|
+
sendJson(res, 200, { ok: true });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
108
149
|
try {
|
|
109
150
|
const handler = routes.get(`${req.method} ${req.url}`);
|
|
110
151
|
if (!handler) {
|
|
@@ -117,6 +158,31 @@ function createAdminHttp() {
|
|
|
117
158
|
}
|
|
118
159
|
}
|
|
119
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Post-boot self-check: confirm the server actually answers on the port
|
|
163
|
+
* it just wrote to the token file, before start() reports success. Catches
|
|
164
|
+
* the class of bug where the written port/token is stale or the listener
|
|
165
|
+
* never came up right — otherwise the only symptom is a caller getting
|
|
166
|
+
* "unauthorized"/connection-refused with zero context in the logs.
|
|
167
|
+
*/
|
|
168
|
+
function selfCheck(port) {
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
const req = http.request(
|
|
171
|
+
{ hostname: '127.0.0.1', port, method: 'GET', path: HEALTH_PATH, headers: { Authorization: `Bearer ${token}` }, timeout: 2000 },
|
|
172
|
+
(res) => {
|
|
173
|
+
res.resume();
|
|
174
|
+
res.on('end', () => {
|
|
175
|
+
if (res.statusCode === 200) resolve();
|
|
176
|
+
else reject(new Error(`admin HTTP self-check got status ${res.statusCode} from port ${port}`));
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
req.on('timeout', () => { req.destroy(new Error(`admin HTTP self-check timed out on port ${port}`)); });
|
|
181
|
+
req.on('error', (e) => reject(new Error(`admin HTTP self-check failed to reach port ${port}: ${e.message}`)));
|
|
182
|
+
req.end();
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
120
186
|
async function start() {
|
|
121
187
|
await ensureToken();
|
|
122
188
|
server = http.createServer((req, res) => {
|
|
@@ -129,7 +195,13 @@ function createAdminHttp() {
|
|
|
129
195
|
server.listen(0, '127.0.0.1', resolve);
|
|
130
196
|
});
|
|
131
197
|
const { port } = server.address();
|
|
132
|
-
|
|
198
|
+
try {
|
|
199
|
+
await persistPort(port);
|
|
200
|
+
await selfCheck(port);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
await stop();
|
|
203
|
+
throw e;
|
|
204
|
+
}
|
|
133
205
|
return { port, token };
|
|
134
206
|
}
|
|
135
207
|
|
|
@@ -151,6 +223,7 @@ function createAdminHttp() {
|
|
|
151
223
|
module.exports = {
|
|
152
224
|
createAdminHttp,
|
|
153
225
|
TOKEN_PATH,
|
|
226
|
+
resolveTokenPath,
|
|
154
227
|
timingSafeEqualStrings,
|
|
155
228
|
readBody,
|
|
156
229
|
sendJson,
|