aws-runtime-bridge 1.0.2 → 1.0.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/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +52 -34
- package/dist/routes/runtime-binding.d.ts.map +1 -1
- package/dist/routes/runtime-binding.js +14 -2
- package/dist/services/auto-register.d.ts +3 -1
- package/dist/services/auto-register.d.ts.map +1 -1
- package/dist/services/auto-register.js +18 -12
- package/dist/services/runtime-binding.d.ts +11 -1
- package/dist/services/runtime-binding.d.ts.map +1 -1
- package/dist/services/runtime-binding.js +130 -4
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ export declare const AUTO_REGISTER_PROJECT_NAME: string;
|
|
|
67
67
|
export declare const AUTO_REGISTER_ROLE_NAME: string;
|
|
68
68
|
/** 工作空间路径 */
|
|
69
69
|
export declare const AUTO_REGISTER_WORKSPACE_PATH: string;
|
|
70
|
-
/**
|
|
70
|
+
/** 注册 IP(优先),兼容 EasyTier 虚拟 IP */
|
|
71
71
|
export declare const AUTO_REGISTER_VIRTUAL_IP: string;
|
|
72
72
|
/** 注册重试次数 */
|
|
73
73
|
export declare const AUTO_REGISTER_MAX_RETRIES: number;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,WAAW;AACX,eAAO,MAAM,IAAI,EAAE,MAElB,CAAC;AAEF,gBAAgB;AAChB,eAAO,MAAM,gBAAgB,EAAE,MACwC,CAAC;AAExE,oBAAoB;AACpB,eAAO,MAAM,8BAA8B,gCAAgC,CAAC;AAE5E,cAAc;AACd,eAAO,MAAM,OAAO,EAAE,MAA8C,CAAC;AAErE,kBAAkB;AAClB,eAAO,MAAM,YAAY,EAAE,MAC+C,CAAC;AAE3E,2BAA2B;AAC3B,eAAO,MAAM,wBAAwB,EAAE,OACiB,CAAC;AAEzD,oCAAoC;AACpC,eAAO,MAAM,oBAAoB,EAAE,OACiB,CAAC;AAErD,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB,EAAE,MAAM,EAMC,CAAC;AAEzC,uBAAuB;AACvB,wBAAgB,uBAAuB,IAAI,IAAI,CAc9C;AAED,eAAe;AACf,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,2BAA2B;AAC3B,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED,0BAA0B;AAC1B,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED,sBAAsB;AACtB,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,4BAA4B;AAC5B,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED;;GAEG;AAEH,yBAAyB;AACzB,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,sBAAsB,EACrB,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1C,eAAe;AACf,eAAO,MAAM,mBAAmB,SACiB,CAAC;AAElD,iCAAiC;AACjC,eAAO,MAAM,2BAA2B,QAEvC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,uBAAuB,QAEnC,CAAC;AAEF;;GAEG;AAEH,eAAe;AACf,eAAO,MAAM,qBAAqB,SAA2C,CAAC;AAE9E,YAAY;AACZ,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,iBAAiB;AACjB,eAAO,MAAM,sBAAsB,QAAiC,CAAC;AAErE,oBAAoB;AACpB,eAAO,MAAM,2BAA2B,QAAsC,CAAC;AAE/E,WAAW;AACX,eAAO,MAAM,0BAA0B,QAAqC,CAAC;AAE7E,WAAW;AACX,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,aAAa;AACb,eAAO,MAAM,4BAA4B,QACH,CAAC;AAEvC,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,WAAW;AACX,eAAO,MAAM,IAAI,EAAE,MAElB,CAAC;AAEF,gBAAgB;AAChB,eAAO,MAAM,gBAAgB,EAAE,MACwC,CAAC;AAExE,oBAAoB;AACpB,eAAO,MAAM,8BAA8B,gCAAgC,CAAC;AAE5E,cAAc;AACd,eAAO,MAAM,OAAO,EAAE,MAA8C,CAAC;AAErE,kBAAkB;AAClB,eAAO,MAAM,YAAY,EAAE,MAC+C,CAAC;AAE3E,2BAA2B;AAC3B,eAAO,MAAM,wBAAwB,EAAE,OACiB,CAAC;AAEzD,oCAAoC;AACpC,eAAO,MAAM,oBAAoB,EAAE,OACiB,CAAC;AAErD,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB,EAAE,MAAM,EAMC,CAAC;AAEzC,uBAAuB;AACvB,wBAAgB,uBAAuB,IAAI,IAAI,CAc9C;AAED,eAAe;AACf,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,2BAA2B;AAC3B,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED,0BAA0B;AAC1B,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED,sBAAsB;AACtB,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,4BAA4B;AAC5B,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED;;GAEG;AAEH,yBAAyB;AACzB,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,sBAAsB,EACrB,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1C,eAAe;AACf,eAAO,MAAM,mBAAmB,SACiB,CAAC;AAElD,iCAAiC;AACjC,eAAO,MAAM,2BAA2B,QAEvC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,uBAAuB,QAEnC,CAAC;AAEF;;GAEG;AAEH,eAAe;AACf,eAAO,MAAM,qBAAqB,SAA2C,CAAC;AAE9E,YAAY;AACZ,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,iBAAiB;AACjB,eAAO,MAAM,sBAAsB,QAAiC,CAAC;AAErE,oBAAoB;AACpB,eAAO,MAAM,2BAA2B,QAAsC,CAAC;AAE/E,WAAW;AACX,eAAO,MAAM,0BAA0B,QAAqC,CAAC;AAE7E,WAAW;AACX,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,aAAa;AACb,eAAO,MAAM,4BAA4B,QACH,CAAC;AAEvC,kCAAkC;AAClC,eAAO,MAAM,wBAAwB,QAC4B,CAAC;AAElE,aAAa;AACb,eAAO,MAAM,yBAAyB,QAErC,CAAC;AAEF,iBAAiB;AACjB,eAAO,MAAM,4BAA4B,QAExC,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -104,8 +104,8 @@ export const AUTO_REGISTER_PROJECT_NAME = process.env.AWS_PROJECT_NAME || "";
|
|
|
104
104
|
export const AUTO_REGISTER_ROLE_NAME = process.env.AWS_ROLE_NAME || "";
|
|
105
105
|
/** 工作空间路径 */
|
|
106
106
|
export const AUTO_REGISTER_WORKSPACE_PATH = process.env.AWS_WORKSPACE_PATH || "";
|
|
107
|
-
/**
|
|
108
|
-
export const AUTO_REGISTER_VIRTUAL_IP = process.env.AWS_VIRTUAL_IP || "";
|
|
107
|
+
/** 注册 IP(优先),兼容 EasyTier 虚拟 IP */
|
|
108
|
+
export const AUTO_REGISTER_VIRTUAL_IP = process.env.AWS_REGISTER_IP || process.env.AWS_VIRTUAL_IP || "";
|
|
109
109
|
/** 注册重试次数 */
|
|
110
110
|
export const AUTO_REGISTER_MAX_RETRIES = Number(process.env.AWS_REGISTER_MAX_RETRIES || 5);
|
|
111
111
|
/** 注册重试间隔(毫秒) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAwBA,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC"}
|
package/dist/routes/instance.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Router } from
|
|
2
|
-
import axios from
|
|
3
|
-
import { validateToken } from
|
|
4
|
-
import { schedulerBaseUrl, runtimeToken } from
|
|
5
|
-
import { loadInstanceState, saveInstanceState } from
|
|
6
|
-
import { initInstance } from
|
|
7
|
-
import { detectToolStatuses, ensureToolsInstalled, SUPPORTED_INSTALLABLE_TOOLS } from
|
|
8
|
-
import { createLogger } from
|
|
9
|
-
const log = createLogger(
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { validateToken } from "../middleware/auth.js";
|
|
4
|
+
import { schedulerBaseUrl, runtimeToken } from "../config.js";
|
|
5
|
+
import { loadInstanceState, saveInstanceState, } from "../services/instance-state.js";
|
|
6
|
+
import { initInstance } from "../services/instance-init-service.js";
|
|
7
|
+
import { detectToolStatuses, ensureToolsInstalled, SUPPORTED_INSTALLABLE_TOOLS, } from "../services/tool-installer.js";
|
|
8
|
+
import { createLogger } from "../utils/logger.js";
|
|
9
|
+
const log = createLogger("instance");
|
|
10
10
|
// ★★★ 导出 gracefulShutdown 的触发函数
|
|
11
11
|
// 用于 aws-mcp-server 通知 bridge 保留会话状态关闭
|
|
12
12
|
let gracefulShutdownFn = null;
|
|
@@ -14,12 +14,18 @@ export function setGracefulShutdownFn(fn) {
|
|
|
14
14
|
gracefulShutdownFn = fn;
|
|
15
15
|
}
|
|
16
16
|
export const instanceRouter = Router();
|
|
17
|
-
instanceRouter.get(
|
|
17
|
+
instanceRouter.get("/healthz", (_req, res) => {
|
|
18
|
+
res.json({
|
|
19
|
+
ok: true,
|
|
20
|
+
runtimeBridge: "healthy",
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
instanceRouter.get("/ping", validateToken, async (_req, res) => {
|
|
18
24
|
try {
|
|
19
|
-
const schedulerResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: {
|
|
25
|
+
const schedulerResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": runtimeToken } });
|
|
20
26
|
res.json({
|
|
21
27
|
ok: true,
|
|
22
|
-
runtimeBridge:
|
|
28
|
+
runtimeBridge: "healthy",
|
|
23
29
|
schedulerReachable: schedulerResponse.status >= 200 && schedulerResponse.status < 300,
|
|
24
30
|
schedulerBaseUrl,
|
|
25
31
|
});
|
|
@@ -28,15 +34,15 @@ instanceRouter.get('/ping', validateToken, async (_req, res) => {
|
|
|
28
34
|
const err = error;
|
|
29
35
|
res.status(502).json({
|
|
30
36
|
ok: false,
|
|
31
|
-
error: err?.message ||
|
|
37
|
+
error: err?.message || "scheduler ping failed",
|
|
32
38
|
schedulerBaseUrl,
|
|
33
39
|
});
|
|
34
40
|
}
|
|
35
41
|
});
|
|
36
|
-
instanceRouter.post(
|
|
42
|
+
instanceRouter.post("/init-instance", validateToken, async (req, res) => {
|
|
37
43
|
const { agentId, workspacePath, skillEnabled, mcpEnabled, ccSwitchEnabledTools, skillPackage, skillPackages, mcpServers, } = req.body || {};
|
|
38
44
|
if (!agentId || !workspacePath) {
|
|
39
|
-
res.status(400).json({ error:
|
|
45
|
+
res.status(400).json({ error: "agentId and workspacePath are required" });
|
|
40
46
|
return;
|
|
41
47
|
}
|
|
42
48
|
try {
|
|
@@ -53,39 +59,51 @@ instanceRouter.post('/init-instance', validateToken, async (req, res) => {
|
|
|
53
59
|
catch (error) {
|
|
54
60
|
const err = error;
|
|
55
61
|
res.status(400).json({
|
|
56
|
-
error: err?.message ||
|
|
62
|
+
error: err?.message || "initialize instance failed",
|
|
57
63
|
});
|
|
58
64
|
}
|
|
59
65
|
});
|
|
60
|
-
instanceRouter.post(
|
|
66
|
+
instanceRouter.post("/cc-switch/state", validateToken, async (req, res) => {
|
|
61
67
|
const { agentId } = req.body || {};
|
|
62
68
|
if (!agentId) {
|
|
63
|
-
res.status(400).json({ error:
|
|
69
|
+
res.status(400).json({ error: "agentId is required" });
|
|
64
70
|
return;
|
|
65
71
|
}
|
|
66
72
|
try {
|
|
67
73
|
const state = await loadInstanceState(agentId);
|
|
68
74
|
const toolStatus = await detectToolStatuses(state.enabledTools || []);
|
|
69
|
-
res.json({
|
|
75
|
+
res.json({
|
|
76
|
+
ok: true,
|
|
77
|
+
agentId: String(agentId),
|
|
78
|
+
state: { ...state, toolStatus },
|
|
79
|
+
});
|
|
70
80
|
}
|
|
71
81
|
catch (error) {
|
|
72
82
|
const err = error;
|
|
73
|
-
res.status(400).json({ error: err?.message ||
|
|
83
|
+
res.status(400).json({ error: err?.message || "load state failed" });
|
|
74
84
|
}
|
|
75
85
|
});
|
|
76
|
-
instanceRouter.post(
|
|
86
|
+
instanceRouter.post("/cc-switch/install-tools", validateToken, async (req, res) => {
|
|
77
87
|
const { agentId, tools } = req.body || {};
|
|
78
88
|
if (!agentId) {
|
|
79
|
-
res.status(400).json({ error:
|
|
89
|
+
res.status(400).json({ error: "agentId is required" });
|
|
80
90
|
return;
|
|
81
91
|
}
|
|
82
92
|
const requestedTools = Array.isArray(tools)
|
|
83
|
-
? tools
|
|
93
|
+
? tools
|
|
94
|
+
.map((tool) => String(tool || "")
|
|
95
|
+
.trim()
|
|
96
|
+
.toLowerCase())
|
|
97
|
+
.filter(Boolean)
|
|
84
98
|
: [];
|
|
85
99
|
const supportedInstallableTools = new Set(SUPPORTED_INSTALLABLE_TOOLS);
|
|
86
100
|
const installableTools = requestedTools.filter((tool) => supportedInstallableTools.has(tool));
|
|
87
101
|
if (installableTools.length === 0) {
|
|
88
|
-
res
|
|
102
|
+
res
|
|
103
|
+
.status(400)
|
|
104
|
+
.json({
|
|
105
|
+
error: `tools must include ${SUPPORTED_INSTALLABLE_TOOLS.join(", ")}`,
|
|
106
|
+
});
|
|
89
107
|
return;
|
|
90
108
|
}
|
|
91
109
|
try {
|
|
@@ -111,7 +129,7 @@ instanceRouter.post('/cc-switch/install-tools', validateToken, async (req, res)
|
|
|
111
129
|
}
|
|
112
130
|
catch (error) {
|
|
113
131
|
const err = error;
|
|
114
|
-
res.status(400).json({ error: err?.message ||
|
|
132
|
+
res.status(400).json({ error: err?.message || "install tools failed" });
|
|
115
133
|
}
|
|
116
134
|
});
|
|
117
135
|
/**
|
|
@@ -126,22 +144,22 @@ instanceRouter.post('/cc-switch/install-tools', validateToken, async (req, res)
|
|
|
126
144
|
* - preserveSessions: true = 保留会话(aws-mcp-server 重启)
|
|
127
145
|
* false = 清理会话(bridge 自己关闭)
|
|
128
146
|
*/
|
|
129
|
-
instanceRouter.post(
|
|
147
|
+
instanceRouter.post("/prepare-restart", validateToken, async (req, res) => {
|
|
130
148
|
const { preserveSessions = true } = req.body || {};
|
|
131
149
|
try {
|
|
132
150
|
log.info(`[prepare-restart] 收到准备重启通知,preserveSessions=${preserveSessions}`);
|
|
133
151
|
// 记录重启模式到文件,供下次启动时判断
|
|
134
|
-
const fs = await import(
|
|
135
|
-
const path = await import(
|
|
136
|
-
const os = await import(
|
|
137
|
-
const restartFlagFile = path.join(os.tmpdir(),
|
|
152
|
+
const fs = await import("fs/promises");
|
|
153
|
+
const path = await import("path");
|
|
154
|
+
const os = await import("os");
|
|
155
|
+
const restartFlagFile = path.join(os.tmpdir(), "agentswork-runtime-bridge", ".preserve-sessions");
|
|
138
156
|
if (preserveSessions) {
|
|
139
157
|
// 创建标记文件,表示 aws-mcp-server 重启,需要保留会话
|
|
140
158
|
await fs.mkdir(path.dirname(restartFlagFile), { recursive: true });
|
|
141
159
|
await fs.writeFile(restartFlagFile, JSON.stringify({
|
|
142
160
|
preserveSessions: true,
|
|
143
161
|
timestamp: new Date().toISOString(),
|
|
144
|
-
}),
|
|
162
|
+
}), "utf-8");
|
|
145
163
|
log.info(`[prepare-restart] 已创建保留会话标记文件: ${restartFlagFile}`);
|
|
146
164
|
}
|
|
147
165
|
else {
|
|
@@ -158,13 +176,13 @@ instanceRouter.post('/prepare-restart', validateToken, async (req, res) => {
|
|
|
158
176
|
ok: true,
|
|
159
177
|
preserveSessions,
|
|
160
178
|
message: preserveSessions
|
|
161
|
-
?
|
|
162
|
-
:
|
|
179
|
+
? "Bridge will preserve sessions for aws-mcp-server restart"
|
|
180
|
+
: "Bridge will clean up sessions on shutdown",
|
|
163
181
|
});
|
|
164
182
|
}
|
|
165
183
|
catch (error) {
|
|
166
184
|
const err = error;
|
|
167
|
-
log.error(
|
|
185
|
+
log.error("[prepare-restart] Error:", err);
|
|
168
186
|
res.status(500).json({ error: err.message });
|
|
169
187
|
}
|
|
170
188
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/routes/runtime-binding.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,oBAAoB,4CAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/routes/runtime-binding.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,oBAAoB,4CAAW,CAAC;AAkL7C,wBAAgB,6BAA6B,IAAI,IAAI,CAapD"}
|
|
@@ -24,7 +24,7 @@ runtimeBindingRouter.get("/binding/status", (_req, res) => {
|
|
|
24
24
|
});
|
|
25
25
|
});
|
|
26
26
|
runtimeBindingRouter.post("/binding/pair", (req, res) => {
|
|
27
|
-
const { accessToken, instanceId, schedulerBaseUrl, pairingCode } = req.body || {};
|
|
27
|
+
const { accessToken, instanceId, userId, schedulerBaseUrl, pairingCode } = req.body || {};
|
|
28
28
|
const existingBinding = hasRuntimeBinding();
|
|
29
29
|
const currentRuntimeToken = extractRuntimeToken(req);
|
|
30
30
|
if (existingBinding) {
|
|
@@ -48,6 +48,7 @@ runtimeBindingRouter.post("/binding/pair", (req, res) => {
|
|
|
48
48
|
saveRuntimeBinding({
|
|
49
49
|
accessToken: String(accessToken || ""),
|
|
50
50
|
instanceId: instanceId ? String(instanceId) : undefined,
|
|
51
|
+
userId: userId ? String(userId) : undefined,
|
|
51
52
|
schedulerBaseUrl: schedulerBaseUrl ? String(schedulerBaseUrl) : undefined,
|
|
52
53
|
});
|
|
53
54
|
log.info(`[runtime-bridge] paired with instanceId=${instanceId || "unknown"}`);
|
|
@@ -72,6 +73,16 @@ runtimeBindingRouter.post("/binding/request-token", async (req, res) => {
|
|
|
72
73
|
res.status(403).json({ ok: false, error: "loopback request required" });
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
76
|
+
const { userId, serverUrl, schedulerBaseUrl } = req.body || {};
|
|
77
|
+
const scopedToken = getRuntimeAccessToken(userId, serverUrl || schedulerBaseUrl);
|
|
78
|
+
if (scopedToken) {
|
|
79
|
+
res.json({
|
|
80
|
+
ok: true,
|
|
81
|
+
runtimeAccessToken: scopedToken,
|
|
82
|
+
updated: false,
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
75
86
|
const result = await requestRuntimeAccessTokenRefresh();
|
|
76
87
|
if (!result.success || !result.runtimeAccessToken) {
|
|
77
88
|
res.status(400).json({
|
|
@@ -94,7 +105,7 @@ runtimeBindingRouter.post("/binding/issue", (req, res) => {
|
|
|
94
105
|
res.status(401).json({ error: "unauthorized" });
|
|
95
106
|
return;
|
|
96
107
|
}
|
|
97
|
-
const { accessToken, instanceId, schedulerBaseUrl } = req.body || {};
|
|
108
|
+
const { accessToken, instanceId, userId, schedulerBaseUrl } = req.body || {};
|
|
98
109
|
const nextToken = String(accessToken || "").trim();
|
|
99
110
|
if (!nextToken) {
|
|
100
111
|
res.status(400).json({ error: "accessToken is required" });
|
|
@@ -107,6 +118,7 @@ runtimeBindingRouter.post("/binding/issue", (req, res) => {
|
|
|
107
118
|
saveRuntimeBinding({
|
|
108
119
|
accessToken: nextToken,
|
|
109
120
|
instanceId: instanceId ? String(instanceId) : undefined,
|
|
121
|
+
userId: userId ? String(userId) : undefined,
|
|
110
122
|
schedulerBaseUrl: schedulerBaseUrl
|
|
111
123
|
? String(schedulerBaseUrl)
|
|
112
124
|
: undefined,
|
|
@@ -25,7 +25,9 @@ interface AutoRegisterConfig {
|
|
|
25
25
|
roleName?: string;
|
|
26
26
|
/** 工作空间路径 */
|
|
27
27
|
workspacePath?: string;
|
|
28
|
-
/**
|
|
28
|
+
/** 注册 IP(优先用于生成 runtimeBridgeBaseUrl) */
|
|
29
|
+
registerIp?: string;
|
|
30
|
+
/** 虚拟 IP(EasyTier,兼容旧配置) */
|
|
29
31
|
virtualIp?: string;
|
|
30
32
|
/** 注册重试次数 */
|
|
31
33
|
maxRetries?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-register.d.ts","sourceRoot":"","sources":["../../src/services/auto-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"auto-register.d.ts","sourceRoot":"","sources":["../../src/services/auto-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6CH;;GAEG;AACH,UAAU,kBAAkB;IAC1B,eAAe;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAsKD;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,kBAAkB,CAwC/C;AAuLD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,YAAY,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GACzC,OAAO,CAAC,OAAO,CAAC,CAoHlB;AAED;;GAEG;AACH,wBAAsB,gCAAgC,IAAI,OAAO,CAAC;IAChE,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAoED;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAuBnD;AAED;;GAEG;AACH,wBAAgB,oBAAoB;gBA/mBtB,OAAO;iBACN,MAAM;kBACL,IAAI;YACV,MAAM;EA8mBf;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CA0ED"}
|
|
@@ -71,7 +71,8 @@ function ensureConfigFile() {
|
|
|
71
71
|
* "projectName": "default", // 可选
|
|
72
72
|
* "roleName": "Bridge", // 可选
|
|
73
73
|
* "workspacePath": "/path/to/workspace", // 可选
|
|
74
|
-
* "
|
|
74
|
+
* "registerIp": "192.168.1.25", // 可选,优先作为注册 IP
|
|
75
|
+
* "virtualIp": "10.144.144.1", // 可选,兼容 EasyTier 虚拟 IP
|
|
75
76
|
* "maxRetries": 5, // 可选
|
|
76
77
|
* "retryInterval": 5000 // 可选
|
|
77
78
|
* }
|
|
@@ -95,6 +96,7 @@ function loadConfigFromFile() {
|
|
|
95
96
|
projectName: config.projectName,
|
|
96
97
|
roleName: config.roleName,
|
|
97
98
|
workspacePath: config.workspacePath,
|
|
99
|
+
registerIp: config.registerIp,
|
|
98
100
|
virtualIp: config.virtualIp,
|
|
99
101
|
maxRetries: config.maxRetries,
|
|
100
102
|
retryInterval: config.retryInterval,
|
|
@@ -132,6 +134,9 @@ function loadConfigFromEnv() {
|
|
|
132
134
|
if (process.env.AWS_WORKSPACE_PATH) {
|
|
133
135
|
envConfig.workspacePath = process.env.AWS_WORKSPACE_PATH;
|
|
134
136
|
}
|
|
137
|
+
if (process.env.AWS_REGISTER_IP) {
|
|
138
|
+
envConfig.registerIp = process.env.AWS_REGISTER_IP;
|
|
139
|
+
}
|
|
135
140
|
if (process.env.AWS_VIRTUAL_IP) {
|
|
136
141
|
envConfig.virtualIp = process.env.AWS_VIRTUAL_IP;
|
|
137
142
|
}
|
|
@@ -181,6 +186,7 @@ export function loadConfig() {
|
|
|
181
186
|
projectName: merged.projectName,
|
|
182
187
|
roleName: merged.roleName,
|
|
183
188
|
workspacePath: merged.workspacePath,
|
|
189
|
+
registerIp: merged.registerIp,
|
|
184
190
|
virtualIp: merged.virtualIp,
|
|
185
191
|
maxRetries: merged.maxRetries,
|
|
186
192
|
retryInterval: merged.retryInterval,
|
|
@@ -298,8 +304,8 @@ async function doRegister(config) {
|
|
|
298
304
|
userKey: config.userKey,
|
|
299
305
|
instanceName,
|
|
300
306
|
hostname: os.hostname(),
|
|
301
|
-
virtualIp: config.virtualIp,
|
|
302
|
-
runtimeBridgeBaseUrl: `http://${config.virtualIp || localIp}:${port}`,
|
|
307
|
+
virtualIp: config.registerIp || config.virtualIp,
|
|
308
|
+
runtimeBridgeBaseUrl: `http://${config.registerIp || config.virtualIp || localIp}:${port}`,
|
|
303
309
|
workspacePath: config.workspacePath,
|
|
304
310
|
projectName: config.projectName,
|
|
305
311
|
roleName: config.roleName,
|
|
@@ -379,6 +385,7 @@ export async function autoRegister(customConfig) {
|
|
|
379
385
|
saveRuntimeBinding({
|
|
380
386
|
accessToken: response.runtimeAccessToken,
|
|
381
387
|
instanceId: response.instanceId,
|
|
388
|
+
userId: response.userId,
|
|
382
389
|
schedulerBaseUrl: response.serverUrl || schedulerBaseUrl,
|
|
383
390
|
});
|
|
384
391
|
logger.info("[AutoRegister] 已保存调度中心下发的 runtime access token");
|
|
@@ -441,7 +448,7 @@ export async function requestRuntimeAccessTokenRefresh() {
|
|
|
441
448
|
const state = getRuntimeBindingPublicState();
|
|
442
449
|
const localIp = getLocalIpAddress();
|
|
443
450
|
const runtimeBridgePort = process.env.AWS_RUNTIME_BRIDGE_PORT || 18081;
|
|
444
|
-
const runtimeBridgeBaseUrl = `http://${config.virtualIp || localIp}:${runtimeBridgePort}`;
|
|
451
|
+
const runtimeBridgeBaseUrl = `http://${config.registerIp || config.virtualIp || localIp}:${runtimeBridgePort}`;
|
|
445
452
|
try {
|
|
446
453
|
const response = await axios.post(`${schedulerBaseUrl}/api/instances/runtime-tokens/refresh`, {
|
|
447
454
|
tenantId: config.tenantId,
|
|
@@ -465,13 +472,12 @@ export async function requestRuntimeAccessTokenRefresh() {
|
|
|
465
472
|
}
|
|
466
473
|
const previousToken = getRuntimeAccessToken();
|
|
467
474
|
const updated = previousToken !== response.data.runtimeAccessToken;
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
+
saveRuntimeBinding({
|
|
476
|
+
accessToken: response.data.runtimeAccessToken,
|
|
477
|
+
instanceId: state.instanceId,
|
|
478
|
+
userId: response.data.userId || config.userKey,
|
|
479
|
+
schedulerBaseUrl,
|
|
480
|
+
});
|
|
475
481
|
return {
|
|
476
482
|
success: true,
|
|
477
483
|
runtimeAccessToken: response.data.runtimeAccessToken,
|
|
@@ -552,7 +558,7 @@ export async function bridgeRestartCleanup() {
|
|
|
552
558
|
if (config.enabled) {
|
|
553
559
|
const localIp = getLocalIpAddress();
|
|
554
560
|
const port = process.env.AWS_RUNTIME_BRIDGE_PORT || 18081;
|
|
555
|
-
requestBody.runtimeBridgeBaseUrl = `http://${config.virtualIp || localIp}:${port}`;
|
|
561
|
+
requestBody.runtimeBridgeBaseUrl = `http://${config.registerIp || config.virtualIp || localIp}:${port}`;
|
|
556
562
|
}
|
|
557
563
|
if (!requestBody.instanceId && !requestBody.runtimeBridgeBaseUrl) {
|
|
558
564
|
logger.info("[AutoRegister] Bridge 重启清理:没有 instanceId 或 runtimeBridgeBaseUrl,跳过清理");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type RuntimeBindingState = {
|
|
2
2
|
status: "unpaired" | "paired";
|
|
3
3
|
instanceId?: string;
|
|
4
|
+
userId?: string;
|
|
4
5
|
schedulerBaseUrl?: string;
|
|
5
6
|
/**
|
|
6
7
|
* Plain runtime access token issued by the scheduler.
|
|
@@ -15,17 +16,26 @@ export type RuntimeBindingState = {
|
|
|
15
16
|
};
|
|
16
17
|
export declare function getRuntimePairingCode(): string;
|
|
17
18
|
export declare function getRuntimeBindingFilePath(): string;
|
|
19
|
+
export declare function getRuntimeTokenStoreFilePath(): string;
|
|
20
|
+
export declare function buildRuntimeTokenKey(userId: unknown, serverBaseUrl: unknown): string;
|
|
21
|
+
export declare function saveScopedRuntimeAccessToken(input: {
|
|
22
|
+
userId: string;
|
|
23
|
+
serverBaseUrl: string;
|
|
24
|
+
accessToken: string;
|
|
25
|
+
}): string;
|
|
26
|
+
export declare function getScopedRuntimeAccessToken(userId: unknown, serverBaseUrl: unknown): string | undefined;
|
|
18
27
|
export declare function loadRuntimeBinding(): RuntimeBindingState;
|
|
19
28
|
export declare function getRuntimeBindingPublicState(): Omit<RuntimeBindingState, "tokenHash" | "accessToken"> & {
|
|
20
29
|
paired: boolean;
|
|
21
30
|
};
|
|
22
31
|
export declare function hasRuntimeBinding(): boolean;
|
|
23
32
|
export declare function validateRuntimeBindingToken(token: unknown): boolean;
|
|
24
|
-
export declare function getRuntimeAccessToken(): string | undefined;
|
|
33
|
+
export declare function getRuntimeAccessToken(userId?: unknown, serverBaseUrl?: unknown): string | undefined;
|
|
25
34
|
export declare function validateRuntimePairingCode(code: unknown): boolean;
|
|
26
35
|
export declare function saveRuntimeBinding(input: {
|
|
27
36
|
accessToken: string;
|
|
28
37
|
instanceId?: string;
|
|
38
|
+
userId?: string;
|
|
29
39
|
schedulerBaseUrl?: string;
|
|
30
40
|
}): RuntimeBindingState;
|
|
31
41
|
export declare function clearRuntimeBinding(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/services/runtime-binding.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/services/runtime-binding.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAoDF,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAoBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,CAYR;AAsCD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAWT;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,GAAG,SAAS,CAUpB;AAcD,wBAAgB,kBAAkB,IAAI,mBAAmB,CAWxD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAClD,mBAAmB,EACnB,WAAW,GAAG,aAAa,CAC5B,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAWtB;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAG3C;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAcnE;AAED,wBAAgB,qBAAqB,CACnC,MAAM,CAAC,EAAE,OAAO,EAChB,aAAa,CAAC,EAAE,OAAO,GACtB,MAAM,GAAG,SAAS,CAWpB;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAEjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,mBAAmB,CAuCtB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAkB1C"}
|
|
@@ -18,6 +18,9 @@ function bindingDir() {
|
|
|
18
18
|
function bindingFile() {
|
|
19
19
|
return path.join(bindingDir(), "binding.json");
|
|
20
20
|
}
|
|
21
|
+
function tokenStoreFile() {
|
|
22
|
+
return path.join(bindingDir(), "runtime-tokens.properties");
|
|
23
|
+
}
|
|
21
24
|
function hashToken(token) {
|
|
22
25
|
return crypto
|
|
23
26
|
.createHash(TOKEN_HASH_ALGORITHM)
|
|
@@ -32,6 +35,14 @@ function safeEqual(a, b) {
|
|
|
32
35
|
}
|
|
33
36
|
return crypto.timingSafeEqual(left, right);
|
|
34
37
|
}
|
|
38
|
+
function safeTextEqual(a, b) {
|
|
39
|
+
const left = Buffer.from(a, "utf8");
|
|
40
|
+
const right = Buffer.from(b, "utf8");
|
|
41
|
+
if (left.length !== right.length) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return crypto.timingSafeEqual(left, right);
|
|
45
|
+
}
|
|
35
46
|
function normalizeToken(token) {
|
|
36
47
|
return String(token || "").trim();
|
|
37
48
|
}
|
|
@@ -41,6 +52,106 @@ export function getRuntimePairingCode() {
|
|
|
41
52
|
export function getRuntimeBindingFilePath() {
|
|
42
53
|
return bindingFile();
|
|
43
54
|
}
|
|
55
|
+
export function getRuntimeTokenStoreFilePath() {
|
|
56
|
+
return tokenStoreFile();
|
|
57
|
+
}
|
|
58
|
+
function normalizeServerIp(serverBaseUrl) {
|
|
59
|
+
const raw = String(serverBaseUrl || "").trim();
|
|
60
|
+
if (!raw) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
return new URL(raw).hostname.toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return raw
|
|
68
|
+
.replace(/^wss?:\/\//i, "")
|
|
69
|
+
.replace(/^https?:\/\//i, "")
|
|
70
|
+
.split("/")[0]
|
|
71
|
+
.split(":")[0]
|
|
72
|
+
.toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function buildRuntimeTokenKey(userId, serverBaseUrl) {
|
|
76
|
+
const normalizedUserId = String(userId || "").trim();
|
|
77
|
+
const serverIp = normalizeServerIp(serverBaseUrl);
|
|
78
|
+
if (!normalizedUserId || !serverIp) {
|
|
79
|
+
throw new Error("userId and serverBaseUrl are required to build runtime token key");
|
|
80
|
+
}
|
|
81
|
+
return crypto
|
|
82
|
+
.createHash("md5")
|
|
83
|
+
.update(`${normalizedUserId}:${serverIp}`, "utf8")
|
|
84
|
+
.digest("hex");
|
|
85
|
+
}
|
|
86
|
+
function loadRuntimeTokenStore() {
|
|
87
|
+
const result = new Map();
|
|
88
|
+
try {
|
|
89
|
+
const content = fs.readFileSync(tokenStoreFile(), "utf8");
|
|
90
|
+
for (const line of content.split(/\r?\n/)) {
|
|
91
|
+
const trimmed = line.trim();
|
|
92
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const separatorIndex = trimmed.indexOf("=");
|
|
96
|
+
if (separatorIndex <= 0) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
100
|
+
const token = trimmed.slice(separatorIndex + 1).trim();
|
|
101
|
+
if (key && token) {
|
|
102
|
+
result.set(key, token);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Missing token store is valid for an unpaired bridge.
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
function saveRuntimeTokenStore(tokens) {
|
|
112
|
+
fs.mkdirSync(bindingDir(), { recursive: true });
|
|
113
|
+
const lines = [...tokens.entries()]
|
|
114
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
115
|
+
.map(([key, token]) => `${key}=${token}`);
|
|
116
|
+
fs.writeFileSync(tokenStoreFile(), `${lines.join("\n")}\n`, {
|
|
117
|
+
encoding: "utf8",
|
|
118
|
+
mode: 0o600,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
export function saveScopedRuntimeAccessToken(input) {
|
|
122
|
+
const accessToken = normalizeToken(input.accessToken);
|
|
123
|
+
if (accessToken.length < 16) {
|
|
124
|
+
throw new Error("accessToken must be at least 16 characters");
|
|
125
|
+
}
|
|
126
|
+
const key = buildRuntimeTokenKey(input.userId, input.serverBaseUrl);
|
|
127
|
+
const tokens = loadRuntimeTokenStore();
|
|
128
|
+
tokens.set(key, accessToken);
|
|
129
|
+
saveRuntimeTokenStore(tokens);
|
|
130
|
+
return key;
|
|
131
|
+
}
|
|
132
|
+
export function getScopedRuntimeAccessToken(userId, serverBaseUrl) {
|
|
133
|
+
if (!userId || !serverBaseUrl) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const key = buildRuntimeTokenKey(userId, serverBaseUrl);
|
|
138
|
+
return loadRuntimeTokenStore().get(key);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function validateRuntimeTokenStore(token) {
|
|
145
|
+
if (!token) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
for (const storedToken of loadRuntimeTokenStore().values()) {
|
|
149
|
+
if (safeTextEqual(token, storedToken)) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
44
155
|
export function loadRuntimeBinding() {
|
|
45
156
|
try {
|
|
46
157
|
const raw = fs.readFileSync(bindingFile(), "utf8");
|
|
@@ -72,12 +183,18 @@ export function validateRuntimeBindingToken(token) {
|
|
|
72
183
|
return false;
|
|
73
184
|
}
|
|
74
185
|
const state = loadRuntimeBinding();
|
|
75
|
-
if (state.status
|
|
76
|
-
|
|
186
|
+
if (state.status === "paired" && state.tokenHash) {
|
|
187
|
+
if (safeEqual(hashToken(candidate), state.tokenHash)) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
77
190
|
}
|
|
78
|
-
return
|
|
191
|
+
return validateRuntimeTokenStore(candidate);
|
|
79
192
|
}
|
|
80
|
-
export function getRuntimeAccessToken() {
|
|
193
|
+
export function getRuntimeAccessToken(userId, serverBaseUrl) {
|
|
194
|
+
const scopedToken = getScopedRuntimeAccessToken(userId, serverBaseUrl);
|
|
195
|
+
if (scopedToken) {
|
|
196
|
+
return scopedToken;
|
|
197
|
+
}
|
|
81
198
|
const state = loadRuntimeBinding();
|
|
82
199
|
if (state.status !== "paired") {
|
|
83
200
|
return undefined;
|
|
@@ -99,6 +216,7 @@ export function saveRuntimeBinding(input) {
|
|
|
99
216
|
instanceId: input.instanceId
|
|
100
217
|
? String(input.instanceId)
|
|
101
218
|
: previous.instanceId,
|
|
219
|
+
userId: input.userId ? String(input.userId) : previous.userId,
|
|
102
220
|
schedulerBaseUrl: input.schedulerBaseUrl
|
|
103
221
|
? String(input.schedulerBaseUrl)
|
|
104
222
|
: previous.schedulerBaseUrl,
|
|
@@ -112,6 +230,14 @@ export function saveRuntimeBinding(input) {
|
|
|
112
230
|
encoding: "utf8",
|
|
113
231
|
mode: 0o600,
|
|
114
232
|
});
|
|
233
|
+
if (next.userId && next.schedulerBaseUrl) {
|
|
234
|
+
const key = saveScopedRuntimeAccessToken({
|
|
235
|
+
userId: next.userId,
|
|
236
|
+
serverBaseUrl: next.schedulerBaseUrl,
|
|
237
|
+
accessToken,
|
|
238
|
+
});
|
|
239
|
+
log.info(`[runtime-bridge] scoped runtime token saved: key=${key}`);
|
|
240
|
+
}
|
|
115
241
|
log.info(`[runtime-bridge] runtime binding saved: ${bindingFile()}`);
|
|
116
242
|
return next;
|
|
117
243
|
}
|