@lightharu/krouter 1.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.
- package/LICENSE +679 -0
- package/README.md +238 -0
- package/dist-web/assets/index-CM4-0adf.css +1 -0
- package/dist-web/assets/index-DCslvfUR.js +139 -0
- package/dist-web/favicon.svg +9 -0
- package/dist-web/icon.svg +9 -0
- package/dist-web/index.html +19 -0
- package/out-server/main/kiroAuthSync.js +249 -0
- package/out-server/main/kproxy/certManager.js +262 -0
- package/out-server/main/kproxy/index.js +254 -0
- package/out-server/main/kproxy/mitmProxy.js +475 -0
- package/out-server/main/kproxy/types.js +23 -0
- package/out-server/main/proxy/accountPool.js +543 -0
- package/out-server/main/proxy/clientConfig.js +596 -0
- package/out-server/main/proxy/index.js +25 -0
- package/out-server/main/proxy/kiroApi.js +1996 -0
- package/out-server/main/proxy/logger.js +407 -0
- package/out-server/main/proxy/modelCatalog.js +75 -0
- package/out-server/main/proxy/promptCacheTracker.js +301 -0
- package/out-server/main/proxy/proxyServer.js +3543 -0
- package/out-server/main/proxy/selfSignedCert.js +179 -0
- package/out-server/main/proxy/systemProxy.js +250 -0
- package/out-server/main/proxy/tokenCounter.js +164 -0
- package/out-server/main/proxy/toolNameRegistry.js +57 -0
- package/out-server/main/proxy/translator.js +1084 -0
- package/out-server/main/proxy/types.js +3 -0
- package/out-server/main/registration/browser-identity.js +184 -0
- package/out-server/main/registration/chainProxy.js +349 -0
- package/out-server/main/registration/config.js +58 -0
- package/out-server/main/registration/email-service.js +801 -0
- package/out-server/main/registration/fingerprint.js +352 -0
- package/out-server/main/registration/http-utils.js +148 -0
- package/out-server/main/registration/jwe.js +74 -0
- package/out-server/main/registration/names.js +142 -0
- package/out-server/main/registration/proton-mail-window.js +339 -0
- package/out-server/main/registration/registrar.js +1715 -0
- package/out-server/main/registration/tlsClientPool.js +70 -0
- package/out-server/main/registration/xxtea.js +161 -0
- package/out-server/main/runtimePaths.js +19 -0
- package/out-server/main/utils/redact.js +95 -0
- package/out-server/server/index.js +1272 -0
- package/out-server/server/services/accountExtras.js +105 -0
- package/out-server/server/services/accountProfileHydration.js +95 -0
- package/out-server/server/services/authFlows.js +509 -0
- package/out-server/server/services/dashboardTunnel.js +315 -0
- package/out-server/server/services/diagnostics.js +326 -0
- package/out-server/server/services/kiroAccounts.js +431 -0
- package/out-server/server/services/kiroSettings.js +260 -0
- package/out-server/server/services/kproxyRuntime.js +264 -0
- package/out-server/server/services/localKiroCredentials.js +320 -0
- package/out-server/server/services/machineIdRuntime.js +327 -0
- package/out-server/server/services/protonBrowserRuntime.js +724 -0
- package/out-server/server/services/proxyRuntime.js +523 -0
- package/out-server/server/services/registrationRuntime.js +106 -0
- package/out-server/server/store.js +266 -0
- package/package.json +113 -0
- package/resources/tls-client-xgo-1.14.0-windows-amd64.dll +0 -0
- package/scripts/kiro-manager-cli.cjs +3 -0
- package/scripts/krouter-cli.cjs +509 -0
- package/src/renderer/src/assets/krouter-logo.svg +11 -0
- package/src/renderer/src/assets/krouter-mark.svg +9 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getDashboardTunnelRuntime = getDashboardTunnelRuntime;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const URL_RE = /https:\/\/[a-z0-9-]+\.trycloudflare\.com\b/i;
|
|
11
|
+
const MAX_LOGS = 80;
|
|
12
|
+
const PID_FILE = 'dashboard-tunnel-cloudflared.pid';
|
|
13
|
+
function dataDir() {
|
|
14
|
+
return (process.env.KROUTER_DATA_DIR ||
|
|
15
|
+
process.env.KAM_DATA_DIR ||
|
|
16
|
+
process.env.KIRO_RUNTIME_DATA_DIR ||
|
|
17
|
+
process.env.KIRO_WEB_DATA_DIR ||
|
|
18
|
+
path_1.default.join(process.cwd(), '.web-data'));
|
|
19
|
+
}
|
|
20
|
+
function tunnelDir() {
|
|
21
|
+
return path_1.default.join(dataDir(), 'tunnel');
|
|
22
|
+
}
|
|
23
|
+
function pidPath() {
|
|
24
|
+
return path_1.default.join(tunnelDir(), PID_FILE);
|
|
25
|
+
}
|
|
26
|
+
function savePid(pid) {
|
|
27
|
+
if (!pid)
|
|
28
|
+
return;
|
|
29
|
+
(0, fs_1.mkdirSync)(tunnelDir(), { recursive: true });
|
|
30
|
+
(0, fs_1.writeFileSync)(pidPath(), String(pid), 'utf8');
|
|
31
|
+
}
|
|
32
|
+
function loadPid() {
|
|
33
|
+
try {
|
|
34
|
+
if (!(0, fs_1.existsSync)(pidPath()))
|
|
35
|
+
return undefined;
|
|
36
|
+
const pid = Number((0, fs_1.readFileSync)(pidPath(), 'utf8').trim());
|
|
37
|
+
return Number.isFinite(pid) && pid > 0 ? pid : undefined;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function clearPid() {
|
|
44
|
+
try {
|
|
45
|
+
if ((0, fs_1.existsSync)(pidPath()))
|
|
46
|
+
(0, fs_1.unlinkSync)(pidPath());
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// best effort
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function isPidAlive(pid) {
|
|
53
|
+
try {
|
|
54
|
+
process.kill(pid, 0);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function commandLineForPid(pid) {
|
|
62
|
+
if (process.platform === 'linux') {
|
|
63
|
+
try {
|
|
64
|
+
return (0, fs_1.readFileSync)(`/proc/${pid}/cmdline`, 'utf8').replace(/\0/g, ' ');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
function killSavedCloudflaredPid() {
|
|
73
|
+
const pid = loadPid();
|
|
74
|
+
if (!pid)
|
|
75
|
+
return;
|
|
76
|
+
if (!isPidAlive(pid)) {
|
|
77
|
+
clearPid();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const commandLine = commandLineForPid(pid);
|
|
81
|
+
if (commandLine && !/cloudflared/i.test(commandLine))
|
|
82
|
+
return;
|
|
83
|
+
try {
|
|
84
|
+
process.kill(pid, process.platform === 'win32' ? undefined : 'SIGTERM');
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// best effort
|
|
88
|
+
}
|
|
89
|
+
clearPid();
|
|
90
|
+
}
|
|
91
|
+
function defaultTunnelTarget() {
|
|
92
|
+
return (process.env.DASHBOARD_TUNNEL_TARGET ||
|
|
93
|
+
process.env.KROUTER_DASHBOARD_TUNNEL_TARGET ||
|
|
94
|
+
process.env.KAM_DASHBOARD_TUNNEL_TARGET ||
|
|
95
|
+
process.env.KROUTER_DASHBOARD_URL ||
|
|
96
|
+
process.env.KAM_DASHBOARD_URL ||
|
|
97
|
+
process.env.PUBLIC_DASHBOARD_URL ||
|
|
98
|
+
process.env.DASHBOARD_URL ||
|
|
99
|
+
`http://127.0.0.1:${process.env.PORT || '4010'}`).trim();
|
|
100
|
+
}
|
|
101
|
+
function defaultCloudflaredBinary() {
|
|
102
|
+
return (process.env.CLOUDFLARED_BIN || process.env.KROUTER_CLOUDFLARED_BIN || process.env.KAM_CLOUDFLARED_BIN || 'cloudflared').trim();
|
|
103
|
+
}
|
|
104
|
+
function defaultHttpHostHeader(localUrl) {
|
|
105
|
+
const explicit = process.env.DASHBOARD_TUNNEL_HOST_HEADER || process.env.KROUTER_DASHBOARD_TUNNEL_HOST_HEADER || process.env.KAM_DASHBOARD_TUNNEL_HOST_HEADER || process.env.TUNNEL_HTTP_HOST_HEADER;
|
|
106
|
+
if (explicit !== undefined)
|
|
107
|
+
return explicit.trim();
|
|
108
|
+
try {
|
|
109
|
+
return new URL(localUrl).host;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return '';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function isHttpUrl(value) {
|
|
116
|
+
try {
|
|
117
|
+
const url = new URL(value);
|
|
118
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
class DashboardTunnelRuntime {
|
|
125
|
+
process = null;
|
|
126
|
+
cleanupRegistered = false;
|
|
127
|
+
status = {
|
|
128
|
+
running: false,
|
|
129
|
+
requested: false,
|
|
130
|
+
localUrl: defaultTunnelTarget(),
|
|
131
|
+
httpHostHeader: defaultHttpHostHeader(defaultTunnelTarget()),
|
|
132
|
+
binary: defaultCloudflaredBinary(),
|
|
133
|
+
logs: []
|
|
134
|
+
};
|
|
135
|
+
constructor() {
|
|
136
|
+
this.registerCleanup();
|
|
137
|
+
}
|
|
138
|
+
getStatus() {
|
|
139
|
+
if (this.process && this.process.exitCode === null && !this.process.killed) {
|
|
140
|
+
this.status.running = true;
|
|
141
|
+
this.status.pid = this.process.pid;
|
|
142
|
+
}
|
|
143
|
+
else if (this.process) {
|
|
144
|
+
this.process = null;
|
|
145
|
+
this.status.running = false;
|
|
146
|
+
delete this.status.pid;
|
|
147
|
+
delete this.status.publicUrl;
|
|
148
|
+
}
|
|
149
|
+
this.status.localUrl = this.status.localUrl || defaultTunnelTarget();
|
|
150
|
+
this.status.httpHostHeader = this.status.httpHostHeader || defaultHttpHostHeader(this.status.localUrl);
|
|
151
|
+
this.status.binary = this.status.binary || defaultCloudflaredBinary();
|
|
152
|
+
return { ...this.status, logs: [...this.status.logs] };
|
|
153
|
+
}
|
|
154
|
+
async start(input = {}) {
|
|
155
|
+
const existing = this.getStatus();
|
|
156
|
+
const localUrl = (input.localUrl || defaultTunnelTarget()).trim();
|
|
157
|
+
const binary = (input.binary || defaultCloudflaredBinary()).trim();
|
|
158
|
+
const httpHostHeader = (input.httpHostHeader || defaultHttpHostHeader(localUrl)).trim();
|
|
159
|
+
if (existing.running) {
|
|
160
|
+
if (existing.localUrl === localUrl && (existing.httpHostHeader || '') === httpHostHeader) {
|
|
161
|
+
return { success: true, status: existing };
|
|
162
|
+
}
|
|
163
|
+
await this.stop();
|
|
164
|
+
}
|
|
165
|
+
if (!isHttpUrl(localUrl)) {
|
|
166
|
+
const error = 'Tunnel target must be an HTTP/HTTPS URL.';
|
|
167
|
+
this.status = { ...this.status, localUrl, httpHostHeader, binary, requested: false, running: false, error };
|
|
168
|
+
return { success: false, status: this.getStatus(), error };
|
|
169
|
+
}
|
|
170
|
+
this.status = {
|
|
171
|
+
running: false,
|
|
172
|
+
requested: true,
|
|
173
|
+
localUrl,
|
|
174
|
+
httpHostHeader,
|
|
175
|
+
binary,
|
|
176
|
+
startedAt: Date.now(),
|
|
177
|
+
logs: [],
|
|
178
|
+
error: undefined
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
killSavedCloudflaredPid();
|
|
182
|
+
const args = ['tunnel', '--url', localUrl];
|
|
183
|
+
if (httpHostHeader)
|
|
184
|
+
args.push('--http-host-header', httpHostHeader);
|
|
185
|
+
args.push('--no-autoupdate');
|
|
186
|
+
const child = (0, child_process_1.spawn)(binary, args, {
|
|
187
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
188
|
+
windowsHide: true
|
|
189
|
+
});
|
|
190
|
+
this.process = child;
|
|
191
|
+
this.status.pid = child.pid;
|
|
192
|
+
savePid(child.pid);
|
|
193
|
+
child.stdout?.on('data', chunk => this.appendOutput(String(chunk)));
|
|
194
|
+
child.stderr?.on('data', chunk => this.appendOutput(String(chunk)));
|
|
195
|
+
child.once('error', error => {
|
|
196
|
+
this.status.error = `Cannot start cloudflared: ${error.message}`;
|
|
197
|
+
this.status.running = false;
|
|
198
|
+
this.status.requested = false;
|
|
199
|
+
this.process = null;
|
|
200
|
+
});
|
|
201
|
+
child.once('exit', (code, signal) => {
|
|
202
|
+
if (loadPid() === child.pid)
|
|
203
|
+
clearPid();
|
|
204
|
+
this.status.running = false;
|
|
205
|
+
this.status.requested = false;
|
|
206
|
+
delete this.status.pid;
|
|
207
|
+
delete this.status.publicUrl;
|
|
208
|
+
if (!this.status.error && code !== 0) {
|
|
209
|
+
this.status.error = `cloudflared exited with ${signal || code}`;
|
|
210
|
+
}
|
|
211
|
+
this.process = null;
|
|
212
|
+
});
|
|
213
|
+
const detected = await this.waitForUrlOrExit(12000);
|
|
214
|
+
const status = this.getStatus();
|
|
215
|
+
if (!status.running && status.error)
|
|
216
|
+
return { success: false, status, error: status.error };
|
|
217
|
+
return {
|
|
218
|
+
success: true,
|
|
219
|
+
status,
|
|
220
|
+
error: detected ? undefined : 'Tunnel started, but public URL was not detected yet.'
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
const message = error instanceof Error ? error.message : 'Failed to start tunnel';
|
|
225
|
+
this.status.running = false;
|
|
226
|
+
this.status.requested = false;
|
|
227
|
+
this.status.error = message;
|
|
228
|
+
this.process = null;
|
|
229
|
+
return { success: false, status: this.getStatus(), error: message };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async stop() {
|
|
233
|
+
const child = this.process;
|
|
234
|
+
if (!child || child.killed || child.exitCode !== null) {
|
|
235
|
+
this.process = null;
|
|
236
|
+
this.status.running = false;
|
|
237
|
+
this.status.requested = false;
|
|
238
|
+
delete this.status.pid;
|
|
239
|
+
return { success: true, status: this.getStatus() };
|
|
240
|
+
}
|
|
241
|
+
child.kill(process.platform === 'win32' ? undefined : 'SIGTERM');
|
|
242
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
243
|
+
if (child.exitCode === null && !child.killed)
|
|
244
|
+
child.kill('SIGKILL');
|
|
245
|
+
this.process = null;
|
|
246
|
+
this.status.running = false;
|
|
247
|
+
this.status.requested = false;
|
|
248
|
+
delete this.status.pid;
|
|
249
|
+
delete this.status.publicUrl;
|
|
250
|
+
clearPid();
|
|
251
|
+
return { success: true, status: this.getStatus() };
|
|
252
|
+
}
|
|
253
|
+
stopSync() {
|
|
254
|
+
const child = this.process;
|
|
255
|
+
if (child && child.exitCode === null && !child.killed) {
|
|
256
|
+
try {
|
|
257
|
+
child.kill(process.platform === 'win32' ? undefined : 'SIGTERM');
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// best effort
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
killSavedCloudflaredPid();
|
|
265
|
+
}
|
|
266
|
+
clearPid();
|
|
267
|
+
}
|
|
268
|
+
registerCleanup() {
|
|
269
|
+
if (this.cleanupRegistered)
|
|
270
|
+
return;
|
|
271
|
+
this.cleanupRegistered = true;
|
|
272
|
+
const cleanup = () => {
|
|
273
|
+
this.stopSync();
|
|
274
|
+
process.exit(0);
|
|
275
|
+
};
|
|
276
|
+
process.once('SIGINT', cleanup);
|
|
277
|
+
process.once('SIGTERM', cleanup);
|
|
278
|
+
}
|
|
279
|
+
appendOutput(output) {
|
|
280
|
+
for (const rawLine of output.split(/\r?\n/)) {
|
|
281
|
+
const line = rawLine.trim();
|
|
282
|
+
if (!line)
|
|
283
|
+
continue;
|
|
284
|
+
const match = line.match(URL_RE);
|
|
285
|
+
if (match) {
|
|
286
|
+
this.status.publicUrl = match[0];
|
|
287
|
+
this.status.running = true;
|
|
288
|
+
}
|
|
289
|
+
this.status.logs.push(line);
|
|
290
|
+
if (this.status.logs.length > MAX_LOGS) {
|
|
291
|
+
this.status.logs.splice(0, this.status.logs.length - MAX_LOGS);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
waitForUrlOrExit(timeoutMs) {
|
|
296
|
+
return new Promise(resolve => {
|
|
297
|
+
const start = Date.now();
|
|
298
|
+
const timer = setInterval(() => {
|
|
299
|
+
if (this.status.publicUrl) {
|
|
300
|
+
clearInterval(timer);
|
|
301
|
+
resolve(true);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (!this.process || this.process.exitCode !== null || Date.now() - start >= timeoutMs) {
|
|
305
|
+
clearInterval(timer);
|
|
306
|
+
resolve(Boolean(this.status.publicUrl));
|
|
307
|
+
}
|
|
308
|
+
}, 200);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const dashboardTunnelRuntime = new DashboardTunnelRuntime();
|
|
313
|
+
function getDashboardTunnelRuntime() {
|
|
314
|
+
return dashboardTunnelRuntime;
|
|
315
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.proxyPoolValidate = proxyPoolValidate;
|
|
4
|
+
exports.networkRouteValidate = networkRouteValidate;
|
|
5
|
+
exports.proxyPoolDiagnoseChain = proxyPoolDiagnoseChain;
|
|
6
|
+
exports.diagnoseAccountLiveness = diagnoseAccountLiveness;
|
|
7
|
+
const undici_1 = require("undici");
|
|
8
|
+
const chainProxy_1 = require("../../main/registration/chainProxy");
|
|
9
|
+
const kiroApi_1 = require("../../main/proxy/kiroApi");
|
|
10
|
+
const systemProxy_1 = require("../../main/proxy/systemProxy");
|
|
11
|
+
const translator_1 = require("../../main/proxy/translator");
|
|
12
|
+
const kiroAccounts_1 = require("./kiroAccounts");
|
|
13
|
+
function compactErrorMessage(error, maxLength = 360) {
|
|
14
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
15
|
+
return raw.split('\n')[0].slice(0, maxLength);
|
|
16
|
+
}
|
|
17
|
+
function isAccountAuthBlocked(error) {
|
|
18
|
+
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
19
|
+
return message.includes('auth error 401')
|
|
20
|
+
|| message.includes('auth error 403')
|
|
21
|
+
|| message.includes('temporarily_suspended')
|
|
22
|
+
|| message.includes('permanently_suspended')
|
|
23
|
+
|| message.includes('accountsuspendedexception')
|
|
24
|
+
|| message.includes('temporarily suspended')
|
|
25
|
+
|| message.includes('account suspended')
|
|
26
|
+
|| message.includes('user id is temporarily suspended')
|
|
27
|
+
|| message.includes('unusual user activity')
|
|
28
|
+
|| message.includes('locked it as a security precaution')
|
|
29
|
+
|| message.includes('restricted your ability to use kiro');
|
|
30
|
+
}
|
|
31
|
+
function isInvalidBearerToken(error) {
|
|
32
|
+
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
33
|
+
return message.includes('bearer token included in the request is invalid')
|
|
34
|
+
|| message.includes('invalid bearer');
|
|
35
|
+
}
|
|
36
|
+
async function proxyPoolValidate(params) {
|
|
37
|
+
const { url, testUrl = 'https://api.ipify.org?format=json', timeoutMs = 8000, upstreamProxy } = params || {};
|
|
38
|
+
if (!url)
|
|
39
|
+
return { success: false, error: 'Missing proxy URL' };
|
|
40
|
+
let chainRelay = null;
|
|
41
|
+
let proxyForAgent = url;
|
|
42
|
+
if (upstreamProxy?.trim()) {
|
|
43
|
+
try {
|
|
44
|
+
chainRelay = new chainProxy_1.ChainProxyRelay(upstreamProxy.trim(), url);
|
|
45
|
+
proxyForAgent = await chainRelay.start();
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return { success: false, error: `Proxy chain failed to start: ${error instanceof Error ? error.message : String(error)}` };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const agent = (0, systemProxy_1.safeCreateProxyAgent)(proxyForAgent);
|
|
52
|
+
if (!agent) {
|
|
53
|
+
if (chainRelay)
|
|
54
|
+
await chainRelay.stop();
|
|
55
|
+
return { success: false, error: 'Unsupported proxy protocol or invalid proxy URL' };
|
|
56
|
+
}
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
59
|
+
const started = Date.now();
|
|
60
|
+
try {
|
|
61
|
+
const response = await (0, undici_1.fetch)(testUrl, {
|
|
62
|
+
method: 'GET',
|
|
63
|
+
dispatcher: agent,
|
|
64
|
+
signal: controller.signal,
|
|
65
|
+
headers: { 'User-Agent': 'KiroAccountManager-ProxyValidator/1.0' }
|
|
66
|
+
});
|
|
67
|
+
const latencyMs = Date.now() - started;
|
|
68
|
+
if (response.status < 200 || response.status >= 400)
|
|
69
|
+
return { success: false, latencyMs, error: `HTTP ${response.status}` };
|
|
70
|
+
const text = await response.text().catch(() => '');
|
|
71
|
+
return { success: true, latencyMs, externalIp: extractIp(response.headers.get('content-type') || '', text) };
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
latencyMs: Date.now() - started,
|
|
77
|
+
error: controller.signal.aborted ? `Request timed out (${timeoutMs}ms)` : error instanceof Error ? error.message : String(error)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
if (chainRelay)
|
|
83
|
+
await chainRelay.stop();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function networkRouteValidate(params) {
|
|
87
|
+
const testUrl = params?.testUrl || 'https://api.ipify.org?format=json';
|
|
88
|
+
const timeoutMs = params?.timeoutMs || 8000;
|
|
89
|
+
const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy
|
|
90
|
+
|| process.env.HTTP_PROXY || process.env.http_proxy
|
|
91
|
+
|| (0, systemProxy_1.getSystemProxy)();
|
|
92
|
+
const route = proxyUrl ? 'system-proxy' : 'direct-or-vpn';
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
95
|
+
const started = Date.now();
|
|
96
|
+
try {
|
|
97
|
+
const response = await (0, undici_1.fetch)(testUrl, {
|
|
98
|
+
method: 'GET',
|
|
99
|
+
dispatcher: (0, systemProxy_1.safeCreateProxyAgent)(proxyUrl) || undefined,
|
|
100
|
+
signal: controller.signal,
|
|
101
|
+
headers: { 'User-Agent': 'KiroAccountManager-NetworkValidator/1.0' }
|
|
102
|
+
});
|
|
103
|
+
const latencyMs = Date.now() - started;
|
|
104
|
+
if (response.status < 200 || response.status >= 400) {
|
|
105
|
+
return { success: false, latencyMs, route, error: `HTTP ${response.status}` };
|
|
106
|
+
}
|
|
107
|
+
const text = await response.text().catch(() => '');
|
|
108
|
+
const externalIp = extractIp(response.headers.get('content-type') || '', text);
|
|
109
|
+
return externalIp
|
|
110
|
+
? { success: true, latencyMs, externalIp, route }
|
|
111
|
+
: { success: false, latencyMs, route, error: 'The network check succeeded but no exit IP was returned' };
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
latencyMs: Date.now() - started,
|
|
117
|
+
route,
|
|
118
|
+
error: controller.signal.aborted ? `Request timed out (${timeoutMs}ms)` : error instanceof Error ? error.message : String(error)
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function proxyPoolDiagnoseChain(params) {
|
|
126
|
+
const { targetUrl, upstreamProxy, testHost, testPort } = params || {};
|
|
127
|
+
if (!targetUrl)
|
|
128
|
+
return { success: false, error: 'Missing target proxy URL' };
|
|
129
|
+
if (!upstreamProxy)
|
|
130
|
+
return { success: false, error: 'Missing upstream proxy URL' };
|
|
131
|
+
try {
|
|
132
|
+
const relay = new chainProxy_1.ChainProxyRelay(upstreamProxy, targetUrl);
|
|
133
|
+
return { success: true, diagnose: await relay.diagnose(testHost, testPort) };
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function diagnoseAccountLiveness(params) {
|
|
140
|
+
const account = params?.account;
|
|
141
|
+
const model = (params?.model || 'claude-sonnet-4.5').trim();
|
|
142
|
+
const message = (params?.message || 'Hi, reply with "pong" only.').trim();
|
|
143
|
+
const timeoutMs = params?.timeoutMs ?? 45000;
|
|
144
|
+
const started = Date.now();
|
|
145
|
+
if (!account?.accessToken)
|
|
146
|
+
return { success: false, latencyMs: 0, model, error: 'Account is missing accessToken' };
|
|
147
|
+
const controller = new AbortController();
|
|
148
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
149
|
+
let resolvedProfileArn;
|
|
150
|
+
let runCredentialCheck;
|
|
151
|
+
try {
|
|
152
|
+
let accessToken = account.accessToken;
|
|
153
|
+
const refreshAccessToken = async () => {
|
|
154
|
+
if (!account.refreshToken)
|
|
155
|
+
return null;
|
|
156
|
+
const refreshed = await (0, kiroAccounts_1.refreshTokenByMethod)({
|
|
157
|
+
refreshToken: account.refreshToken,
|
|
158
|
+
clientId: account.clientId,
|
|
159
|
+
clientSecret: account.clientSecret,
|
|
160
|
+
region: account.region || 'us-east-1',
|
|
161
|
+
authMethod: account.authMethod,
|
|
162
|
+
provider: account.provider,
|
|
163
|
+
machineId: account.machineId
|
|
164
|
+
}).catch(() => null);
|
|
165
|
+
return refreshed?.success && refreshed.accessToken ? refreshed.accessToken : null;
|
|
166
|
+
};
|
|
167
|
+
const needsRefresh = account.refreshToken && (!account.expiresAt || account.expiresAt - Date.now() < 60000);
|
|
168
|
+
if (needsRefresh) {
|
|
169
|
+
const refreshedAccessToken = await refreshAccessToken();
|
|
170
|
+
if (refreshedAccessToken)
|
|
171
|
+
accessToken = refreshedAccessToken;
|
|
172
|
+
}
|
|
173
|
+
const proxyAccount = {
|
|
174
|
+
id: account.id || 'diagnose',
|
|
175
|
+
email: account.email,
|
|
176
|
+
accessToken,
|
|
177
|
+
refreshToken: account.refreshToken,
|
|
178
|
+
clientId: account.clientId,
|
|
179
|
+
clientSecret: account.clientSecret,
|
|
180
|
+
region: account.region || 'us-east-1',
|
|
181
|
+
authMethod: account.authMethod,
|
|
182
|
+
provider: account.provider,
|
|
183
|
+
profileArn: account.profileArn,
|
|
184
|
+
machineId: account.machineId,
|
|
185
|
+
proxyUrl: account.proxyUrl,
|
|
186
|
+
expiresAt: account.expiresAt
|
|
187
|
+
};
|
|
188
|
+
resolvedProfileArn = await (0, kiroAccounts_1.resolveStreamingProfileArn)({
|
|
189
|
+
accessToken,
|
|
190
|
+
profileArn: account.profileArn,
|
|
191
|
+
machineId: account.machineId,
|
|
192
|
+
region: account.region || 'us-east-1',
|
|
193
|
+
authMethod: account.authMethod,
|
|
194
|
+
provider: account.provider
|
|
195
|
+
}).catch(() => undefined);
|
|
196
|
+
resolvedProfileArn = resolvedProfileArn || (0, kiroApi_1.resolveProfileArn)(proxyAccount);
|
|
197
|
+
runCredentialCheck = async (fallbackReason, options) => {
|
|
198
|
+
if (account.refreshToken) {
|
|
199
|
+
const verified = await (0, kiroAccounts_1.verifyAccountCredentials)({
|
|
200
|
+
refreshToken: account.refreshToken,
|
|
201
|
+
clientId: account.clientId,
|
|
202
|
+
clientSecret: account.clientSecret,
|
|
203
|
+
region: account.region || 'us-east-1',
|
|
204
|
+
authMethod: account.authMethod,
|
|
205
|
+
provider: account.provider,
|
|
206
|
+
profileArn: account.profileArn,
|
|
207
|
+
machineId: account.machineId
|
|
208
|
+
});
|
|
209
|
+
if (!verified.success) {
|
|
210
|
+
return {
|
|
211
|
+
success: false,
|
|
212
|
+
latencyMs: Date.now() - started,
|
|
213
|
+
model: 'credential-check',
|
|
214
|
+
error: verified.error || 'Credential check failed'
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const usage = verified.data?.usage;
|
|
218
|
+
const email = verified.data?.email || account.email || 'unknown';
|
|
219
|
+
const quota = usage ? `, usage ${usage.current ?? 0}/${usage.limit ?? 0}` : '';
|
|
220
|
+
const message = `${fallbackReason ? `${fallbackReason} ` : ''}Credential and quota check passed for ${email}${quota}.`;
|
|
221
|
+
return {
|
|
222
|
+
success: options?.success ?? true,
|
|
223
|
+
latencyMs: Date.now() - started,
|
|
224
|
+
model: 'credential-check',
|
|
225
|
+
profileArn: verified.data?.profileArn,
|
|
226
|
+
...(options?.success === false ? { error: message } : { content: message })
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const message = `${fallbackReason ? `${fallbackReason} ` : ''}The account has an access token, but no refresh token was available for quota verification.`;
|
|
230
|
+
return {
|
|
231
|
+
success: options?.success ?? true,
|
|
232
|
+
latencyMs: Date.now() - started,
|
|
233
|
+
model: 'credential-check',
|
|
234
|
+
profileArn: resolvedProfileArn,
|
|
235
|
+
...(options?.success === false ? { error: message } : { content: message })
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
if (!resolvedProfileArn) {
|
|
239
|
+
return await runCredentialCheck('No usable streaming profileArn is available for this account, so model chat was skipped.', { success: false });
|
|
240
|
+
}
|
|
241
|
+
proxyAccount.profileArn = resolvedProfileArn;
|
|
242
|
+
const request = {
|
|
243
|
+
model,
|
|
244
|
+
messages: [{ role: 'user', content: message }],
|
|
245
|
+
stream: false,
|
|
246
|
+
max_tokens: 64
|
|
247
|
+
};
|
|
248
|
+
let result = null;
|
|
249
|
+
try {
|
|
250
|
+
result = await (0, kiroApi_1.callKiroApi)(proxyAccount, (0, translator_1.openaiToKiro)(request, resolvedProfileArn), controller.signal);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
if (!controller.signal.aborted && account.refreshToken && isInvalidBearerToken(error)) {
|
|
254
|
+
const refreshedAccessToken = await refreshAccessToken();
|
|
255
|
+
if (refreshedAccessToken) {
|
|
256
|
+
proxyAccount.accessToken = refreshedAccessToken;
|
|
257
|
+
result = await (0, kiroApi_1.callKiroApi)(proxyAccount, (0, translator_1.openaiToKiro)(request, resolvedProfileArn), controller.signal);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (!result) {
|
|
268
|
+
throw new Error('Model liveness did not return a result');
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
success: true,
|
|
272
|
+
latencyMs: Date.now() - started,
|
|
273
|
+
model,
|
|
274
|
+
profileArn: resolvedProfileArn,
|
|
275
|
+
content: result.content.trim().slice(0, 500),
|
|
276
|
+
usage: {
|
|
277
|
+
inputTokens: result.usage.inputTokens || 0,
|
|
278
|
+
outputTokens: result.usage.outputTokens || 0,
|
|
279
|
+
credits: result.usage.credits || 0
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
try {
|
|
285
|
+
if (!controller.signal.aborted && runCredentialCheck && (0, kiroApi_1.isPlaceholderProfileArn)(resolvedProfileArn)) {
|
|
286
|
+
const detail = compactErrorMessage(error);
|
|
287
|
+
if (isAccountAuthBlocked(error)) {
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
latencyMs: Date.now() - started,
|
|
291
|
+
model,
|
|
292
|
+
error: `Model liveness failed: ${detail}`
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return await runCredentialCheck(`Builder ID model liveness fallback: Kiro did not accept the fixed placeholder profileArn (${detail}).`, { success: false });
|
|
296
|
+
}
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
catch (fallbackError) {
|
|
300
|
+
return {
|
|
301
|
+
success: false,
|
|
302
|
+
latencyMs: Date.now() - started,
|
|
303
|
+
model,
|
|
304
|
+
error: controller.signal.aborted ? `Timed out (${timeoutMs}ms)` : fallbackError instanceof Error ? fallbackError.message : String(fallbackError)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
clearTimeout(timer);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function extractIp(contentType, text) {
|
|
313
|
+
if (contentType.includes('json') || text.trimStart().startsWith('{')) {
|
|
314
|
+
try {
|
|
315
|
+
const body = JSON.parse(text);
|
|
316
|
+
const raw = body.ip ?? body.query ?? body.origin ?? body.ipAddress ?? '';
|
|
317
|
+
const match = String(raw).match(/\b\d{1,3}(?:\.\d{1,3}){3}\b/);
|
|
318
|
+
if (match)
|
|
319
|
+
return match[0];
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// Fall back to text extraction below.
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return text.match(/\b\d{1,3}(?:\.\d{1,3}){3}\b/)?.[0];
|
|
326
|
+
}
|