@omnicross/daemon 0.1.3 → 0.1.4
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/cli.cjs +32 -8
- package/dist/cli.js +32 -8
- package/dist/index.cjs +32 -8
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +32 -8
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -966,6 +966,7 @@ var CodexOAuthSessionStore = class {
|
|
|
966
966
|
ttlMs;
|
|
967
967
|
sessions = /* @__PURE__ */ new Map();
|
|
968
968
|
activeSessionId = null;
|
|
969
|
+
aborters = /* @__PURE__ */ new Map();
|
|
969
970
|
/** Whether a codex sign-in is currently in flight (port 1455 held). */
|
|
970
971
|
isBusy() {
|
|
971
972
|
this.sweep();
|
|
@@ -977,13 +978,22 @@ var CodexOAuthSessionStore = class {
|
|
|
977
978
|
const sessionId = import_node_crypto3.default.randomBytes(24).toString("base64url");
|
|
978
979
|
this.sessions.set(sessionId, { status: "pending", createdAt: Date.now() });
|
|
979
980
|
this.activeSessionId = sessionId;
|
|
980
|
-
|
|
981
|
+
const controller = new AbortController();
|
|
982
|
+
this.aborters.set(sessionId, controller);
|
|
983
|
+
return { sessionId, signal: controller.signal };
|
|
981
984
|
}
|
|
982
985
|
/** Settle a flow (done/error) + free the active slot. */
|
|
983
986
|
settle(sessionId, status, error) {
|
|
984
987
|
const prior = this.sessions.get(sessionId);
|
|
985
988
|
this.sessions.set(sessionId, { status, error, createdAt: prior?.createdAt ?? Date.now() });
|
|
986
989
|
if (this.activeSessionId === sessionId) this.activeSessionId = null;
|
|
990
|
+
this.aborters.delete(sessionId);
|
|
991
|
+
}
|
|
992
|
+
cancel(sessionId) {
|
|
993
|
+
if (!this.sessions.has(sessionId)) return false;
|
|
994
|
+
this.aborters.get(sessionId)?.abort();
|
|
995
|
+
this.settle(sessionId, "error", "login: cancelled");
|
|
996
|
+
return true;
|
|
987
997
|
}
|
|
988
998
|
/** Read a flow's status (token-free), or null when unknown/expired. */
|
|
989
999
|
get(sessionId) {
|
|
@@ -1012,13 +1022,13 @@ function handleCodexOAuthStart(deps) {
|
|
|
1012
1022
|
);
|
|
1013
1023
|
}
|
|
1014
1024
|
const { authUrl, codeVerifier, state } = import_subscriptions.codexOAuth.generateAuthParams();
|
|
1015
|
-
const sessionId = deps.codexSessions.begin();
|
|
1016
|
-
void runCodexLoopback(sessionId, codeVerifier, state, deps);
|
|
1025
|
+
const { sessionId, signal } = deps.codexSessions.begin();
|
|
1026
|
+
void runCodexLoopback(sessionId, codeVerifier, state, signal, deps);
|
|
1017
1027
|
return { status: 200, body: { authUrl, sessionId } };
|
|
1018
1028
|
}
|
|
1019
|
-
async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
|
|
1029
|
+
async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
|
|
1020
1030
|
try {
|
|
1021
|
-
const code = await deps.codexAwaitLoopback(state);
|
|
1031
|
+
const code = await deps.codexAwaitLoopback(state, void 0, signal);
|
|
1022
1032
|
const result = await import_subscriptions.codexOAuth.exchangeCodeForTokens(
|
|
1023
1033
|
{ authorizationCode: code, codeVerifier, state },
|
|
1024
1034
|
deps.oauthExchangeFetch
|
|
@@ -1040,6 +1050,10 @@ async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
|
|
|
1040
1050
|
deps.codexSessions.settle(sessionId, "error", reason);
|
|
1041
1051
|
}
|
|
1042
1052
|
}
|
|
1053
|
+
function handleCodexOAuthCancel(sessionId, deps) {
|
|
1054
|
+
if (!deps.codexSessions.cancel(sessionId)) return err(404, "unknown or expired codex sign-in session");
|
|
1055
|
+
return { status: 200, body: { ok: true } };
|
|
1056
|
+
}
|
|
1043
1057
|
function handleCodexOAuthStatus(sessionId, deps) {
|
|
1044
1058
|
const s = deps.codexSessions.get(sessionId);
|
|
1045
1059
|
if (!s) return err(404, "unknown or expired codex sign-in session");
|
|
@@ -3831,6 +3845,10 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
3831
3845
|
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
3832
3846
|
return writeJson2(res, result.status, result.body);
|
|
3833
3847
|
}
|
|
3848
|
+
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
3849
|
+
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
3850
|
+
return writeJson2(res, result.status, result.body);
|
|
3851
|
+
}
|
|
3834
3852
|
if (method === "PUT" || method === "POST" || method === "DELETE") {
|
|
3835
3853
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
3836
3854
|
if (!providerId) {
|
|
@@ -4171,7 +4189,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
4171
4189
|
}
|
|
4172
4190
|
|
|
4173
4191
|
// src/admin/version.ts
|
|
4174
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
4192
|
+
var DAEMON_VERSION = true ? "0.1.4" : "0.0.0-dev";
|
|
4175
4193
|
|
|
4176
4194
|
// src/admin/AdminServer.ts
|
|
4177
4195
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -4426,7 +4444,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
|
4426
4444
|
function pageHtml(message) {
|
|
4427
4445
|
return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
|
|
4428
4446
|
}
|
|
4429
|
-
function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
4447
|
+
function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal) {
|
|
4430
4448
|
return new Promise((resolve, reject) => {
|
|
4431
4449
|
let settled = false;
|
|
4432
4450
|
const finish = (server2, fn) => {
|
|
@@ -4460,6 +4478,12 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
4460
4478
|
res.end(pageHtml("Login complete."));
|
|
4461
4479
|
finish(server, () => resolve(code));
|
|
4462
4480
|
});
|
|
4481
|
+
const abort = () => finish(server, () => reject(new Error("login: cancelled")));
|
|
4482
|
+
if (signal?.aborted) {
|
|
4483
|
+
abort();
|
|
4484
|
+
return;
|
|
4485
|
+
}
|
|
4486
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
4463
4487
|
server.on("error", (err5) => {
|
|
4464
4488
|
if (settled) return;
|
|
4465
4489
|
settled = true;
|
|
@@ -7477,7 +7501,7 @@ function buildDaemon(config, paths) {
|
|
|
7477
7501
|
// the app polls the token-free status. A test seam (`paths.codexAwaitLoopback`)
|
|
7478
7502
|
// can inject a mock so no real port is bound.
|
|
7479
7503
|
codexSessions: new CodexOAuthSessionStore(),
|
|
7480
|
-
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs) => awaitLoopbackCode(state, timeoutMs)),
|
|
7504
|
+
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
7481
7505
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
7482
7506
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
7483
7507
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
package/dist/cli.js
CHANGED
|
@@ -960,6 +960,7 @@ var CodexOAuthSessionStore = class {
|
|
|
960
960
|
ttlMs;
|
|
961
961
|
sessions = /* @__PURE__ */ new Map();
|
|
962
962
|
activeSessionId = null;
|
|
963
|
+
aborters = /* @__PURE__ */ new Map();
|
|
963
964
|
/** Whether a codex sign-in is currently in flight (port 1455 held). */
|
|
964
965
|
isBusy() {
|
|
965
966
|
this.sweep();
|
|
@@ -971,13 +972,22 @@ var CodexOAuthSessionStore = class {
|
|
|
971
972
|
const sessionId = crypto.randomBytes(24).toString("base64url");
|
|
972
973
|
this.sessions.set(sessionId, { status: "pending", createdAt: Date.now() });
|
|
973
974
|
this.activeSessionId = sessionId;
|
|
974
|
-
|
|
975
|
+
const controller = new AbortController();
|
|
976
|
+
this.aborters.set(sessionId, controller);
|
|
977
|
+
return { sessionId, signal: controller.signal };
|
|
975
978
|
}
|
|
976
979
|
/** Settle a flow (done/error) + free the active slot. */
|
|
977
980
|
settle(sessionId, status, error) {
|
|
978
981
|
const prior = this.sessions.get(sessionId);
|
|
979
982
|
this.sessions.set(sessionId, { status, error, createdAt: prior?.createdAt ?? Date.now() });
|
|
980
983
|
if (this.activeSessionId === sessionId) this.activeSessionId = null;
|
|
984
|
+
this.aborters.delete(sessionId);
|
|
985
|
+
}
|
|
986
|
+
cancel(sessionId) {
|
|
987
|
+
if (!this.sessions.has(sessionId)) return false;
|
|
988
|
+
this.aborters.get(sessionId)?.abort();
|
|
989
|
+
this.settle(sessionId, "error", "login: cancelled");
|
|
990
|
+
return true;
|
|
981
991
|
}
|
|
982
992
|
/** Read a flow's status (token-free), or null when unknown/expired. */
|
|
983
993
|
get(sessionId) {
|
|
@@ -1006,13 +1016,13 @@ function handleCodexOAuthStart(deps) {
|
|
|
1006
1016
|
);
|
|
1007
1017
|
}
|
|
1008
1018
|
const { authUrl, codeVerifier, state } = codexOAuth.generateAuthParams();
|
|
1009
|
-
const sessionId = deps.codexSessions.begin();
|
|
1010
|
-
void runCodexLoopback(sessionId, codeVerifier, state, deps);
|
|
1019
|
+
const { sessionId, signal } = deps.codexSessions.begin();
|
|
1020
|
+
void runCodexLoopback(sessionId, codeVerifier, state, signal, deps);
|
|
1011
1021
|
return { status: 200, body: { authUrl, sessionId } };
|
|
1012
1022
|
}
|
|
1013
|
-
async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
|
|
1023
|
+
async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
|
|
1014
1024
|
try {
|
|
1015
|
-
const code = await deps.codexAwaitLoopback(state);
|
|
1025
|
+
const code = await deps.codexAwaitLoopback(state, void 0, signal);
|
|
1016
1026
|
const result = await codexOAuth.exchangeCodeForTokens(
|
|
1017
1027
|
{ authorizationCode: code, codeVerifier, state },
|
|
1018
1028
|
deps.oauthExchangeFetch
|
|
@@ -1034,6 +1044,10 @@ async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
|
|
|
1034
1044
|
deps.codexSessions.settle(sessionId, "error", reason);
|
|
1035
1045
|
}
|
|
1036
1046
|
}
|
|
1047
|
+
function handleCodexOAuthCancel(sessionId, deps) {
|
|
1048
|
+
if (!deps.codexSessions.cancel(sessionId)) return err(404, "unknown or expired codex sign-in session");
|
|
1049
|
+
return { status: 200, body: { ok: true } };
|
|
1050
|
+
}
|
|
1037
1051
|
function handleCodexOAuthStatus(sessionId, deps) {
|
|
1038
1052
|
const s = deps.codexSessions.get(sessionId);
|
|
1039
1053
|
if (!s) return err(404, "unknown or expired codex sign-in session");
|
|
@@ -3852,6 +3866,10 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
3852
3866
|
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
3853
3867
|
return writeJson2(res, result.status, result.body);
|
|
3854
3868
|
}
|
|
3869
|
+
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
3870
|
+
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
3871
|
+
return writeJson2(res, result.status, result.body);
|
|
3872
|
+
}
|
|
3855
3873
|
if (method === "PUT" || method === "POST" || method === "DELETE") {
|
|
3856
3874
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
3857
3875
|
if (!providerId) {
|
|
@@ -4191,7 +4209,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
4191
4209
|
}
|
|
4192
4210
|
|
|
4193
4211
|
// src/admin/version.ts
|
|
4194
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
4212
|
+
var DAEMON_VERSION = true ? "0.1.4" : "0.0.0-dev";
|
|
4195
4213
|
|
|
4196
4214
|
// src/admin/AdminServer.ts
|
|
4197
4215
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -4446,7 +4464,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
|
4446
4464
|
function pageHtml(message) {
|
|
4447
4465
|
return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
|
|
4448
4466
|
}
|
|
4449
|
-
function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
4467
|
+
function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal) {
|
|
4450
4468
|
return new Promise((resolve, reject) => {
|
|
4451
4469
|
let settled = false;
|
|
4452
4470
|
const finish = (server2, fn) => {
|
|
@@ -4480,6 +4498,12 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
4480
4498
|
res.end(pageHtml("Login complete."));
|
|
4481
4499
|
finish(server, () => resolve(code));
|
|
4482
4500
|
});
|
|
4501
|
+
const abort = () => finish(server, () => reject(new Error("login: cancelled")));
|
|
4502
|
+
if (signal?.aborted) {
|
|
4503
|
+
abort();
|
|
4504
|
+
return;
|
|
4505
|
+
}
|
|
4506
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
4483
4507
|
server.on("error", (err5) => {
|
|
4484
4508
|
if (settled) return;
|
|
4485
4509
|
settled = true;
|
|
@@ -7504,7 +7528,7 @@ function buildDaemon(config, paths) {
|
|
|
7504
7528
|
// the app polls the token-free status. A test seam (`paths.codexAwaitLoopback`)
|
|
7505
7529
|
// can inject a mock so no real port is bound.
|
|
7506
7530
|
codexSessions: new CodexOAuthSessionStore(),
|
|
7507
|
-
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs) => awaitLoopbackCode(state, timeoutMs)),
|
|
7531
|
+
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
7508
7532
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
7509
7533
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
7510
7534
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
package/dist/index.cjs
CHANGED
|
@@ -80,6 +80,7 @@ var CodexOAuthSessionStore = class {
|
|
|
80
80
|
ttlMs;
|
|
81
81
|
sessions = /* @__PURE__ */ new Map();
|
|
82
82
|
activeSessionId = null;
|
|
83
|
+
aborters = /* @__PURE__ */ new Map();
|
|
83
84
|
/** Whether a codex sign-in is currently in flight (port 1455 held). */
|
|
84
85
|
isBusy() {
|
|
85
86
|
this.sweep();
|
|
@@ -91,13 +92,22 @@ var CodexOAuthSessionStore = class {
|
|
|
91
92
|
const sessionId = import_node_crypto.default.randomBytes(24).toString("base64url");
|
|
92
93
|
this.sessions.set(sessionId, { status: "pending", createdAt: Date.now() });
|
|
93
94
|
this.activeSessionId = sessionId;
|
|
94
|
-
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
this.aborters.set(sessionId, controller);
|
|
97
|
+
return { sessionId, signal: controller.signal };
|
|
95
98
|
}
|
|
96
99
|
/** Settle a flow (done/error) + free the active slot. */
|
|
97
100
|
settle(sessionId, status, error) {
|
|
98
101
|
const prior = this.sessions.get(sessionId);
|
|
99
102
|
this.sessions.set(sessionId, { status, error, createdAt: prior?.createdAt ?? Date.now() });
|
|
100
103
|
if (this.activeSessionId === sessionId) this.activeSessionId = null;
|
|
104
|
+
this.aborters.delete(sessionId);
|
|
105
|
+
}
|
|
106
|
+
cancel(sessionId) {
|
|
107
|
+
if (!this.sessions.has(sessionId)) return false;
|
|
108
|
+
this.aborters.get(sessionId)?.abort();
|
|
109
|
+
this.settle(sessionId, "error", "login: cancelled");
|
|
110
|
+
return true;
|
|
101
111
|
}
|
|
102
112
|
/** Read a flow's status (token-free), or null when unknown/expired. */
|
|
103
113
|
get(sessionId) {
|
|
@@ -126,13 +136,13 @@ function handleCodexOAuthStart(deps) {
|
|
|
126
136
|
);
|
|
127
137
|
}
|
|
128
138
|
const { authUrl, codeVerifier, state } = import_subscriptions.codexOAuth.generateAuthParams();
|
|
129
|
-
const sessionId = deps.codexSessions.begin();
|
|
130
|
-
void runCodexLoopback(sessionId, codeVerifier, state, deps);
|
|
139
|
+
const { sessionId, signal } = deps.codexSessions.begin();
|
|
140
|
+
void runCodexLoopback(sessionId, codeVerifier, state, signal, deps);
|
|
131
141
|
return { status: 200, body: { authUrl, sessionId } };
|
|
132
142
|
}
|
|
133
|
-
async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
|
|
143
|
+
async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
|
|
134
144
|
try {
|
|
135
|
-
const code = await deps.codexAwaitLoopback(state);
|
|
145
|
+
const code = await deps.codexAwaitLoopback(state, void 0, signal);
|
|
136
146
|
const result = await import_subscriptions.codexOAuth.exchangeCodeForTokens(
|
|
137
147
|
{ authorizationCode: code, codeVerifier, state },
|
|
138
148
|
deps.oauthExchangeFetch
|
|
@@ -154,6 +164,10 @@ async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
|
|
|
154
164
|
deps.codexSessions.settle(sessionId, "error", reason);
|
|
155
165
|
}
|
|
156
166
|
}
|
|
167
|
+
function handleCodexOAuthCancel(sessionId, deps) {
|
|
168
|
+
if (!deps.codexSessions.cancel(sessionId)) return err(404, "unknown or expired codex sign-in session");
|
|
169
|
+
return { status: 200, body: { ok: true } };
|
|
170
|
+
}
|
|
157
171
|
function handleCodexOAuthStatus(sessionId, deps) {
|
|
158
172
|
const s = deps.codexSessions.get(sessionId);
|
|
159
173
|
if (!s) return err(404, "unknown or expired codex sign-in session");
|
|
@@ -3535,6 +3549,10 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
3535
3549
|
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
3536
3550
|
return writeJson2(res, result.status, result.body);
|
|
3537
3551
|
}
|
|
3552
|
+
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
3553
|
+
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
3554
|
+
return writeJson2(res, result.status, result.body);
|
|
3555
|
+
}
|
|
3538
3556
|
if (method === "PUT" || method === "POST" || method === "DELETE") {
|
|
3539
3557
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
3540
3558
|
if (!providerId) {
|
|
@@ -3875,7 +3893,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
3875
3893
|
}
|
|
3876
3894
|
|
|
3877
3895
|
// src/admin/version.ts
|
|
3878
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
3896
|
+
var DAEMON_VERSION = true ? "0.1.4" : "0.0.0-dev";
|
|
3879
3897
|
|
|
3880
3898
|
// src/admin/AdminServer.ts
|
|
3881
3899
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -4130,7 +4148,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
|
4130
4148
|
function pageHtml(message) {
|
|
4131
4149
|
return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
|
|
4132
4150
|
}
|
|
4133
|
-
function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
4151
|
+
function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal) {
|
|
4134
4152
|
return new Promise((resolve, reject) => {
|
|
4135
4153
|
let settled = false;
|
|
4136
4154
|
const finish = (server2, fn) => {
|
|
@@ -4164,6 +4182,12 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
4164
4182
|
res.end(pageHtml("Login complete."));
|
|
4165
4183
|
finish(server, () => resolve(code));
|
|
4166
4184
|
});
|
|
4185
|
+
const abort = () => finish(server, () => reject(new Error("login: cancelled")));
|
|
4186
|
+
if (signal?.aborted) {
|
|
4187
|
+
abort();
|
|
4188
|
+
return;
|
|
4189
|
+
}
|
|
4190
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
4167
4191
|
server.on("error", (err5) => {
|
|
4168
4192
|
if (settled) return;
|
|
4169
4193
|
settled = true;
|
|
@@ -7321,7 +7345,7 @@ function buildDaemon(config, paths) {
|
|
|
7321
7345
|
// the app polls the token-free status. A test seam (`paths.codexAwaitLoopback`)
|
|
7322
7346
|
// can inject a mock so no real port is bound.
|
|
7323
7347
|
codexSessions: new CodexOAuthSessionStore(),
|
|
7324
|
-
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs) => awaitLoopbackCode(state, timeoutMs)),
|
|
7348
|
+
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
7325
7349
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
7326
7350
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
7327
7351
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
package/dist/index.d.cts
CHANGED
|
@@ -1048,7 +1048,7 @@ interface SubscriptionAccountAppender {
|
|
|
1048
1048
|
*/
|
|
1049
1049
|
|
|
1050
1050
|
/** The loopback-listener fn (injected so tests need not bind a real port). */
|
|
1051
|
-
type CodexLoopbackFn = (state: string, timeoutMs?: number) => Promise<string>;
|
|
1051
|
+
type CodexLoopbackFn = (state: string, timeoutMs?: number, signal?: AbortSignal) => Promise<string>;
|
|
1052
1052
|
/** One codex sign-in flow's polled status (NEVER carries a token). */
|
|
1053
1053
|
interface CodexFlowState {
|
|
1054
1054
|
status: 'pending' | 'done' | 'error';
|
|
@@ -1066,13 +1066,18 @@ declare class CodexOAuthSessionStore {
|
|
|
1066
1066
|
private readonly ttlMs;
|
|
1067
1067
|
private readonly sessions;
|
|
1068
1068
|
private activeSessionId;
|
|
1069
|
+
private readonly aborters;
|
|
1069
1070
|
constructor(ttlMs?: number);
|
|
1070
1071
|
/** Whether a codex sign-in is currently in flight (port 1455 held). */
|
|
1071
1072
|
isBusy(): boolean;
|
|
1072
1073
|
/** Mint a fresh sessionId, mark it pending + active, return the id. */
|
|
1073
|
-
begin():
|
|
1074
|
+
begin(): {
|
|
1075
|
+
sessionId: string;
|
|
1076
|
+
signal: AbortSignal;
|
|
1077
|
+
};
|
|
1074
1078
|
/** Settle a flow (done/error) + free the active slot. */
|
|
1075
1079
|
settle(sessionId: string, status: 'done' | 'error', error?: string): void;
|
|
1080
|
+
cancel(sessionId: string): boolean;
|
|
1076
1081
|
/** Read a flow's status (token-free), or null when unknown/expired. */
|
|
1077
1082
|
get(sessionId: string): CodexFlowState | null;
|
|
1078
1083
|
/** Drop expired flows; free the active slot if the active flow expired. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1048,7 +1048,7 @@ interface SubscriptionAccountAppender {
|
|
|
1048
1048
|
*/
|
|
1049
1049
|
|
|
1050
1050
|
/** The loopback-listener fn (injected so tests need not bind a real port). */
|
|
1051
|
-
type CodexLoopbackFn = (state: string, timeoutMs?: number) => Promise<string>;
|
|
1051
|
+
type CodexLoopbackFn = (state: string, timeoutMs?: number, signal?: AbortSignal) => Promise<string>;
|
|
1052
1052
|
/** One codex sign-in flow's polled status (NEVER carries a token). */
|
|
1053
1053
|
interface CodexFlowState {
|
|
1054
1054
|
status: 'pending' | 'done' | 'error';
|
|
@@ -1066,13 +1066,18 @@ declare class CodexOAuthSessionStore {
|
|
|
1066
1066
|
private readonly ttlMs;
|
|
1067
1067
|
private readonly sessions;
|
|
1068
1068
|
private activeSessionId;
|
|
1069
|
+
private readonly aborters;
|
|
1069
1070
|
constructor(ttlMs?: number);
|
|
1070
1071
|
/** Whether a codex sign-in is currently in flight (port 1455 held). */
|
|
1071
1072
|
isBusy(): boolean;
|
|
1072
1073
|
/** Mint a fresh sessionId, mark it pending + active, return the id. */
|
|
1073
|
-
begin():
|
|
1074
|
+
begin(): {
|
|
1075
|
+
sessionId: string;
|
|
1076
|
+
signal: AbortSignal;
|
|
1077
|
+
};
|
|
1074
1078
|
/** Settle a flow (done/error) + free the active slot. */
|
|
1075
1079
|
settle(sessionId: string, status: 'done' | 'error', error?: string): void;
|
|
1080
|
+
cancel(sessionId: string): boolean;
|
|
1076
1081
|
/** Read a flow's status (token-free), or null when unknown/expired. */
|
|
1077
1082
|
get(sessionId: string): CodexFlowState | null;
|
|
1078
1083
|
/** Drop expired flows; free the active slot if the active flow expired. */
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ var CodexOAuthSessionStore = class {
|
|
|
38
38
|
ttlMs;
|
|
39
39
|
sessions = /* @__PURE__ */ new Map();
|
|
40
40
|
activeSessionId = null;
|
|
41
|
+
aborters = /* @__PURE__ */ new Map();
|
|
41
42
|
/** Whether a codex sign-in is currently in flight (port 1455 held). */
|
|
42
43
|
isBusy() {
|
|
43
44
|
this.sweep();
|
|
@@ -49,13 +50,22 @@ var CodexOAuthSessionStore = class {
|
|
|
49
50
|
const sessionId = crypto.randomBytes(24).toString("base64url");
|
|
50
51
|
this.sessions.set(sessionId, { status: "pending", createdAt: Date.now() });
|
|
51
52
|
this.activeSessionId = sessionId;
|
|
52
|
-
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
this.aborters.set(sessionId, controller);
|
|
55
|
+
return { sessionId, signal: controller.signal };
|
|
53
56
|
}
|
|
54
57
|
/** Settle a flow (done/error) + free the active slot. */
|
|
55
58
|
settle(sessionId, status, error) {
|
|
56
59
|
const prior = this.sessions.get(sessionId);
|
|
57
60
|
this.sessions.set(sessionId, { status, error, createdAt: prior?.createdAt ?? Date.now() });
|
|
58
61
|
if (this.activeSessionId === sessionId) this.activeSessionId = null;
|
|
62
|
+
this.aborters.delete(sessionId);
|
|
63
|
+
}
|
|
64
|
+
cancel(sessionId) {
|
|
65
|
+
if (!this.sessions.has(sessionId)) return false;
|
|
66
|
+
this.aborters.get(sessionId)?.abort();
|
|
67
|
+
this.settle(sessionId, "error", "login: cancelled");
|
|
68
|
+
return true;
|
|
59
69
|
}
|
|
60
70
|
/** Read a flow's status (token-free), or null when unknown/expired. */
|
|
61
71
|
get(sessionId) {
|
|
@@ -84,13 +94,13 @@ function handleCodexOAuthStart(deps) {
|
|
|
84
94
|
);
|
|
85
95
|
}
|
|
86
96
|
const { authUrl, codeVerifier, state } = codexOAuth.generateAuthParams();
|
|
87
|
-
const sessionId = deps.codexSessions.begin();
|
|
88
|
-
void runCodexLoopback(sessionId, codeVerifier, state, deps);
|
|
97
|
+
const { sessionId, signal } = deps.codexSessions.begin();
|
|
98
|
+
void runCodexLoopback(sessionId, codeVerifier, state, signal, deps);
|
|
89
99
|
return { status: 200, body: { authUrl, sessionId } };
|
|
90
100
|
}
|
|
91
|
-
async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
|
|
101
|
+
async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
|
|
92
102
|
try {
|
|
93
|
-
const code = await deps.codexAwaitLoopback(state);
|
|
103
|
+
const code = await deps.codexAwaitLoopback(state, void 0, signal);
|
|
94
104
|
const result = await codexOAuth.exchangeCodeForTokens(
|
|
95
105
|
{ authorizationCode: code, codeVerifier, state },
|
|
96
106
|
deps.oauthExchangeFetch
|
|
@@ -112,6 +122,10 @@ async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
|
|
|
112
122
|
deps.codexSessions.settle(sessionId, "error", reason);
|
|
113
123
|
}
|
|
114
124
|
}
|
|
125
|
+
function handleCodexOAuthCancel(sessionId, deps) {
|
|
126
|
+
if (!deps.codexSessions.cancel(sessionId)) return err(404, "unknown or expired codex sign-in session");
|
|
127
|
+
return { status: 200, body: { ok: true } };
|
|
128
|
+
}
|
|
115
129
|
function handleCodexOAuthStatus(sessionId, deps) {
|
|
116
130
|
const s = deps.codexSessions.get(sessionId);
|
|
117
131
|
if (!s) return err(404, "unknown or expired codex sign-in session");
|
|
@@ -3520,6 +3534,10 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
3520
3534
|
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
3521
3535
|
return writeJson2(res, result.status, result.body);
|
|
3522
3536
|
}
|
|
3537
|
+
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
3538
|
+
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
3539
|
+
return writeJson2(res, result.status, result.body);
|
|
3540
|
+
}
|
|
3523
3541
|
if (method === "PUT" || method === "POST" || method === "DELETE") {
|
|
3524
3542
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
3525
3543
|
if (!providerId) {
|
|
@@ -3859,7 +3877,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
3859
3877
|
}
|
|
3860
3878
|
|
|
3861
3879
|
// src/admin/version.ts
|
|
3862
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
3880
|
+
var DAEMON_VERSION = true ? "0.1.4" : "0.0.0-dev";
|
|
3863
3881
|
|
|
3864
3882
|
// src/admin/AdminServer.ts
|
|
3865
3883
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -4114,7 +4132,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
|
4114
4132
|
function pageHtml(message) {
|
|
4115
4133
|
return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
|
|
4116
4134
|
}
|
|
4117
|
-
function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
4135
|
+
function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal) {
|
|
4118
4136
|
return new Promise((resolve, reject) => {
|
|
4119
4137
|
let settled = false;
|
|
4120
4138
|
const finish = (server2, fn) => {
|
|
@@ -4148,6 +4166,12 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
4148
4166
|
res.end(pageHtml("Login complete."));
|
|
4149
4167
|
finish(server, () => resolve(code));
|
|
4150
4168
|
});
|
|
4169
|
+
const abort = () => finish(server, () => reject(new Error("login: cancelled")));
|
|
4170
|
+
if (signal?.aborted) {
|
|
4171
|
+
abort();
|
|
4172
|
+
return;
|
|
4173
|
+
}
|
|
4174
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
4151
4175
|
server.on("error", (err5) => {
|
|
4152
4176
|
if (settled) return;
|
|
4153
4177
|
settled = true;
|
|
@@ -7312,7 +7336,7 @@ function buildDaemon(config, paths) {
|
|
|
7312
7336
|
// the app polls the token-free status. A test seam (`paths.codexAwaitLoopback`)
|
|
7313
7337
|
// can inject a mock so no real port is bound.
|
|
7314
7338
|
codexSessions: new CodexOAuthSessionStore(),
|
|
7315
|
-
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs) => awaitLoopbackCode(state, timeoutMs)),
|
|
7339
|
+
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
7316
7340
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
7317
7341
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
7318
7342
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnicross/daemon",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Omnicross standalone daemon
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Omnicross standalone daemon 鈥?the bare-Node embedder of @omnicross/core with an admin HTTP API + dashboard.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sayo (https://github.com/Dumoedss)",
|
|
7
7
|
"homepage": "https://github.com/Dumoedss/omnicross#readme",
|