@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,1272 @@
|
|
|
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
|
+
const http_1 = __importDefault(require("http"));
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const store_1 = require("./store");
|
|
10
|
+
const kiroAccounts_1 = require("./services/kiroAccounts");
|
|
11
|
+
const accountProfileHydration_1 = require("./services/accountProfileHydration");
|
|
12
|
+
const localKiroCredentials_1 = require("./services/localKiroCredentials");
|
|
13
|
+
const machineIdRuntime_1 = require("./services/machineIdRuntime");
|
|
14
|
+
const kiroSettings_1 = require("./services/kiroSettings");
|
|
15
|
+
const proxyRuntime_1 = require("./services/proxyRuntime");
|
|
16
|
+
const kproxyRuntime_1 = require("./services/kproxyRuntime");
|
|
17
|
+
const diagnostics_1 = require("./services/diagnostics");
|
|
18
|
+
const accountExtras_1 = require("./services/accountExtras");
|
|
19
|
+
const authFlows_1 = require("./services/authFlows");
|
|
20
|
+
const registrationRuntime_1 = require("./services/registrationRuntime");
|
|
21
|
+
const protonBrowserRuntime_1 = require("./services/protonBrowserRuntime");
|
|
22
|
+
const dashboardTunnel_1 = require("./services/dashboardTunnel");
|
|
23
|
+
const store = new store_1.WebStore();
|
|
24
|
+
const sseClients = new Set();
|
|
25
|
+
const dashboardTunnelRuntime = (0, dashboardTunnel_1.getDashboardTunnelRuntime)();
|
|
26
|
+
const SESSION_COOKIE_NAME = 'krouter_session';
|
|
27
|
+
const LEGACY_SESSION_COOKIE_NAME = 'kam_session';
|
|
28
|
+
const TOKEN_REFRESH_BEFORE_EXPIRY_MS = 5 * 60 * 1000;
|
|
29
|
+
const BACKEND_AUTO_REFRESH_MIN_INTERVAL_MS = 60 * 1000;
|
|
30
|
+
const backendAutoRefreshTimers = new Map();
|
|
31
|
+
const backendAutoRefreshRunning = new Set();
|
|
32
|
+
function envFlag(name) {
|
|
33
|
+
const raw = process.env[name];
|
|
34
|
+
if (raw === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (/^(1|true|yes|on)$/i.test(raw))
|
|
37
|
+
return true;
|
|
38
|
+
if (/^(0|false|no|off)$/i.test(raw))
|
|
39
|
+
return false;
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
function shouldServeStatic() {
|
|
43
|
+
if (process.argv.includes('--api-only') || process.argv.includes('--backend-only'))
|
|
44
|
+
return false;
|
|
45
|
+
if (process.argv.includes('--serve-static'))
|
|
46
|
+
return true;
|
|
47
|
+
const mode = (process.env.KROUTER_SERVER_MODE || process.env.KAM_SERVER_MODE || process.env.SERVER_MODE || '').trim().toLowerCase();
|
|
48
|
+
if (mode === 'api' || mode === 'backend' || mode === 'cli')
|
|
49
|
+
return false;
|
|
50
|
+
if (mode === 'fullstack' || mode === 'web')
|
|
51
|
+
return true;
|
|
52
|
+
return envFlag('SERVE_STATIC') ?? true;
|
|
53
|
+
}
|
|
54
|
+
function shouldAutoStartDashboardTunnel() {
|
|
55
|
+
return Boolean(envFlag('KROUTER_DASHBOARD_TUNNEL_AUTOSTART') ??
|
|
56
|
+
envFlag('KAM_DASHBOARD_TUNNEL_AUTOSTART') ??
|
|
57
|
+
envFlag('DASHBOARD_TUNNEL_AUTOSTART') ??
|
|
58
|
+
false);
|
|
59
|
+
}
|
|
60
|
+
const serveStaticAssets = shouldServeStatic();
|
|
61
|
+
function packageVersion() {
|
|
62
|
+
try {
|
|
63
|
+
const raw = require('fs').readFileSync(path_1.default.join(process.cwd(), 'package.json'), 'utf8');
|
|
64
|
+
return JSON.parse(raw).version || '0.0.0';
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return '0.0.0';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function sendJson(response, status, data) {
|
|
71
|
+
response.writeHead(status, {
|
|
72
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
73
|
+
'Cache-Control': 'no-store'
|
|
74
|
+
});
|
|
75
|
+
response.end(JSON.stringify(data));
|
|
76
|
+
}
|
|
77
|
+
function sendHtml(response, status, html) {
|
|
78
|
+
response.writeHead(status, {
|
|
79
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
80
|
+
'Cache-Control': 'no-store'
|
|
81
|
+
});
|
|
82
|
+
response.end(html);
|
|
83
|
+
}
|
|
84
|
+
function parseCookies(request) {
|
|
85
|
+
const header = request.headers.cookie || '';
|
|
86
|
+
return Object.fromEntries(header
|
|
87
|
+
.split(';')
|
|
88
|
+
.map((part) => part.trim())
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.map((part) => {
|
|
91
|
+
const index = part.indexOf('=');
|
|
92
|
+
if (index === -1)
|
|
93
|
+
return [part, ''];
|
|
94
|
+
return [decodeURIComponent(part.slice(0, index)), decodeURIComponent(part.slice(index + 1))];
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
function sessionCookie(sessionId, expiresAt) {
|
|
98
|
+
const secure = process.env.COOKIE_SECURE === 'true' ? '; Secure' : '';
|
|
99
|
+
return [
|
|
100
|
+
`${SESSION_COOKIE_NAME}=${encodeURIComponent(sessionId)}`,
|
|
101
|
+
'HttpOnly',
|
|
102
|
+
'SameSite=Lax',
|
|
103
|
+
'Path=/',
|
|
104
|
+
`Expires=${new Date(expiresAt).toUTCString()}`,
|
|
105
|
+
secure
|
|
106
|
+
].join('; ');
|
|
107
|
+
}
|
|
108
|
+
function clearCookie(name) {
|
|
109
|
+
return `${name}=; HttpOnly; SameSite=Lax; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
110
|
+
}
|
|
111
|
+
async function readJson(request) {
|
|
112
|
+
const chunks = [];
|
|
113
|
+
for await (const chunk of request)
|
|
114
|
+
chunks.push(Buffer.from(chunk));
|
|
115
|
+
if (chunks.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
118
|
+
}
|
|
119
|
+
function getUser(request) {
|
|
120
|
+
const cookies = parseCookies(request);
|
|
121
|
+
return store.findUserBySession(cookies[SESSION_COOKIE_NAME] || cookies[LEGACY_SESSION_COOKIE_NAME]);
|
|
122
|
+
}
|
|
123
|
+
function publicUser(user) {
|
|
124
|
+
return { id: user.id, email: user.email, name: user.name, role: user.role };
|
|
125
|
+
}
|
|
126
|
+
function emit(channel, ...args) {
|
|
127
|
+
const payload = JSON.stringify({ channel, args });
|
|
128
|
+
for (const client of sseClients) {
|
|
129
|
+
client.write(`data: ${payload}\n\n`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function startAutoProxyRuntimes() {
|
|
133
|
+
for (const user of store.getUsers()) {
|
|
134
|
+
const runtime = (0, proxyRuntime_1.getProxyRuntime)(store, user.id, emit);
|
|
135
|
+
const result = await runtime.ensureAutoStarted('server-boot');
|
|
136
|
+
if (!result.success) {
|
|
137
|
+
console.error(`[Server] Proxy auto-start skipped for ${user.email}: ${result.error}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function startDashboardTunnelIfConfigured() {
|
|
142
|
+
if (!shouldAutoStartDashboardTunnel())
|
|
143
|
+
return;
|
|
144
|
+
const result = await dashboardTunnelRuntime.start();
|
|
145
|
+
if (!result.success) {
|
|
146
|
+
console.error(`[Server] Dashboard tunnel auto-start skipped: ${result.error || result.status.error || 'unknown error'}`);
|
|
147
|
+
}
|
|
148
|
+
else if (result.status.publicUrl) {
|
|
149
|
+
console.log(`[Server] Dashboard tunnel running at ${result.status.publicUrl}`);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log('[Server] Dashboard tunnel start requested; public URL is not ready yet');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function defaultAccountData() {
|
|
156
|
+
return {
|
|
157
|
+
accounts: {},
|
|
158
|
+
groups: {},
|
|
159
|
+
tags: {},
|
|
160
|
+
activeAccountId: null,
|
|
161
|
+
autoRefreshEnabled: true,
|
|
162
|
+
autoRefreshInterval: 5,
|
|
163
|
+
autoRefreshConcurrency: 100,
|
|
164
|
+
autoRefreshSyncInfo: true,
|
|
165
|
+
statusCheckInterval: 60,
|
|
166
|
+
privacyMode: false,
|
|
167
|
+
usagePrecision: false,
|
|
168
|
+
proxyEnabled: false,
|
|
169
|
+
proxyUrl: '',
|
|
170
|
+
autoSwitchEnabled: false,
|
|
171
|
+
autoSwitchThreshold: 0,
|
|
172
|
+
autoSwitchInterval: 5,
|
|
173
|
+
switchTarget: 'ide',
|
|
174
|
+
theme: 'default',
|
|
175
|
+
darkMode: false,
|
|
176
|
+
language: 'auto',
|
|
177
|
+
machineIdConfig: {
|
|
178
|
+
autoSwitchOnAccountChange: false,
|
|
179
|
+
bindMachineIdToAccount: false,
|
|
180
|
+
useBindedMachineId: true
|
|
181
|
+
},
|
|
182
|
+
currentMachineId: '',
|
|
183
|
+
originalMachineId: null,
|
|
184
|
+
originalBackupTime: null,
|
|
185
|
+
accountMachineIds: {},
|
|
186
|
+
machineIdHistory: [],
|
|
187
|
+
proxyPool: {},
|
|
188
|
+
proxyPoolConfig: {
|
|
189
|
+
enabled: false,
|
|
190
|
+
strategy: 'round_robin',
|
|
191
|
+
validateOnStartup: false,
|
|
192
|
+
autoDisableDead: true,
|
|
193
|
+
failureThreshold: 3,
|
|
194
|
+
testUrl: 'https://api.ipify.org?format=json',
|
|
195
|
+
testTimeoutMs: 8000,
|
|
196
|
+
autoValidateIntervalMin: 0,
|
|
197
|
+
autoValidateConcurrency: 5,
|
|
198
|
+
upstreamProxy: ''
|
|
199
|
+
},
|
|
200
|
+
proxyPoolCursor: 0,
|
|
201
|
+
accountProxyBindings: {}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function mergeAccountData(currentRaw, incomingRaw) {
|
|
205
|
+
const current = currentRaw && typeof currentRaw === 'object'
|
|
206
|
+
? currentRaw
|
|
207
|
+
: defaultAccountData();
|
|
208
|
+
const incoming = incomingRaw && typeof incomingRaw === 'object'
|
|
209
|
+
? incomingRaw
|
|
210
|
+
: defaultAccountData();
|
|
211
|
+
const currentAccounts = current.accounts && typeof current.accounts === 'object'
|
|
212
|
+
? current.accounts
|
|
213
|
+
: {};
|
|
214
|
+
const incomingAccounts = incoming.accounts && typeof incoming.accounts === 'object'
|
|
215
|
+
? incoming.accounts
|
|
216
|
+
: {};
|
|
217
|
+
const deletedIds = new Set([
|
|
218
|
+
...(Array.isArray(current._deletedAccountIds) ? current._deletedAccountIds.filter((id) => typeof id === 'string') : []),
|
|
219
|
+
...(Array.isArray(incoming._deletedAccountIds) ? incoming._deletedAccountIds.filter((id) => typeof id === 'string') : [])
|
|
220
|
+
]);
|
|
221
|
+
const accounts = { ...currentAccounts, ...incomingAccounts };
|
|
222
|
+
for (const id of deletedIds)
|
|
223
|
+
delete accounts[id];
|
|
224
|
+
return {
|
|
225
|
+
...current,
|
|
226
|
+
...incoming,
|
|
227
|
+
accounts,
|
|
228
|
+
_deletedAccountIds: Array.from(deletedIds)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function unsupported(method) {
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
error: `Web backend handler '${method}' has not been ported from Electron yet.`
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function httpProbe(params) {
|
|
238
|
+
const started = Date.now();
|
|
239
|
+
const controller = new AbortController();
|
|
240
|
+
const timeout = setTimeout(() => controller.abort(), params.timeoutMs || 5000);
|
|
241
|
+
try {
|
|
242
|
+
const response = await fetch(params.url, {
|
|
243
|
+
method: params.method || 'GET',
|
|
244
|
+
signal: controller.signal
|
|
245
|
+
});
|
|
246
|
+
return { success: true, status: response.status, latencyMs: Date.now() - started };
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
return { success: false, latencyMs: Date.now() - started, error: error instanceof Error ? error.message : String(error) };
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
clearTimeout(timeout);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function checkForUpdatesManual() {
|
|
256
|
+
try {
|
|
257
|
+
const response = await fetch('https://api.github.com/repos/LightHaru/Krouter/releases/latest');
|
|
258
|
+
const release = await response.json();
|
|
259
|
+
const currentVersion = packageVersion();
|
|
260
|
+
const latestVersion = String(release.tag_name || '').replace(/^v/, '');
|
|
261
|
+
return {
|
|
262
|
+
hasUpdate: latestVersion !== currentVersion,
|
|
263
|
+
currentVersion,
|
|
264
|
+
latestVersion,
|
|
265
|
+
releaseName: release.name,
|
|
266
|
+
releaseNotes: release.body,
|
|
267
|
+
releaseUrl: release.html_url,
|
|
268
|
+
publishedAt: release.published_at,
|
|
269
|
+
assets: Array.isArray(release.assets)
|
|
270
|
+
? release.assets.map((asset) => ({
|
|
271
|
+
name: asset.name,
|
|
272
|
+
downloadUrl: asset.browser_download_url,
|
|
273
|
+
size: asset.size
|
|
274
|
+
}))
|
|
275
|
+
: []
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
return { hasUpdate: false, currentVersion: packageVersion(), error: error instanceof Error ? error.message : String(error) };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function errorMessageFromResult(result) {
|
|
283
|
+
return result?.error?.message || result?.error || 'Unknown error';
|
|
284
|
+
}
|
|
285
|
+
function isPlainRecord(value) {
|
|
286
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
287
|
+
}
|
|
288
|
+
function isBannedAccountError(error) {
|
|
289
|
+
if (!error)
|
|
290
|
+
return false;
|
|
291
|
+
const lowerError = error.toLowerCase();
|
|
292
|
+
return (lowerError.includes('accountsuspendedexception') ||
|
|
293
|
+
lowerError.includes('account suspended') ||
|
|
294
|
+
lowerError.includes('temporarily_suspended') ||
|
|
295
|
+
lowerError.includes('temporarily suspended') ||
|
|
296
|
+
(lowerError.includes('user id is') && lowerError.includes('suspended')) ||
|
|
297
|
+
lowerError.includes('account is locked') ||
|
|
298
|
+
lowerError.includes('security precaution') ||
|
|
299
|
+
lowerError.includes('账户已封禁') ||
|
|
300
|
+
lowerError.includes('已封禁') ||
|
|
301
|
+
/\b423\b/.test(lowerError));
|
|
302
|
+
}
|
|
303
|
+
function clampNumber(value, fallback, min, max) {
|
|
304
|
+
const parsed = Number(value);
|
|
305
|
+
if (!Number.isFinite(parsed))
|
|
306
|
+
return fallback;
|
|
307
|
+
return Math.max(min, Math.min(parsed, max));
|
|
308
|
+
}
|
|
309
|
+
function normalizeBackgroundStatusData(data) {
|
|
310
|
+
const credentials = data?.newCredentials;
|
|
311
|
+
const subscription = data?.subscription || {};
|
|
312
|
+
return {
|
|
313
|
+
accessToken: credentials?.accessToken,
|
|
314
|
+
refreshToken: credentials?.refreshToken,
|
|
315
|
+
expiresIn: credentials?.expiresAt ? Math.max(0, Math.floor((credentials.expiresAt - Date.now()) / 1000)) : undefined,
|
|
316
|
+
usage: data?.usage,
|
|
317
|
+
subscription: {
|
|
318
|
+
...subscription,
|
|
319
|
+
type: data?.subscriptionType || subscription.type || subscription.rawType,
|
|
320
|
+
title: data?.subscriptionTitle || subscription.title,
|
|
321
|
+
daysRemaining: data?.daysRemaining,
|
|
322
|
+
expiresAt: data?.expiresAt,
|
|
323
|
+
subscriptionManagementTarget: subscription.subscriptionManagementTarget || subscription.managementTarget
|
|
324
|
+
},
|
|
325
|
+
userInfo: {
|
|
326
|
+
email: data?.email,
|
|
327
|
+
userId: data?.userId
|
|
328
|
+
},
|
|
329
|
+
profileArn: data?.profileArn,
|
|
330
|
+
status: data?.status,
|
|
331
|
+
errorMessage: data?.errorMessage
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function accountForStatusCheck(account, allowRefresh) {
|
|
335
|
+
if (allowRefresh || !account.credentials?.accessToken)
|
|
336
|
+
return account;
|
|
337
|
+
return {
|
|
338
|
+
...account,
|
|
339
|
+
credentials: {
|
|
340
|
+
...account.credentials,
|
|
341
|
+
expiresAt: account.credentials.expiresAt || Date.now() + 3600000
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function accountNeedsBackendRefresh(account, now) {
|
|
346
|
+
const credentials = account.credentials || {};
|
|
347
|
+
const expiresAt = Number(credentials.expiresAt || 0);
|
|
348
|
+
return !credentials.accessToken || !expiresAt || expiresAt - now <= TOKEN_REFRESH_BEFORE_EXPIRY_MS;
|
|
349
|
+
}
|
|
350
|
+
function getStoredAccounts(accountData) {
|
|
351
|
+
return isPlainRecord(accountData.accounts) ? accountData.accounts : {};
|
|
352
|
+
}
|
|
353
|
+
function applyRefreshDataToStoredAccount(id, account, data, now) {
|
|
354
|
+
const credentials = account.credentials || {};
|
|
355
|
+
const nextCredentials = { ...credentials };
|
|
356
|
+
if (typeof data?.accessToken === 'string' && data.accessToken) {
|
|
357
|
+
nextCredentials.accessToken = data.accessToken;
|
|
358
|
+
}
|
|
359
|
+
if (typeof data?.refreshToken === 'string' && data.refreshToken) {
|
|
360
|
+
nextCredentials.refreshToken = data.refreshToken;
|
|
361
|
+
}
|
|
362
|
+
const expiresIn = Number(data?.expiresIn);
|
|
363
|
+
if (Number.isFinite(expiresIn) && expiresIn > 0 && nextCredentials.accessToken) {
|
|
364
|
+
nextCredentials.expiresAt = now + expiresIn * 1000;
|
|
365
|
+
}
|
|
366
|
+
let usage = account.usage;
|
|
367
|
+
if (isPlainRecord(data?.usage)) {
|
|
368
|
+
const currentUsage = isPlainRecord(account.usage) ? account.usage : {};
|
|
369
|
+
usage = { ...currentUsage, ...data.usage, lastUpdated: now };
|
|
370
|
+
}
|
|
371
|
+
let subscription = account.subscription;
|
|
372
|
+
if (isPlainRecord(data?.subscription)) {
|
|
373
|
+
const currentSubscription = isPlainRecord(account.subscription) ? account.subscription : {};
|
|
374
|
+
subscription = { ...currentSubscription, ...data.subscription };
|
|
375
|
+
const managementTarget = data.subscription.subscriptionManagementTarget ?? data.subscription.managementTarget ?? currentSubscription.managementTarget;
|
|
376
|
+
if (managementTarget !== undefined)
|
|
377
|
+
subscription.managementTarget = managementTarget;
|
|
378
|
+
}
|
|
379
|
+
const userInfo = isPlainRecord(data?.userInfo) ? data.userInfo : {};
|
|
380
|
+
const status = data?.status === 'error' ? 'error' : 'active';
|
|
381
|
+
const errorMessage = typeof data?.errorMessage === 'string' && data.errorMessage ? data.errorMessage : undefined;
|
|
382
|
+
return {
|
|
383
|
+
...account,
|
|
384
|
+
id: account.id || id,
|
|
385
|
+
email: typeof userInfo.email === 'string' && userInfo.email ? userInfo.email : account.email,
|
|
386
|
+
userId: typeof userInfo.userId === 'string' && userInfo.userId ? userInfo.userId : account.userId,
|
|
387
|
+
profileArn: typeof data?.profileArn === 'string' && data.profileArn ? data.profileArn : account.profileArn,
|
|
388
|
+
credentials: nextCredentials,
|
|
389
|
+
usage,
|
|
390
|
+
subscription,
|
|
391
|
+
status,
|
|
392
|
+
lastError: errorMessage,
|
|
393
|
+
lastCheckedAt: now
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function applyBackendRefreshFailure(id, account, error, now) {
|
|
397
|
+
return {
|
|
398
|
+
...account,
|
|
399
|
+
id: account.id || id,
|
|
400
|
+
status: 'error',
|
|
401
|
+
lastError: error,
|
|
402
|
+
lastCheckedAt: now
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function backendAutoRefreshEnabled() {
|
|
406
|
+
return envFlag('KROUTER_BACKEND_AUTO_REFRESH') ?? envFlag('KAM_BACKEND_AUTO_REFRESH') ?? true;
|
|
407
|
+
}
|
|
408
|
+
async function runBackendAutoRefreshForUser(user, reason) {
|
|
409
|
+
if (!backendAutoRefreshEnabled())
|
|
410
|
+
return;
|
|
411
|
+
if (backendAutoRefreshRunning.has(user.id))
|
|
412
|
+
return;
|
|
413
|
+
backendAutoRefreshRunning.add(user.id);
|
|
414
|
+
try {
|
|
415
|
+
const accountData = (store.getAccountData(user.id) || defaultAccountData());
|
|
416
|
+
if (accountData.autoRefreshEnabled === false)
|
|
417
|
+
return;
|
|
418
|
+
const accounts = getStoredAccounts(accountData);
|
|
419
|
+
const entries = Object.entries(accounts);
|
|
420
|
+
const now = Date.now();
|
|
421
|
+
const syncInfo = accountData.autoRefreshSyncInfo !== false;
|
|
422
|
+
const autoSwitch = Boolean(accountData.autoSwitchEnabled);
|
|
423
|
+
const pending = entries
|
|
424
|
+
.map(([id, account]) => ({
|
|
425
|
+
id,
|
|
426
|
+
account,
|
|
427
|
+
needsTokenRefresh: accountNeedsBackendRefresh(account, now)
|
|
428
|
+
}))
|
|
429
|
+
.filter(({ account, needsTokenRefresh }) => {
|
|
430
|
+
if (isBannedAccountError(account.lastError))
|
|
431
|
+
return false;
|
|
432
|
+
if (!account.credentials?.refreshToken)
|
|
433
|
+
return false;
|
|
434
|
+
return needsTokenRefresh || syncInfo || autoSwitch;
|
|
435
|
+
});
|
|
436
|
+
if (pending.length === 0)
|
|
437
|
+
return;
|
|
438
|
+
const concurrency = clampNumber(accountData.autoRefreshConcurrency, 5, 1, 100);
|
|
439
|
+
let completed = 0;
|
|
440
|
+
let successCount = 0;
|
|
441
|
+
let failedCount = 0;
|
|
442
|
+
let changed = false;
|
|
443
|
+
console.log(`[BackendAutoRefresh] ${user.email}: processing ${pending.length} account(s), reason=${reason}, syncInfo=${syncInfo}`);
|
|
444
|
+
for (let index = 0; index < pending.length; index += concurrency) {
|
|
445
|
+
const batch = pending.slice(index, index + concurrency);
|
|
446
|
+
await Promise.all(batch.map(async ({ id, account, needsTokenRefresh }) => {
|
|
447
|
+
const backgroundAccount = {
|
|
448
|
+
...account,
|
|
449
|
+
id,
|
|
450
|
+
needsTokenRefresh
|
|
451
|
+
};
|
|
452
|
+
let payload;
|
|
453
|
+
try {
|
|
454
|
+
if (!syncInfo && !autoSwitch) {
|
|
455
|
+
const refresh = await (0, kiroAccounts_1.refreshAccountToken)(backgroundAccount);
|
|
456
|
+
payload = refresh.success && refresh.data
|
|
457
|
+
? { id, success: true, data: refresh.data }
|
|
458
|
+
: { id, success: false, error: errorMessageFromResult(refresh) };
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
const status = await (0, kiroAccounts_1.checkAccountStatus)(accountForStatusCheck(backgroundAccount, needsTokenRefresh));
|
|
462
|
+
payload = status?.success && status.data
|
|
463
|
+
? { id, success: true, data: normalizeBackgroundStatusData(status.data) }
|
|
464
|
+
: { id, success: false, error: errorMessageFromResult(status) };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
payload = { id, success: false, error: error instanceof Error ? error.message : String(error) };
|
|
469
|
+
}
|
|
470
|
+
const finishedAt = Date.now();
|
|
471
|
+
if (payload.success) {
|
|
472
|
+
successCount++;
|
|
473
|
+
accounts[id] = applyRefreshDataToStoredAccount(id, accounts[id] || account, payload.data, finishedAt);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
failedCount++;
|
|
477
|
+
accounts[id] = applyBackendRefreshFailure(id, accounts[id] || account, payload.error || 'Unknown error', finishedAt);
|
|
478
|
+
}
|
|
479
|
+
changed = true;
|
|
480
|
+
emit('background-refresh-result', payload);
|
|
481
|
+
}));
|
|
482
|
+
completed += batch.length;
|
|
483
|
+
emit('background-refresh-progress', { completed, total: pending.length, success: successCount, failed: failedCount });
|
|
484
|
+
if (index + concurrency < pending.length)
|
|
485
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
486
|
+
}
|
|
487
|
+
if (changed) {
|
|
488
|
+
accountData.accounts = accounts;
|
|
489
|
+
await store.setAccountData(user.id, accountData);
|
|
490
|
+
await store.audit(user.id, 'backend-token-refresh', {
|
|
491
|
+
reason,
|
|
492
|
+
completed,
|
|
493
|
+
successCount,
|
|
494
|
+
failedCount
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
console.error(`[BackendAutoRefresh] ${user.email}: failed`, error);
|
|
500
|
+
}
|
|
501
|
+
finally {
|
|
502
|
+
backendAutoRefreshRunning.delete(user.id);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function clearBackendAutoRefreshForUser(userId) {
|
|
506
|
+
const timer = backendAutoRefreshTimers.get(userId);
|
|
507
|
+
if (timer)
|
|
508
|
+
clearInterval(timer);
|
|
509
|
+
backendAutoRefreshTimers.delete(userId);
|
|
510
|
+
}
|
|
511
|
+
function scheduleBackendAutoRefreshForUser(user, runNow) {
|
|
512
|
+
clearBackendAutoRefreshForUser(user.id);
|
|
513
|
+
if (!backendAutoRefreshEnabled())
|
|
514
|
+
return;
|
|
515
|
+
const accountData = (store.getAccountData(user.id) || defaultAccountData());
|
|
516
|
+
if (accountData.autoRefreshEnabled === false) {
|
|
517
|
+
console.log(`[BackendAutoRefresh] ${user.email}: disabled by account settings`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const intervalMinutes = clampNumber(accountData.autoRefreshInterval, 5, 1, 1440);
|
|
521
|
+
const intervalMs = Math.max(BACKEND_AUTO_REFRESH_MIN_INTERVAL_MS, intervalMinutes * 60 * 1000);
|
|
522
|
+
const timer = setInterval(() => {
|
|
523
|
+
void runBackendAutoRefreshForUser(user, 'interval');
|
|
524
|
+
}, intervalMs);
|
|
525
|
+
timer.unref?.();
|
|
526
|
+
backendAutoRefreshTimers.set(user.id, timer);
|
|
527
|
+
if (runNow) {
|
|
528
|
+
const initialTimer = setTimeout(() => {
|
|
529
|
+
void runBackendAutoRefreshForUser(user, 'server-boot');
|
|
530
|
+
}, 2000);
|
|
531
|
+
initialTimer.unref?.();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async function startBackendAutoRefreshRuntimes() {
|
|
535
|
+
if (!backendAutoRefreshEnabled()) {
|
|
536
|
+
console.log('[BackendAutoRefresh] Disabled by environment');
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
for (const user of store.getUsers()) {
|
|
540
|
+
scheduleBackendAutoRefreshForUser(user, true);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async function handleBackgroundBatch(method, args) {
|
|
544
|
+
const accounts = Array.isArray(args[0]) ? args[0] : [];
|
|
545
|
+
const concurrency = Math.max(1, Math.min(Number(args[1]) || 5, 100));
|
|
546
|
+
const syncInfo = Boolean(args[2]);
|
|
547
|
+
const isRefresh = method === 'backgroundBatchRefresh';
|
|
548
|
+
const resultChannel = isRefresh ? 'background-refresh-result' : 'background-check-result';
|
|
549
|
+
const progressChannel = isRefresh ? 'background-refresh-progress' : 'background-check-progress';
|
|
550
|
+
let completed = 0;
|
|
551
|
+
let successCount = 0;
|
|
552
|
+
let failedCount = 0;
|
|
553
|
+
for (let index = 0; index < accounts.length; index += concurrency) {
|
|
554
|
+
const batch = accounts.slice(index, index + concurrency);
|
|
555
|
+
await Promise.all(batch.map(async (account) => {
|
|
556
|
+
let payload;
|
|
557
|
+
try {
|
|
558
|
+
if (isRefresh && !syncInfo) {
|
|
559
|
+
const refresh = await (0, kiroAccounts_1.refreshAccountToken)(account);
|
|
560
|
+
payload = refresh.success && refresh.data
|
|
561
|
+
? { id: account.id, success: true, data: refresh.data }
|
|
562
|
+
: { id: account.id, success: false, error: errorMessageFromResult(refresh) };
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
const allowRefresh = isRefresh && Boolean(account.needsTokenRefresh);
|
|
566
|
+
const status = await (0, kiroAccounts_1.checkAccountStatus)(accountForStatusCheck(account, allowRefresh));
|
|
567
|
+
payload = status?.success && status.data
|
|
568
|
+
? { id: account.id, success: true, data: normalizeBackgroundStatusData(status.data) }
|
|
569
|
+
: { id: account.id, success: false, error: errorMessageFromResult(status) };
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
payload = { id: account.id, success: false, error: error instanceof Error ? error.message : String(error) };
|
|
574
|
+
}
|
|
575
|
+
if (payload.success)
|
|
576
|
+
successCount++;
|
|
577
|
+
else
|
|
578
|
+
failedCount++;
|
|
579
|
+
emit(resultChannel, payload);
|
|
580
|
+
}));
|
|
581
|
+
completed += batch.length;
|
|
582
|
+
emit(progressChannel, { completed, total: accounts.length, success: successCount, failed: failedCount });
|
|
583
|
+
if (index + concurrency < accounts.length)
|
|
584
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
585
|
+
}
|
|
586
|
+
return { success: true, completed, successCount, failedCount };
|
|
587
|
+
}
|
|
588
|
+
async function handleIpc(method, args, user) {
|
|
589
|
+
const settings = store.getUserSettings(user.id);
|
|
590
|
+
const proxyRuntime = (0, proxyRuntime_1.getProxyRuntime)(store, user.id, emit);
|
|
591
|
+
const kproxyRuntime = (0, kproxyRuntime_1.getKProxyRuntime)(store, user.id, emit);
|
|
592
|
+
switch (method) {
|
|
593
|
+
case 'getAppVersion':
|
|
594
|
+
return packageVersion();
|
|
595
|
+
case 'loadAccounts':
|
|
596
|
+
{
|
|
597
|
+
const accountData = (store.getAccountData(user.id) || defaultAccountData());
|
|
598
|
+
const hydrated = await (0, accountProfileHydration_1.hydrateAccountDataProfileArns)(accountData);
|
|
599
|
+
if (hydrated.changed)
|
|
600
|
+
await store.setAccountData(user.id, hydrated.data);
|
|
601
|
+
return hydrated.data;
|
|
602
|
+
}
|
|
603
|
+
case 'saveAccounts':
|
|
604
|
+
{
|
|
605
|
+
const merged = mergeAccountData(store.getAccountData(user.id), args[0]);
|
|
606
|
+
const hydrated = await (0, accountProfileHydration_1.hydrateAccountDataProfileArns)(merged);
|
|
607
|
+
await store.setAccountData(user.id, hydrated.data);
|
|
608
|
+
scheduleBackendAutoRefreshForUser(user, false);
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
case 'getLocalActiveAccount':
|
|
612
|
+
return (0, localKiroCredentials_1.getLocalActiveAccount)();
|
|
613
|
+
case 'loadKiroCredentials':
|
|
614
|
+
return (0, localKiroCredentials_1.loadKiroCredentials)();
|
|
615
|
+
case 'importFromSsoToken':
|
|
616
|
+
return (0, authFlows_1.importFromSsoToken)(String(args[0] || ''), String(args[1] || 'us-east-1'));
|
|
617
|
+
case 'startBuilderIdLogin':
|
|
618
|
+
return (0, authFlows_1.startBuilderIdLogin)(String(args[0] || 'us-east-1'));
|
|
619
|
+
case 'pollBuilderIdAuth':
|
|
620
|
+
return (0, authFlows_1.pollBuilderIdAuth)(String(args[0] || 'us-east-1'));
|
|
621
|
+
case 'cancelBuilderIdLogin':
|
|
622
|
+
return (0, authFlows_1.cancelBuilderIdLogin)();
|
|
623
|
+
case 'startIamSsoLogin':
|
|
624
|
+
return (0, authFlows_1.startIamSsoLogin)(String(args[0] || ''), String(args[1] || 'us-east-1'));
|
|
625
|
+
case 'pollIamSsoAuth':
|
|
626
|
+
return (0, authFlows_1.pollIamSsoAuth)();
|
|
627
|
+
case 'completeIamSsoLogin':
|
|
628
|
+
return (0, authFlows_1.completeIamSsoLogin)(String(args[0] || ''));
|
|
629
|
+
case 'cancelIamSsoLogin':
|
|
630
|
+
return (0, authFlows_1.cancelIamSsoLogin)();
|
|
631
|
+
case 'startSocialLogin':
|
|
632
|
+
return (0, authFlows_1.startSocialLogin)(args[0]);
|
|
633
|
+
case 'exchangeSocialToken':
|
|
634
|
+
return (0, authFlows_1.exchangeSocialToken)(String(args[0] || ''), String(args[1] || ''));
|
|
635
|
+
case 'cancelSocialLogin':
|
|
636
|
+
return (0, authFlows_1.cancelSocialLogin)();
|
|
637
|
+
case 'switchAccount':
|
|
638
|
+
return (0, localKiroCredentials_1.switchAccount)(args[0]);
|
|
639
|
+
case 'switchAccountCli':
|
|
640
|
+
return (0, localKiroCredentials_1.switchAccountCli)(args[0]);
|
|
641
|
+
case 'logoutAccount':
|
|
642
|
+
return (0, localKiroCredentials_1.logoutAccount)();
|
|
643
|
+
case 'refreshAccountToken':
|
|
644
|
+
return (0, kiroAccounts_1.refreshAccountToken)(args[0]);
|
|
645
|
+
case 'verifyAccountCredentials':
|
|
646
|
+
return (0, kiroAccounts_1.verifyAccountCredentials)(args[0]);
|
|
647
|
+
case 'checkAccountStatus':
|
|
648
|
+
return (0, kiroAccounts_1.checkAccountStatus)(args[0]);
|
|
649
|
+
case 'accountGetModels':
|
|
650
|
+
return (0, accountExtras_1.accountGetModels)(args);
|
|
651
|
+
case 'accountGetSubscriptions':
|
|
652
|
+
return (0, accountExtras_1.accountGetSubscriptions)(args);
|
|
653
|
+
case 'accountGetSubscriptionUrl':
|
|
654
|
+
return (0, accountExtras_1.accountGetSubscriptionUrl)(args);
|
|
655
|
+
case 'accountSetOverage':
|
|
656
|
+
return (0, accountExtras_1.accountSetOverage)(args);
|
|
657
|
+
case 'machineIdGetOSType':
|
|
658
|
+
return (0, machineIdRuntime_1.machineIdGetOSType)();
|
|
659
|
+
case 'machineIdGenerateRandom':
|
|
660
|
+
return (0, machineIdRuntime_1.machineIdGenerateRandom)();
|
|
661
|
+
case 'machineIdCheckAdmin':
|
|
662
|
+
return (0, machineIdRuntime_1.machineIdCheckAdmin)();
|
|
663
|
+
case 'machineIdGetCurrent':
|
|
664
|
+
return (0, machineIdRuntime_1.machineIdGetCurrent)();
|
|
665
|
+
case 'machineIdSet':
|
|
666
|
+
return (0, machineIdRuntime_1.machineIdSet)(String(args[0] || ''));
|
|
667
|
+
case 'machineIdRequestAdminRestart':
|
|
668
|
+
return false;
|
|
669
|
+
case 'machineIdBackupToFile':
|
|
670
|
+
return (0, machineIdRuntime_1.machineIdBackupToFile)(String(args[0] || ''));
|
|
671
|
+
case 'machineIdRestoreFromFile':
|
|
672
|
+
return (0, machineIdRuntime_1.machineIdRestoreFromFile)();
|
|
673
|
+
case 'setProxy':
|
|
674
|
+
await store.setUserSetting(user.id, 'proxy', { enabled: args[0], url: args[1] });
|
|
675
|
+
return { success: true, normalizedUrl: args[1] };
|
|
676
|
+
case 'getUsageApiType':
|
|
677
|
+
return store.getUserSetting(user.id, 'usageApiType', 'rest');
|
|
678
|
+
case 'setUsageApiType':
|
|
679
|
+
await store.setUserSetting(user.id, 'usageApiType', args[0]);
|
|
680
|
+
return { success: true, type: args[0] };
|
|
681
|
+
case 'getUseKProxyForApi':
|
|
682
|
+
return store.getUserSetting(user.id, 'useKProxyForApi', false);
|
|
683
|
+
case 'setUseKProxyForApi':
|
|
684
|
+
await store.setUserSetting(user.id, 'useKProxyForApi', Boolean(args[0]));
|
|
685
|
+
return { success: true, enabled: Boolean(args[0]) };
|
|
686
|
+
case 'getShowWindowShortcut':
|
|
687
|
+
return store.getUserSetting(user.id, 'showWindowShortcut', 'Ctrl+Shift+K');
|
|
688
|
+
case 'setShowWindowShortcut':
|
|
689
|
+
await store.setUserSetting(user.id, 'showWindowShortcut', args[0]);
|
|
690
|
+
return { success: true };
|
|
691
|
+
case 'getTraySettings':
|
|
692
|
+
return store.getUserSetting(user.id, 'traySettings', {
|
|
693
|
+
enabled: false,
|
|
694
|
+
closeAction: 'quit',
|
|
695
|
+
showNotifications: false,
|
|
696
|
+
minimizeOnStart: false
|
|
697
|
+
});
|
|
698
|
+
case 'saveTraySettings':
|
|
699
|
+
await store.setUserSetting(user.id, 'traySettings', { ...(settings.traySettings || {}), ...(args[0] || {}) });
|
|
700
|
+
return { success: true };
|
|
701
|
+
case 'checkForUpdates':
|
|
702
|
+
return checkForUpdatesManual();
|
|
703
|
+
case 'checkForUpdatesManual':
|
|
704
|
+
return checkForUpdatesManual();
|
|
705
|
+
case 'proxyGetStatus':
|
|
706
|
+
return proxyRuntime.getStatus();
|
|
707
|
+
case 'proxyStart':
|
|
708
|
+
return proxyRuntime.start(args[0]);
|
|
709
|
+
case 'proxyStop':
|
|
710
|
+
return proxyRuntime.stop();
|
|
711
|
+
case 'proxyUpdateConfig':
|
|
712
|
+
return proxyRuntime.updateConfig(args[0]);
|
|
713
|
+
case 'proxyNeedsRestart':
|
|
714
|
+
return proxyRuntime.needsRestart();
|
|
715
|
+
case 'proxyRestart':
|
|
716
|
+
return proxyRuntime.restart();
|
|
717
|
+
case 'proxyGetLogs':
|
|
718
|
+
return proxyRuntime.getLogs(args[0]);
|
|
719
|
+
case 'proxyGetLogsCount':
|
|
720
|
+
return proxyRuntime.getLogsCount();
|
|
721
|
+
case 'proxyClearLogs':
|
|
722
|
+
return proxyRuntime.clearLogs();
|
|
723
|
+
case 'proxySaveLogs':
|
|
724
|
+
await store.setUserSetting(user.id, 'proxyLogs', args[0] || []);
|
|
725
|
+
return { success: true };
|
|
726
|
+
case 'proxyLoadLogs':
|
|
727
|
+
return { success: true, logs: store.getUserSetting(user.id, 'proxyLogs', []) };
|
|
728
|
+
case 'proxyAuditLog':
|
|
729
|
+
return proxyRuntime.auditLog();
|
|
730
|
+
case 'proxyResetCredits':
|
|
731
|
+
return proxyRuntime.resetCredits();
|
|
732
|
+
case 'proxyResetTokens':
|
|
733
|
+
return proxyRuntime.resetTokens();
|
|
734
|
+
case 'proxyResetRequestStats':
|
|
735
|
+
return proxyRuntime.resetRequestStats();
|
|
736
|
+
case 'proxyResetPool':
|
|
737
|
+
return proxyRuntime.resetPool();
|
|
738
|
+
case 'proxyClearAccountSuspended':
|
|
739
|
+
return proxyRuntime.clearAccountSuspended(String(args[0] || ''));
|
|
740
|
+
case 'proxySelfSignedCertInfo':
|
|
741
|
+
return proxyRuntime.selfSignedCertInfo();
|
|
742
|
+
case 'proxySelfSignedCertRegenerate':
|
|
743
|
+
return proxyRuntime.selfSignedCertRegenerate();
|
|
744
|
+
case 'proxyAddAccount':
|
|
745
|
+
return proxyRuntime.addAccount(args[0]);
|
|
746
|
+
case 'proxyRemoveAccount':
|
|
747
|
+
return proxyRuntime.removeAccount(String(args[0] || ''));
|
|
748
|
+
case 'proxySyncAccounts':
|
|
749
|
+
return proxyRuntime.syncAccounts(args[0]);
|
|
750
|
+
case 'proxySyncAccountsFromStore':
|
|
751
|
+
return proxyRuntime.syncAccountsFromStoreAsync();
|
|
752
|
+
case 'proxyGetAccounts':
|
|
753
|
+
return proxyRuntime.getAccounts();
|
|
754
|
+
case 'proxyRefreshModels':
|
|
755
|
+
return proxyRuntime.refreshModels();
|
|
756
|
+
case 'proxyGetModels':
|
|
757
|
+
return proxyRuntime.getModels();
|
|
758
|
+
case 'getKiroAvailableModels':
|
|
759
|
+
return proxyRuntime.getModels();
|
|
760
|
+
case 'getKiroSettings':
|
|
761
|
+
return (0, kiroSettings_1.getKiroSettings)();
|
|
762
|
+
case 'saveKiroSettings':
|
|
763
|
+
return (0, kiroSettings_1.saveKiroSettings)(args[0]);
|
|
764
|
+
case 'openKiroSettingsFile':
|
|
765
|
+
return (0, kiroSettings_1.ensureKiroSettingsFile)();
|
|
766
|
+
case 'openKiroMcpConfig':
|
|
767
|
+
return (0, kiroSettings_1.ensureMcpConfig)(args[0] || 'user');
|
|
768
|
+
case 'openKiroSteeringFolder':
|
|
769
|
+
return (0, kiroSettings_1.ensureSteeringFolder)();
|
|
770
|
+
case 'openKiroSteeringFile':
|
|
771
|
+
return (0, kiroSettings_1.readSteeringFile)(String(args[0] || ''));
|
|
772
|
+
case 'createKiroDefaultRules':
|
|
773
|
+
return (0, kiroSettings_1.createDefaultRules)();
|
|
774
|
+
case 'readKiroSteeringFile':
|
|
775
|
+
return (0, kiroSettings_1.readSteeringFile)(String(args[0] || ''));
|
|
776
|
+
case 'saveKiroSteeringFile':
|
|
777
|
+
return (0, kiroSettings_1.saveSteeringFile)(String(args[0] || ''), String(args[1] || ''));
|
|
778
|
+
case 'deleteKiroSteeringFile':
|
|
779
|
+
return (0, kiroSettings_1.deleteSteeringFile)(String(args[0] || ''));
|
|
780
|
+
case 'saveMcpServer':
|
|
781
|
+
return (0, kiroSettings_1.saveMcpServer)(String(args[0] || ''), args[1], args[2]);
|
|
782
|
+
case 'deleteMcpServer':
|
|
783
|
+
return (0, kiroSettings_1.deleteMcpServer)(String(args[0] || ''));
|
|
784
|
+
case 'proxyGetApiKeys':
|
|
785
|
+
return proxyRuntime.getApiKeys();
|
|
786
|
+
case 'proxyAddApiKey':
|
|
787
|
+
return proxyRuntime.addApiKey(args[0]);
|
|
788
|
+
case 'proxyUpdateApiKey':
|
|
789
|
+
return proxyRuntime.updateApiKey(String(args[0] || ''), args[1]);
|
|
790
|
+
case 'proxyDeleteApiKey':
|
|
791
|
+
return proxyRuntime.deleteApiKey(String(args[0] || ''));
|
|
792
|
+
case 'proxyResetApiKeyUsage':
|
|
793
|
+
return proxyRuntime.resetApiKeyUsage(String(args[0] || ''));
|
|
794
|
+
case 'proxyConfigureClients':
|
|
795
|
+
return proxyRuntime.configureClients(args[0]);
|
|
796
|
+
case 'proxyPoolValidate':
|
|
797
|
+
return (0, diagnostics_1.proxyPoolValidate)(args[0]);
|
|
798
|
+
case 'networkRouteValidate':
|
|
799
|
+
return (0, diagnostics_1.networkRouteValidate)(args[0]);
|
|
800
|
+
case 'proxyPoolDiagnoseChain':
|
|
801
|
+
return (0, diagnostics_1.proxyPoolDiagnoseChain)(args[0]);
|
|
802
|
+
case 'diagnoseHttpProbe':
|
|
803
|
+
return httpProbe(args[0]);
|
|
804
|
+
case 'diagnoseRun': {
|
|
805
|
+
const input = args[0];
|
|
806
|
+
const targets = input?.targets || [];
|
|
807
|
+
const results = await Promise.all(targets.map(async (target) => {
|
|
808
|
+
const result = await httpProbe({ url: target.url, timeoutMs: target.timeoutMs });
|
|
809
|
+
return {
|
|
810
|
+
id: target.id,
|
|
811
|
+
label: target.label,
|
|
812
|
+
url: target.url,
|
|
813
|
+
success: result.success && (!target.expectStatus || target.expectStatus.includes(result.status || 0)),
|
|
814
|
+
httpStatus: result.status,
|
|
815
|
+
latencyMs: result.latencyMs,
|
|
816
|
+
error: result.error
|
|
817
|
+
};
|
|
818
|
+
}));
|
|
819
|
+
return { results };
|
|
820
|
+
}
|
|
821
|
+
case 'diagnoseAccountLiveness':
|
|
822
|
+
return (0, diagnostics_1.diagnoseAccountLiveness)(args[0]);
|
|
823
|
+
case 'dashboardTunnelGetStatus':
|
|
824
|
+
return dashboardTunnelRuntime.getStatus();
|
|
825
|
+
case 'dashboardTunnelStart':
|
|
826
|
+
return dashboardTunnelRuntime.start(args[0]);
|
|
827
|
+
case 'dashboardTunnelStop':
|
|
828
|
+
return dashboardTunnelRuntime.stop();
|
|
829
|
+
case 'registrationStartAuto':
|
|
830
|
+
return (0, registrationRuntime_1.registrationStartAuto)(args[0], emit);
|
|
831
|
+
case 'registrationManualPhase1':
|
|
832
|
+
return (0, registrationRuntime_1.registrationManualPhase1)(args[0], emit);
|
|
833
|
+
case 'registrationManualPhase2':
|
|
834
|
+
return (0, registrationRuntime_1.registrationManualPhase2)(String(args[0] || ''), args[1]);
|
|
835
|
+
case 'registrationManualPhase3':
|
|
836
|
+
return (0, registrationRuntime_1.registrationManualPhase3)(String(args[0] || ''));
|
|
837
|
+
case 'registrationStatus':
|
|
838
|
+
return (0, registrationRuntime_1.registrationStatus)();
|
|
839
|
+
case 'registrationCancel':
|
|
840
|
+
return (0, registrationRuntime_1.registrationCancel)(args[0]);
|
|
841
|
+
case 'protonOpenLogin':
|
|
842
|
+
return (0, registrationRuntime_1.protonOpenLogin)();
|
|
843
|
+
case 'protonLoginStatus':
|
|
844
|
+
return (0, registrationRuntime_1.protonLoginStatus)();
|
|
845
|
+
case 'protonClose':
|
|
846
|
+
return (0, registrationRuntime_1.protonClose)();
|
|
847
|
+
case 'kproxyGetStatus':
|
|
848
|
+
return kproxyRuntime.getStatus();
|
|
849
|
+
case 'kproxyGenerateDeviceId':
|
|
850
|
+
return kproxyRuntime.generateDeviceId();
|
|
851
|
+
case 'kproxyGetDeviceMappings':
|
|
852
|
+
return kproxyRuntime.getDeviceMappings();
|
|
853
|
+
case 'kproxyInit':
|
|
854
|
+
return kproxyRuntime.init();
|
|
855
|
+
case 'kproxyStart':
|
|
856
|
+
return kproxyRuntime.start(args[0]);
|
|
857
|
+
case 'kproxyStop':
|
|
858
|
+
return kproxyRuntime.stop();
|
|
859
|
+
case 'kproxyUpdateConfig':
|
|
860
|
+
return kproxyRuntime.updateConfig(args[0]);
|
|
861
|
+
case 'kproxySetDeviceId':
|
|
862
|
+
return kproxyRuntime.setDeviceId(String(args[0] || ''));
|
|
863
|
+
case 'kproxyAddDeviceMapping':
|
|
864
|
+
return kproxyRuntime.addDeviceMapping(args[0]);
|
|
865
|
+
case 'kproxySwitchToAccount':
|
|
866
|
+
return kproxyRuntime.switchToAccount(String(args[0] || ''));
|
|
867
|
+
case 'kproxyGetCaCert':
|
|
868
|
+
return kproxyRuntime.getCaCert();
|
|
869
|
+
case 'kproxyExportCaCert':
|
|
870
|
+
return kproxyRuntime.exportCaCert(args[0]);
|
|
871
|
+
case 'kproxyCheckCaCertInstalled':
|
|
872
|
+
return kproxyRuntime.checkCaCertInstalled();
|
|
873
|
+
case 'kproxyInstallCaCert':
|
|
874
|
+
return kproxyRuntime.installCaCert();
|
|
875
|
+
case 'kproxyUninstallCaCert':
|
|
876
|
+
return kproxyRuntime.uninstallCaCert();
|
|
877
|
+
case 'kproxyResetStats':
|
|
878
|
+
return kproxyRuntime.resetStats();
|
|
879
|
+
case 'accountSetProxyBinding': {
|
|
880
|
+
const [accountId, proxyUrl] = args;
|
|
881
|
+
const accountData = (store.getAccountData(user.id) || defaultAccountData());
|
|
882
|
+
accountData.accountProxyBindings = { ...(accountData.accountProxyBindings || {}), [accountId]: proxyUrl };
|
|
883
|
+
if (!proxyUrl)
|
|
884
|
+
delete accountData.accountProxyBindings[accountId];
|
|
885
|
+
await store.setAccountData(user.id, accountData);
|
|
886
|
+
return { success: true };
|
|
887
|
+
}
|
|
888
|
+
case 'backgroundBatchRefresh':
|
|
889
|
+
case 'backgroundBatchCheck':
|
|
890
|
+
return handleBackgroundBatch(method, args);
|
|
891
|
+
default:
|
|
892
|
+
return unsupported(method);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
async function handleAuth(request, response, pathname) {
|
|
896
|
+
if (pathname === '/api/auth/social/callback' && request.method === 'GET') {
|
|
897
|
+
const callbackUrl = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
|
|
898
|
+
const result = (0, authFlows_1.handleSocialCallback)(callbackUrl, emit);
|
|
899
|
+
(0, authFlows_1.sendAuthHtml)(response, result.title, result.body);
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (pathname === '/api/auth/iam-sso/callback' && request.method === 'GET') {
|
|
903
|
+
const callbackUrl = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
|
|
904
|
+
const result = await (0, authFlows_1.handleIamSsoCallback)(callbackUrl);
|
|
905
|
+
(0, authFlows_1.sendAuthHtml)(response, result.title, result.body);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (pathname === '/api/auth/session' && request.method === 'GET') {
|
|
909
|
+
const user = getUser(request);
|
|
910
|
+
sendJson(response, 200, user
|
|
911
|
+
? { authenticated: true, setupRequired: false, user: publicUser(user) }
|
|
912
|
+
: { authenticated: false, setupRequired: store.isSetupRequired() });
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
if (pathname === '/api/auth/setup/status' && request.method === 'GET') {
|
|
916
|
+
sendJson(response, 200, { setupRequired: store.isSetupRequired() });
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (pathname === '/api/auth/setup' && request.method === 'POST') {
|
|
920
|
+
if (!store.isSetupRequired()) {
|
|
921
|
+
sendJson(response, 409, { error: 'Krouter is already set up' });
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
const body = await readJson(request);
|
|
925
|
+
const mode = String(body?.mode || '').trim();
|
|
926
|
+
const generatedPassword = mode === 'random' ? store_1.WebStore.generateAdminPassword() : undefined;
|
|
927
|
+
const password = generatedPassword || String(body?.password || '');
|
|
928
|
+
if (mode !== 'random' && mode !== 'custom') {
|
|
929
|
+
sendJson(response, 400, { error: 'Choose random or custom password setup' });
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
try {
|
|
933
|
+
const user = await store.createInitialAdmin({
|
|
934
|
+
email: String(body?.email || '').trim() || undefined,
|
|
935
|
+
password
|
|
936
|
+
});
|
|
937
|
+
const session = await store.createSession(user.id);
|
|
938
|
+
scheduleBackendAutoRefreshForUser(user, false);
|
|
939
|
+
response.setHeader('Set-Cookie', sessionCookie(session.id, session.expiresAt));
|
|
940
|
+
sendJson(response, 200, {
|
|
941
|
+
authenticated: true,
|
|
942
|
+
setupRequired: false,
|
|
943
|
+
user: publicUser(user),
|
|
944
|
+
generatedPassword
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
catch (error) {
|
|
948
|
+
sendJson(response, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
949
|
+
}
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
if (pathname === '/api/auth/login' && request.method === 'POST') {
|
|
953
|
+
if (store.isSetupRequired()) {
|
|
954
|
+
sendJson(response, 428, { error: 'Krouter setup is required first', setupRequired: true });
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const body = await readJson(request);
|
|
958
|
+
const email = String(body?.email || '').trim();
|
|
959
|
+
const user = email
|
|
960
|
+
? store.findUserByEmail(email)
|
|
961
|
+
: store.getUsers().find(item => item.role === 'admin') || store.getUsers()[0];
|
|
962
|
+
if (!user || !(0, store_1.verifyPassword)(String(body?.password || ''), user)) {
|
|
963
|
+
sendJson(response, 401, { error: email ? 'Invalid email or password' : 'Invalid password' });
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
const session = await store.createSession(user.id);
|
|
967
|
+
response.setHeader('Set-Cookie', sessionCookie(session.id, session.expiresAt));
|
|
968
|
+
sendJson(response, 200, { authenticated: true, user: publicUser(user) });
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if (pathname === '/api/auth/logout' && request.method === 'POST') {
|
|
972
|
+
const cookies = parseCookies(request);
|
|
973
|
+
await store.deleteSession(cookies[SESSION_COOKIE_NAME] || cookies[LEGACY_SESSION_COOKIE_NAME]);
|
|
974
|
+
response.setHeader('Set-Cookie', [clearCookie(SESSION_COOKIE_NAME), clearCookie(LEGACY_SESSION_COOKIE_NAME)]);
|
|
975
|
+
sendJson(response, 200, { success: true });
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
sendJson(response, 404, { error: 'Not found' });
|
|
979
|
+
}
|
|
980
|
+
function protonLoginPageHtml() {
|
|
981
|
+
return `<!doctype html>
|
|
982
|
+
<html lang="en">
|
|
983
|
+
<head>
|
|
984
|
+
<meta charset="utf-8">
|
|
985
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
986
|
+
<title>Proton Login</title>
|
|
987
|
+
<style>
|
|
988
|
+
:root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif; }
|
|
989
|
+
body { margin: 0; min-height: 100vh; background: #111827; color: #e5e7eb; }
|
|
990
|
+
.bar { position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 10px; background: #0f172a; border-bottom: 1px solid #334155; }
|
|
991
|
+
button, input { height: 34px; border-radius: 6px; border: 1px solid #475569; background: #1e293b; color: #f8fafc; font: inherit; }
|
|
992
|
+
button { padding: 0 12px; cursor: pointer; }
|
|
993
|
+
button:hover { background: #334155; }
|
|
994
|
+
input { min-width: 220px; padding: 0 10px; }
|
|
995
|
+
.status { flex: 1 1 320px; min-width: 260px; color: #cbd5e1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
996
|
+
.wrap { padding: 12px; }
|
|
997
|
+
.screen { display: block; max-width: 100%; height: auto; margin: 0 auto; border: 1px solid #334155; background: #020617; cursor: crosshair; }
|
|
998
|
+
.hint { padding: 8px 12px 0; color: #94a3b8; font-size: 13px; }
|
|
999
|
+
.error { color: #fca5a5; }
|
|
1000
|
+
</style>
|
|
1001
|
+
</head>
|
|
1002
|
+
<body>
|
|
1003
|
+
<div class="bar">
|
|
1004
|
+
<button id="refresh">Refresh</button>
|
|
1005
|
+
<button id="inbox">Inbox</button>
|
|
1006
|
+
<button id="statusBtn">Check</button>
|
|
1007
|
+
<input id="text" autocomplete="off" placeholder="Text for focused field">
|
|
1008
|
+
<button id="typeBtn">Type</button>
|
|
1009
|
+
<button data-key="Tab">Tab</button>
|
|
1010
|
+
<button data-key="Enter">Enter</button>
|
|
1011
|
+
<button data-key="Backspace">Backspace</button>
|
|
1012
|
+
<button data-key="Escape">Esc</button>
|
|
1013
|
+
<button id="closeBtn">Close Browser</button>
|
|
1014
|
+
<span class="status" id="status">Starting Proton browser...</span>
|
|
1015
|
+
</div>
|
|
1016
|
+
<div class="hint">Click the screenshot to interact with the server browser. Use the text box to type into the currently focused Proton field.</div>
|
|
1017
|
+
<div class="wrap"><img id="screen" class="screen" alt="Proton browser screenshot"></div>
|
|
1018
|
+
<script>
|
|
1019
|
+
const screen = document.getElementById("screen");
|
|
1020
|
+
const statusEl = document.getElementById("status");
|
|
1021
|
+
const textInput = document.getElementById("text");
|
|
1022
|
+
let lastWidth = 1280;
|
|
1023
|
+
let lastHeight = 900;
|
|
1024
|
+
let busy = false;
|
|
1025
|
+
|
|
1026
|
+
function setStatus(text, isError) {
|
|
1027
|
+
statusEl.textContent = text;
|
|
1028
|
+
statusEl.className = isError ? "status error" : "status";
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
async function api(path, body) {
|
|
1032
|
+
const init = body === undefined
|
|
1033
|
+
? { credentials: "include" }
|
|
1034
|
+
: { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) };
|
|
1035
|
+
const response = await fetch(path, init);
|
|
1036
|
+
const data = await response.json();
|
|
1037
|
+
if (!response.ok || data.success === false) throw new Error(data.error || response.statusText);
|
|
1038
|
+
return data;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
async function refresh() {
|
|
1042
|
+
if (busy) return;
|
|
1043
|
+
busy = true;
|
|
1044
|
+
try {
|
|
1045
|
+
setStatus("Refreshing screenshot...", false);
|
|
1046
|
+
const width = Math.max(900, Math.min(1280, Math.floor(window.innerWidth - 32)));
|
|
1047
|
+
const data = await api("/api/proton/screenshot?width=" + width + "&height=900");
|
|
1048
|
+
lastWidth = data.width || lastWidth;
|
|
1049
|
+
lastHeight = data.height || lastHeight;
|
|
1050
|
+
screen.src = data.dataUrl;
|
|
1051
|
+
setStatus((data.loggedIn ? "Logged in" : "Not logged in") + " - " + (data.url || ""), false);
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
setStatus(error.message || String(error), true);
|
|
1054
|
+
} finally {
|
|
1055
|
+
busy = false;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
async function sendAction(path, body, delayMs) {
|
|
1060
|
+
try {
|
|
1061
|
+
await api(path, body);
|
|
1062
|
+
setTimeout(refresh, delayMs || 350);
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
setStatus(error.message || String(error), true);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
screen.addEventListener("click", (event) => {
|
|
1069
|
+
const rect = screen.getBoundingClientRect();
|
|
1070
|
+
const x = (event.clientX - rect.left) * lastWidth / rect.width;
|
|
1071
|
+
const y = (event.clientY - rect.top) * lastHeight / rect.height;
|
|
1072
|
+
sendAction("/api/proton/click", { x, y });
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
screen.addEventListener("wheel", (event) => {
|
|
1076
|
+
event.preventDefault();
|
|
1077
|
+
const rect = screen.getBoundingClientRect();
|
|
1078
|
+
const x = (event.clientX - rect.left) * lastWidth / rect.width;
|
|
1079
|
+
const y = (event.clientY - rect.top) * lastHeight / rect.height;
|
|
1080
|
+
sendAction("/api/proton/scroll", { x, y, deltaY: event.deltaY }, 200);
|
|
1081
|
+
}, { passive: false });
|
|
1082
|
+
|
|
1083
|
+
document.getElementById("refresh").onclick = refresh;
|
|
1084
|
+
document.getElementById("inbox").onclick = () => sendAction("/api/proton/navigate", {});
|
|
1085
|
+
document.getElementById("statusBtn").onclick = async () => {
|
|
1086
|
+
try {
|
|
1087
|
+
const data = await api("/api/proton/status");
|
|
1088
|
+
setStatus((data.loggedIn ? "Logged in" : "Not logged in") + " - " + (data.url || ""), false);
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
setStatus(error.message || String(error), true);
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
document.getElementById("typeBtn").onclick = () => {
|
|
1094
|
+
const text = textInput.value;
|
|
1095
|
+
textInput.value = "";
|
|
1096
|
+
sendAction("/api/proton/type", { text });
|
|
1097
|
+
};
|
|
1098
|
+
document.querySelectorAll("[data-key]").forEach((button) => {
|
|
1099
|
+
button.onclick = () => sendAction("/api/proton/key", { key: button.getAttribute("data-key") });
|
|
1100
|
+
});
|
|
1101
|
+
document.getElementById("closeBtn").onclick = () => sendAction("/api/proton/close", {}, 0);
|
|
1102
|
+
window.addEventListener("resize", () => setTimeout(refresh, 250));
|
|
1103
|
+
refresh();
|
|
1104
|
+
</script>
|
|
1105
|
+
</body>
|
|
1106
|
+
</html>`;
|
|
1107
|
+
}
|
|
1108
|
+
async function handleProtonRemote(request, response, pathname, url) {
|
|
1109
|
+
if (pathname === '/api/proton/status' && request.method === 'GET') {
|
|
1110
|
+
sendJson(response, 200, await (0, registrationRuntime_1.protonLoginStatus)());
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (pathname === '/api/proton/screenshot' && request.method === 'GET') {
|
|
1114
|
+
sendJson(response, 200, await (0, protonBrowserRuntime_1.captureProtonScreenshot)(Number(url.searchParams.get('width') || 0), Number(url.searchParams.get('height') || 0)));
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
if (pathname === '/api/proton/click' && request.method === 'POST') {
|
|
1118
|
+
const body = await readJson(request);
|
|
1119
|
+
sendJson(response, 200, await (0, protonBrowserRuntime_1.clickProtonPage)(Number(body?.x || 0), Number(body?.y || 0)));
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
if (pathname === '/api/proton/type' && request.method === 'POST') {
|
|
1123
|
+
const body = await readJson(request);
|
|
1124
|
+
sendJson(response, 200, await (0, protonBrowserRuntime_1.typeProtonText)(String(body?.text || '')));
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (pathname === '/api/proton/key' && request.method === 'POST') {
|
|
1128
|
+
const body = await readJson(request);
|
|
1129
|
+
sendJson(response, 200, await (0, protonBrowserRuntime_1.pressProtonKey)(String(body?.key || 'Enter')));
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
if (pathname === '/api/proton/scroll' && request.method === 'POST') {
|
|
1133
|
+
const body = await readJson(request);
|
|
1134
|
+
sendJson(response, 200, await (0, protonBrowserRuntime_1.scrollProtonPage)(Number(body?.deltaY || 0), Number(body?.x || 0), Number(body?.y || 0)));
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
if (pathname === '/api/proton/navigate' && request.method === 'POST') {
|
|
1138
|
+
const body = await readJson(request);
|
|
1139
|
+
sendJson(response, 200, await (0, protonBrowserRuntime_1.navigateProton)(String(body?.url || '')));
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
if (pathname === '/api/proton/close' && request.method === 'POST') {
|
|
1143
|
+
sendJson(response, 200, await (0, registrationRuntime_1.protonClose)());
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
sendJson(response, 404, { error: 'Not found' });
|
|
1147
|
+
}
|
|
1148
|
+
async function serveStatic(response, pathname) {
|
|
1149
|
+
const dist = path_1.default.join(process.cwd(), 'dist-web');
|
|
1150
|
+
const requested = pathname === '/' ? '/index.html' : pathname;
|
|
1151
|
+
const filePath = path_1.default.normalize(path_1.default.join(dist, requested));
|
|
1152
|
+
if (!filePath.startsWith(dist)) {
|
|
1153
|
+
response.writeHead(403);
|
|
1154
|
+
response.end();
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
const data = await fs_1.promises.readFile(filePath);
|
|
1159
|
+
const ext = path_1.default.extname(filePath);
|
|
1160
|
+
const contentType = ext === '.html' ? 'text/html; charset=utf-8'
|
|
1161
|
+
: ext === '.js' ? 'text/javascript; charset=utf-8'
|
|
1162
|
+
: ext === '.css' ? 'text/css; charset=utf-8'
|
|
1163
|
+
: ext === '.svg' ? 'image/svg+xml'
|
|
1164
|
+
: ext === '.png' ? 'image/png'
|
|
1165
|
+
: 'application/octet-stream';
|
|
1166
|
+
response.writeHead(200, { 'Content-Type': contentType });
|
|
1167
|
+
response.end(data);
|
|
1168
|
+
}
|
|
1169
|
+
catch {
|
|
1170
|
+
const indexPath = path_1.default.join(dist, 'index.html');
|
|
1171
|
+
try {
|
|
1172
|
+
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
1173
|
+
response.end(await fs_1.promises.readFile(indexPath));
|
|
1174
|
+
}
|
|
1175
|
+
catch {
|
|
1176
|
+
sendJson(response, 404, { error: 'Web build not found. Run npm run build:web first.' });
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
async function route(request, response) {
|
|
1181
|
+
const url = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
|
|
1182
|
+
if (url.pathname === '/healthz') {
|
|
1183
|
+
sendJson(response, 200, {
|
|
1184
|
+
ok: true,
|
|
1185
|
+
version: packageVersion(),
|
|
1186
|
+
mode: serveStaticAssets ? 'fullstack' : 'backend-cli',
|
|
1187
|
+
static: serveStaticAssets
|
|
1188
|
+
});
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (url.pathname.startsWith('/api/auth/')) {
|
|
1192
|
+
await handleAuth(request, response, url.pathname);
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
if (url.pathname === '/proton-login' && request.method === 'GET') {
|
|
1196
|
+
const user = getUser(request);
|
|
1197
|
+
if (!user) {
|
|
1198
|
+
sendHtml(response, 401, '<!doctype html><title>Unauthorized</title><body>Unauthorized</body>');
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
sendHtml(response, 200, protonLoginPageHtml());
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
if (url.pathname === '/api/events') {
|
|
1205
|
+
const user = getUser(request);
|
|
1206
|
+
if (!user) {
|
|
1207
|
+
sendJson(response, 401, { error: 'Unauthorized' });
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
response.writeHead(200, {
|
|
1211
|
+
'Content-Type': 'text/event-stream',
|
|
1212
|
+
'Cache-Control': 'no-store',
|
|
1213
|
+
Connection: 'keep-alive'
|
|
1214
|
+
});
|
|
1215
|
+
response.write('data: {"channel":"connected","args":[]}\n\n');
|
|
1216
|
+
sseClients.add(response);
|
|
1217
|
+
request.on('close', () => sseClients.delete(response));
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (url.pathname === '/api/ipc' && request.method === 'POST') {
|
|
1221
|
+
const user = getUser(request);
|
|
1222
|
+
if (!user) {
|
|
1223
|
+
sendJson(response, 401, { error: 'Unauthorized' });
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
const body = await readJson(request);
|
|
1227
|
+
const method = String(body?.method || '');
|
|
1228
|
+
const args = Array.isArray(body?.args) ? body.args : [];
|
|
1229
|
+
const result = await handleIpc(method, args, user);
|
|
1230
|
+
sendJson(response, 200, result);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (url.pathname.startsWith('/api/proton/')) {
|
|
1234
|
+
const user = getUser(request);
|
|
1235
|
+
if (!user) {
|
|
1236
|
+
sendJson(response, 401, { error: 'Unauthorized' });
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
await handleProtonRemote(request, response, url.pathname, url);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
if (url.pathname.startsWith('/api/')) {
|
|
1243
|
+
sendJson(response, 404, { error: 'Not found' });
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
if (serveStaticAssets) {
|
|
1247
|
+
await serveStatic(response, url.pathname);
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
sendJson(response, 404, {
|
|
1251
|
+
error: 'Frontend static serving is disabled because this process is running in backend CLI mode.'
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
async function main() {
|
|
1255
|
+
await store.load();
|
|
1256
|
+
void startAutoProxyRuntimes();
|
|
1257
|
+
void startBackendAutoRefreshRuntimes();
|
|
1258
|
+
const port = Number(process.env.PORT || 4010);
|
|
1259
|
+
const host = process.env.HOST || '127.0.0.1';
|
|
1260
|
+
const server = http_1.default.createServer((request, response) => {
|
|
1261
|
+
route(request, response).catch((error) => {
|
|
1262
|
+
console.error('[Server] Request failed:', error);
|
|
1263
|
+
sendJson(response, 500, { error: error instanceof Error ? error.message : 'Internal server error' });
|
|
1264
|
+
});
|
|
1265
|
+
});
|
|
1266
|
+
server.listen(port, host, () => {
|
|
1267
|
+
const mode = serveStaticAssets ? 'fullstack web/API' : 'backend CLI API';
|
|
1268
|
+
void startDashboardTunnelIfConfigured();
|
|
1269
|
+
console.log(`[Server] Krouter ${mode} đang chạy tại http://${host}:${port}`);
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
void main();
|