@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,724 @@
|
|
|
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.openProtonLogin = openProtonLogin;
|
|
7
|
+
exports.getProtonLoginStatus = getProtonLoginStatus;
|
|
8
|
+
exports.closeProtonWindow = closeProtonWindow;
|
|
9
|
+
exports.captureProtonScreenshot = captureProtonScreenshot;
|
|
10
|
+
exports.clickProtonPage = clickProtonPage;
|
|
11
|
+
exports.typeProtonText = typeProtonText;
|
|
12
|
+
exports.pressProtonKey = pressProtonKey;
|
|
13
|
+
exports.scrollProtonPage = scrollProtonPage;
|
|
14
|
+
exports.navigateProton = navigateProton;
|
|
15
|
+
exports.waitProtonOtp = waitProtonOtp;
|
|
16
|
+
const child_process_1 = require("child_process");
|
|
17
|
+
const fs_1 = require("fs");
|
|
18
|
+
const promises_1 = require("fs/promises");
|
|
19
|
+
const net_1 = __importDefault(require("net"));
|
|
20
|
+
const os_1 = __importDefault(require("os"));
|
|
21
|
+
const path_1 = __importDefault(require("path"));
|
|
22
|
+
const runtimePaths_1 = require("../../main/runtimePaths");
|
|
23
|
+
const CHROME_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
|
|
24
|
+
const PROTON_INBOX_URL = 'https://mail.proton.me/u/0/inbox';
|
|
25
|
+
const DEFAULT_VIEWPORT = { width: 1280, height: 900 };
|
|
26
|
+
class CdpClient {
|
|
27
|
+
ws;
|
|
28
|
+
nextId = 1;
|
|
29
|
+
pending = new Map();
|
|
30
|
+
eventWaiters = new Map();
|
|
31
|
+
closed = false;
|
|
32
|
+
constructor(ws) {
|
|
33
|
+
this.ws = ws;
|
|
34
|
+
addWsListener(ws, 'message', (event) => void this.handleMessage(event));
|
|
35
|
+
addWsListener(ws, 'close', () => this.handleClose());
|
|
36
|
+
addWsListener(ws, 'error', (event) => this.handleError(event));
|
|
37
|
+
}
|
|
38
|
+
static async connect(url) {
|
|
39
|
+
const WsCtor = globalThis.WebSocket || require('undici').WebSocket;
|
|
40
|
+
const ws = new WsCtor(url);
|
|
41
|
+
await waitForWsOpen(ws);
|
|
42
|
+
return new CdpClient(ws);
|
|
43
|
+
}
|
|
44
|
+
isClosed() {
|
|
45
|
+
return this.closed;
|
|
46
|
+
}
|
|
47
|
+
close() {
|
|
48
|
+
this.closed = true;
|
|
49
|
+
try {
|
|
50
|
+
this.ws.close();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Ignore close races.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
send(method, params, timeoutMs = 15000) {
|
|
57
|
+
if (this.closed)
|
|
58
|
+
return Promise.reject(new Error('CDP connection is closed'));
|
|
59
|
+
const id = this.nextId++;
|
|
60
|
+
const payload = JSON.stringify({ id, method, params: params || {} });
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const timeout = setTimeout(() => {
|
|
63
|
+
this.pending.delete(id);
|
|
64
|
+
reject(new Error(`CDP command timed out: ${method}`));
|
|
65
|
+
}, timeoutMs);
|
|
66
|
+
this.pending.set(id, { resolve, reject, timeout });
|
|
67
|
+
this.ws.send(payload);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
waitForEvent(method, timeoutMs) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const timeout = setTimeout(() => {
|
|
73
|
+
const waiters = this.eventWaiters.get(method) || [];
|
|
74
|
+
this.eventWaiters.set(method, waiters.filter((waiter) => waiter !== done));
|
|
75
|
+
reject(new Error(`Timed out waiting for ${method}`));
|
|
76
|
+
}, timeoutMs);
|
|
77
|
+
const done = (params) => {
|
|
78
|
+
clearTimeout(timeout);
|
|
79
|
+
resolve(params);
|
|
80
|
+
};
|
|
81
|
+
const waiters = this.eventWaiters.get(method) || [];
|
|
82
|
+
waiters.push(done);
|
|
83
|
+
this.eventWaiters.set(method, waiters);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async handleMessage(event) {
|
|
87
|
+
const raw = await messageText(event);
|
|
88
|
+
if (!raw)
|
|
89
|
+
return;
|
|
90
|
+
let message;
|
|
91
|
+
try {
|
|
92
|
+
message = JSON.parse(raw);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (message.id) {
|
|
98
|
+
const pending = this.pending.get(message.id);
|
|
99
|
+
if (!pending)
|
|
100
|
+
return;
|
|
101
|
+
this.pending.delete(message.id);
|
|
102
|
+
clearTimeout(pending.timeout);
|
|
103
|
+
if (message.error)
|
|
104
|
+
pending.reject(new Error(message.error.message || 'CDP command failed'));
|
|
105
|
+
else
|
|
106
|
+
pending.resolve(message.result);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (message.method) {
|
|
110
|
+
const waiters = this.eventWaiters.get(message.method) || [];
|
|
111
|
+
this.eventWaiters.delete(message.method);
|
|
112
|
+
for (const waiter of waiters)
|
|
113
|
+
waiter(message.params);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
handleClose() {
|
|
117
|
+
this.closed = true;
|
|
118
|
+
for (const pending of this.pending.values()) {
|
|
119
|
+
clearTimeout(pending.timeout);
|
|
120
|
+
pending.reject(new Error('CDP connection closed'));
|
|
121
|
+
}
|
|
122
|
+
this.pending.clear();
|
|
123
|
+
this.eventWaiters.clear();
|
|
124
|
+
}
|
|
125
|
+
handleError(event) {
|
|
126
|
+
const message = event?.message || event?.error?.message || 'CDP websocket error';
|
|
127
|
+
for (const pending of this.pending.values()) {
|
|
128
|
+
clearTimeout(pending.timeout);
|
|
129
|
+
pending.reject(new Error(message));
|
|
130
|
+
}
|
|
131
|
+
this.pending.clear();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
let browserProcess = null;
|
|
135
|
+
let client = null;
|
|
136
|
+
let debugPort = 0;
|
|
137
|
+
let activeProxy = '';
|
|
138
|
+
let viewport = { ...DEFAULT_VIEWPORT };
|
|
139
|
+
let otpQueue = Promise.resolve();
|
|
140
|
+
function addWsListener(ws, event, listener) {
|
|
141
|
+
if (typeof ws.addEventListener === 'function') {
|
|
142
|
+
ws.addEventListener(event, listener);
|
|
143
|
+
}
|
|
144
|
+
else if (typeof ws.on === 'function') {
|
|
145
|
+
ws.on(event, listener);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function messageText(event) {
|
|
149
|
+
const data = event?.data ?? event;
|
|
150
|
+
if (typeof data === 'string')
|
|
151
|
+
return data;
|
|
152
|
+
if (Buffer.isBuffer(data))
|
|
153
|
+
return data.toString('utf8');
|
|
154
|
+
if (data instanceof ArrayBuffer)
|
|
155
|
+
return Buffer.from(data).toString('utf8');
|
|
156
|
+
if (ArrayBuffer.isView(data))
|
|
157
|
+
return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('utf8');
|
|
158
|
+
if (data && typeof data.text === 'function')
|
|
159
|
+
return await data.text();
|
|
160
|
+
if (data === undefined || data === null)
|
|
161
|
+
return '';
|
|
162
|
+
return Buffer.from(data).toString('utf8');
|
|
163
|
+
}
|
|
164
|
+
function waitForWsOpen(ws) {
|
|
165
|
+
if (ws.readyState === 1)
|
|
166
|
+
return Promise.resolve();
|
|
167
|
+
return new Promise((resolve, reject) => {
|
|
168
|
+
const timeout = setTimeout(() => reject(new Error('Timed out connecting to Chromium DevTools')), 15000);
|
|
169
|
+
addWsListener(ws, 'open', () => {
|
|
170
|
+
clearTimeout(timeout);
|
|
171
|
+
resolve();
|
|
172
|
+
});
|
|
173
|
+
addWsListener(ws, 'error', (event) => {
|
|
174
|
+
clearTimeout(timeout);
|
|
175
|
+
reject(new Error(event?.message || 'Failed to connect to Chromium DevTools'));
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function sleep(ms) {
|
|
180
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
181
|
+
}
|
|
182
|
+
function resolveSettingsProxy(explicit) {
|
|
183
|
+
const value = (explicit || '').trim();
|
|
184
|
+
if (value)
|
|
185
|
+
return value;
|
|
186
|
+
return (process.env.HTTPS_PROXY ||
|
|
187
|
+
process.env.https_proxy ||
|
|
188
|
+
process.env.HTTP_PROXY ||
|
|
189
|
+
process.env.http_proxy ||
|
|
190
|
+
'').trim();
|
|
191
|
+
}
|
|
192
|
+
function profileDir() {
|
|
193
|
+
const dir = path_1.default.join((0, runtimePaths_1.getRuntimeUserDataPath)(), 'proton-browser');
|
|
194
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
195
|
+
return dir;
|
|
196
|
+
}
|
|
197
|
+
async function getFreePort() {
|
|
198
|
+
return new Promise((resolve, reject) => {
|
|
199
|
+
const server = net_1.default.createServer();
|
|
200
|
+
server.once('error', reject);
|
|
201
|
+
server.listen(0, '127.0.0.1', () => {
|
|
202
|
+
const address = server.address();
|
|
203
|
+
const port = typeof address === 'object' && address ? address.port : 0;
|
|
204
|
+
server.close(() => resolve(port));
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async function canAccess(filePath) {
|
|
209
|
+
try {
|
|
210
|
+
await (0, promises_1.access)(filePath);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async function findBrowserExecutable() {
|
|
218
|
+
const explicit = (process.env.PROTON_BROWSER_PATH || '').trim();
|
|
219
|
+
if (explicit) {
|
|
220
|
+
if (await canAccess(explicit))
|
|
221
|
+
return explicit;
|
|
222
|
+
throw new Error(`PROTON_BROWSER_PATH does not exist: ${explicit}`);
|
|
223
|
+
}
|
|
224
|
+
const candidates = [];
|
|
225
|
+
if (process.platform === 'win32') {
|
|
226
|
+
const roots = [
|
|
227
|
+
process.env.PROGRAMFILES,
|
|
228
|
+
process.env['PROGRAMFILES(X86)'],
|
|
229
|
+
process.env.LOCALAPPDATA
|
|
230
|
+
].filter((value) => Boolean(value));
|
|
231
|
+
for (const root of roots) {
|
|
232
|
+
candidates.push(path_1.default.join(root, 'Google', 'Chrome', 'Application', 'chrome.exe'));
|
|
233
|
+
candidates.push(path_1.default.join(root, 'Microsoft', 'Edge', 'Application', 'msedge.exe'));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (process.platform === 'darwin') {
|
|
237
|
+
candidates.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome');
|
|
238
|
+
candidates.push('/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge');
|
|
239
|
+
candidates.push('/Applications/Chromium.app/Contents/MacOS/Chromium');
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
candidates.push('/usr/bin/chromium');
|
|
243
|
+
candidates.push('/usr/bin/chromium-browser');
|
|
244
|
+
candidates.push('/usr/bin/google-chrome');
|
|
245
|
+
candidates.push('/usr/bin/google-chrome-stable');
|
|
246
|
+
candidates.push('/usr/bin/microsoft-edge');
|
|
247
|
+
}
|
|
248
|
+
const pathNames = process.platform === 'win32'
|
|
249
|
+
? ['chrome.exe', 'msedge.exe', 'chromium.exe']
|
|
250
|
+
: ['chromium', 'chromium-browser', 'google-chrome', 'google-chrome-stable', 'microsoft-edge'];
|
|
251
|
+
for (const dir of (process.env.PATH || '').split(path_1.default.delimiter)) {
|
|
252
|
+
for (const name of pathNames)
|
|
253
|
+
candidates.push(path_1.default.join(dir, name));
|
|
254
|
+
}
|
|
255
|
+
for (const candidate of candidates) {
|
|
256
|
+
if (candidate && (0, fs_1.existsSync)(candidate))
|
|
257
|
+
return candidate;
|
|
258
|
+
}
|
|
259
|
+
throw new Error('No Chrome/Chromium executable found. Set PROTON_BROWSER_PATH or install chromium on the server.');
|
|
260
|
+
}
|
|
261
|
+
async function fetchJson(url, init) {
|
|
262
|
+
const response = await fetch(url, init);
|
|
263
|
+
if (!response.ok)
|
|
264
|
+
throw new Error(`${init?.method || 'GET'} ${url} failed with ${response.status}`);
|
|
265
|
+
return await response.json();
|
|
266
|
+
}
|
|
267
|
+
async function waitForDevtools(port) {
|
|
268
|
+
for (let attempt = 0; attempt < 80; attempt++) {
|
|
269
|
+
try {
|
|
270
|
+
await fetchJson(`http://127.0.0.1:${port}/json/version`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
await sleep(250);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
throw new Error('Chromium DevTools endpoint did not become ready');
|
|
278
|
+
}
|
|
279
|
+
async function getPageWebSocketUrl(port) {
|
|
280
|
+
let targets = await fetchJson(`http://127.0.0.1:${port}/json/list`);
|
|
281
|
+
let target = targets.find((item) => item.type === 'page' && /proton\.me/i.test(item.url || ''))
|
|
282
|
+
|| targets.find((item) => item.type === 'page');
|
|
283
|
+
if (!target?.webSocketDebuggerUrl) {
|
|
284
|
+
const createUrl = `http://127.0.0.1:${port}/json/new?${encodeURIComponent(PROTON_INBOX_URL)}`;
|
|
285
|
+
try {
|
|
286
|
+
await fetchJson(createUrl, { method: 'PUT' });
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
await fetchJson(createUrl);
|
|
290
|
+
}
|
|
291
|
+
targets = await fetchJson(`http://127.0.0.1:${port}/json/list`);
|
|
292
|
+
target = targets.find((item) => item.type === 'page' && /proton\.me/i.test(item.url || ''))
|
|
293
|
+
|| targets.find((item) => item.type === 'page');
|
|
294
|
+
}
|
|
295
|
+
if (!target?.webSocketDebuggerUrl)
|
|
296
|
+
throw new Error('Could not find a Chromium page target');
|
|
297
|
+
return target.webSocketDebuggerUrl;
|
|
298
|
+
}
|
|
299
|
+
function browserIsRunning() {
|
|
300
|
+
return Boolean(browserProcess && browserProcess.exitCode === null && !browserProcess.killed);
|
|
301
|
+
}
|
|
302
|
+
async function launchBrowser(proxy) {
|
|
303
|
+
const resolvedProxy = resolveSettingsProxy(proxy);
|
|
304
|
+
if (browserIsRunning() && activeProxy === resolvedProxy)
|
|
305
|
+
return;
|
|
306
|
+
await closeProtonWindow();
|
|
307
|
+
debugPort = Number(process.env.PROTON_BROWSER_DEBUG_PORT || 0) || await getFreePort();
|
|
308
|
+
activeProxy = resolvedProxy;
|
|
309
|
+
const executable = await findBrowserExecutable();
|
|
310
|
+
const headless = process.env.PROTON_BROWSER_HEADLESS !== 'false';
|
|
311
|
+
const noSandbox = process.env.PROTON_BROWSER_NO_SANDBOX === 'true'
|
|
312
|
+
|| (process.env.PROTON_BROWSER_NO_SANDBOX !== 'false'
|
|
313
|
+
&& process.platform !== 'win32'
|
|
314
|
+
&& (typeof process.getuid !== 'function' || process.getuid() === 0));
|
|
315
|
+
const args = [
|
|
316
|
+
`--remote-debugging-port=${debugPort}`,
|
|
317
|
+
'--remote-debugging-address=127.0.0.1',
|
|
318
|
+
'--remote-allow-origins=*',
|
|
319
|
+
`--user-data-dir=${profileDir()}`,
|
|
320
|
+
'--no-first-run',
|
|
321
|
+
'--no-default-browser-check',
|
|
322
|
+
'--disable-dev-shm-usage',
|
|
323
|
+
'--disable-gpu',
|
|
324
|
+
'--disable-gpu-sandbox',
|
|
325
|
+
'--disable-extensions',
|
|
326
|
+
'--disable-background-timer-throttling',
|
|
327
|
+
'--disable-backgrounding-occluded-windows',
|
|
328
|
+
'--disable-renderer-backgrounding',
|
|
329
|
+
`--window-size=${viewport.width},${viewport.height}`,
|
|
330
|
+
`--user-agent=${CHROME_UA}`
|
|
331
|
+
];
|
|
332
|
+
if (headless)
|
|
333
|
+
args.push('--headless=new');
|
|
334
|
+
if (noSandbox)
|
|
335
|
+
args.push('--no-sandbox');
|
|
336
|
+
if (resolvedProxy)
|
|
337
|
+
args.push(`--proxy-server=${resolvedProxy}`);
|
|
338
|
+
args.push(PROTON_INBOX_URL);
|
|
339
|
+
const proc = (0, child_process_1.spawn)(executable, args, {
|
|
340
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
341
|
+
windowsHide: true
|
|
342
|
+
});
|
|
343
|
+
browserProcess = proc;
|
|
344
|
+
proc.stderr?.on('data', (chunk) => {
|
|
345
|
+
const line = chunk.toString('utf8').trim();
|
|
346
|
+
if (line)
|
|
347
|
+
console.warn(`[ProtonBrowser] ${line}`);
|
|
348
|
+
});
|
|
349
|
+
proc.on('exit', () => {
|
|
350
|
+
browserProcess = null;
|
|
351
|
+
client?.close();
|
|
352
|
+
client = null;
|
|
353
|
+
});
|
|
354
|
+
await waitForDevtools(debugPort);
|
|
355
|
+
}
|
|
356
|
+
async function ensureClient(proxy) {
|
|
357
|
+
await launchBrowser(proxy);
|
|
358
|
+
if (!client || client.isClosed()) {
|
|
359
|
+
const wsUrl = await getPageWebSocketUrl(debugPort);
|
|
360
|
+
client = await CdpClient.connect(wsUrl);
|
|
361
|
+
await client.send('Page.enable');
|
|
362
|
+
await client.send('Runtime.enable');
|
|
363
|
+
await setViewport(viewport.width, viewport.height);
|
|
364
|
+
}
|
|
365
|
+
return client;
|
|
366
|
+
}
|
|
367
|
+
async function setViewport(width, height) {
|
|
368
|
+
viewport = {
|
|
369
|
+
width: Math.max(640, Math.min(Math.floor(width || DEFAULT_VIEWPORT.width), 1920)),
|
|
370
|
+
height: Math.max(480, Math.min(Math.floor(height || DEFAULT_VIEWPORT.height), 1400))
|
|
371
|
+
};
|
|
372
|
+
if (!client || client.isClosed())
|
|
373
|
+
return;
|
|
374
|
+
await client.send('Emulation.setDeviceMetricsOverride', {
|
|
375
|
+
width: viewport.width,
|
|
376
|
+
height: viewport.height,
|
|
377
|
+
deviceScaleFactor: 1,
|
|
378
|
+
mobile: false
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
async function evaluate(expression, awaitPromise = false) {
|
|
382
|
+
const cdp = await ensureClient();
|
|
383
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
384
|
+
expression,
|
|
385
|
+
awaitPromise,
|
|
386
|
+
returnByValue: true,
|
|
387
|
+
userGesture: true
|
|
388
|
+
});
|
|
389
|
+
if (result?.exceptionDetails) {
|
|
390
|
+
throw new Error(result.exceptionDetails.text || 'Chromium evaluation failed');
|
|
391
|
+
}
|
|
392
|
+
const remote = result?.result;
|
|
393
|
+
return remote?.value;
|
|
394
|
+
}
|
|
395
|
+
async function currentUrl() {
|
|
396
|
+
try {
|
|
397
|
+
return await evaluate('location.href');
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
return '';
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async function loadAndWait(url = PROTON_INBOX_URL, timeoutMs = 30000) {
|
|
404
|
+
const cdp = await ensureClient();
|
|
405
|
+
const load = cdp.waitForEvent('Page.loadEventFired', timeoutMs).catch(() => undefined);
|
|
406
|
+
await cdp.send('Page.navigate', { url });
|
|
407
|
+
await load;
|
|
408
|
+
const started = Date.now();
|
|
409
|
+
while (Date.now() - started < timeoutMs) {
|
|
410
|
+
try {
|
|
411
|
+
const ready = await evaluate('document.readyState');
|
|
412
|
+
if (ready === 'interactive' || ready === 'complete')
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// Keep polling until the page is usable.
|
|
417
|
+
}
|
|
418
|
+
await sleep(250);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
async function checkLoggedIn() {
|
|
422
|
+
const url = await currentUrl();
|
|
423
|
+
if (/account\.proton\.me/i.test(url) || /\/(login|authorize|switch)/i.test(url))
|
|
424
|
+
return false;
|
|
425
|
+
if (!/mail\.proton\.me\/u\//i.test(url))
|
|
426
|
+
return false;
|
|
427
|
+
try {
|
|
428
|
+
return Boolean(await evaluate(`(() => {
|
|
429
|
+
if (document.querySelector('input[type="password"], #password')) return false
|
|
430
|
+
const sels = ['[data-testid="message-list"]','.items-column-list','[data-shortcut-target="item-container"]','main [role="main"]']
|
|
431
|
+
return sels.some(s => document.querySelector(s)) || /\\/u\\//.test(location.pathname)
|
|
432
|
+
})()`));
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return /mail\.proton\.me\/u\//i.test(url);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
async function openProtonLogin(proxy) {
|
|
439
|
+
try {
|
|
440
|
+
await ensureClient(proxy);
|
|
441
|
+
if (!/proton\.me/i.test(await currentUrl()))
|
|
442
|
+
await loadAndWait(PROTON_INBOX_URL);
|
|
443
|
+
await sleep(1200);
|
|
444
|
+
const loggedIn = await checkLoggedIn();
|
|
445
|
+
return { success: true, loggedIn, loginUrl: '/proton-login', url: await currentUrl() };
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
return {
|
|
449
|
+
success: false,
|
|
450
|
+
loggedIn: false,
|
|
451
|
+
loginUrl: '/proton-login',
|
|
452
|
+
error: error instanceof Error ? error.message : String(error)
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async function getProtonLoginStatus(proxy) {
|
|
457
|
+
try {
|
|
458
|
+
await ensureClient(proxy);
|
|
459
|
+
await sleep(600);
|
|
460
|
+
return { loggedIn: await checkLoggedIn(), loginUrl: '/proton-login', url: await currentUrl() };
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
return { loggedIn: false, loginUrl: '/proton-login', error: error instanceof Error ? error.message : String(error) };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
async function closeProtonWindow() {
|
|
467
|
+
client?.close();
|
|
468
|
+
client = null;
|
|
469
|
+
if (browserProcess && browserProcess.exitCode === null && !browserProcess.killed) {
|
|
470
|
+
browserProcess.kill();
|
|
471
|
+
}
|
|
472
|
+
browserProcess = null;
|
|
473
|
+
activeProxy = '';
|
|
474
|
+
}
|
|
475
|
+
async function captureProtonScreenshot(width, height) {
|
|
476
|
+
try {
|
|
477
|
+
const cdp = await ensureClient();
|
|
478
|
+
await setViewport(width || viewport.width, height || viewport.height);
|
|
479
|
+
await cdp.send('Page.bringToFront');
|
|
480
|
+
let result;
|
|
481
|
+
try {
|
|
482
|
+
result = await cdp.send('Page.captureScreenshot', {
|
|
483
|
+
format: 'jpeg',
|
|
484
|
+
quality: 82,
|
|
485
|
+
fromSurface: true,
|
|
486
|
+
captureBeyondViewport: false
|
|
487
|
+
}, 12000);
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
result = await cdp.send('Page.captureScreenshot', {
|
|
491
|
+
format: 'jpeg',
|
|
492
|
+
quality: 82,
|
|
493
|
+
fromSurface: false,
|
|
494
|
+
captureBeyondViewport: false
|
|
495
|
+
}, 12000);
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
success: true,
|
|
499
|
+
dataUrl: `data:image/jpeg;base64,${result.data}`,
|
|
500
|
+
width: viewport.width,
|
|
501
|
+
height: viewport.height,
|
|
502
|
+
loggedIn: await checkLoggedIn(),
|
|
503
|
+
url: await currentUrl()
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async function clickProtonPage(x, y) {
|
|
511
|
+
try {
|
|
512
|
+
const cdp = await ensureClient();
|
|
513
|
+
const safeX = Math.max(0, Math.min(Number(x) || 0, viewport.width));
|
|
514
|
+
const safeY = Math.max(0, Math.min(Number(y) || 0, viewport.height));
|
|
515
|
+
await cdp.send('Input.dispatchMouseEvent', { type: 'mousePressed', x: safeX, y: safeY, button: 'left', clickCount: 1 });
|
|
516
|
+
await cdp.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x: safeX, y: safeY, button: 'left', clickCount: 1 });
|
|
517
|
+
return { success: true };
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
async function typeProtonText(text) {
|
|
524
|
+
try {
|
|
525
|
+
const cdp = await ensureClient();
|
|
526
|
+
await cdp.send('Input.insertText', { text });
|
|
527
|
+
return { success: true };
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function keyParams(key) {
|
|
534
|
+
const normalized = key.trim();
|
|
535
|
+
const aliases = {
|
|
536
|
+
Enter: { key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13 },
|
|
537
|
+
Tab: { key: 'Tab', code: 'Tab', windowsVirtualKeyCode: 9 },
|
|
538
|
+
Backspace: { key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 },
|
|
539
|
+
Escape: { key: 'Escape', code: 'Escape', windowsVirtualKeyCode: 27 },
|
|
540
|
+
ArrowUp: { key: 'ArrowUp', code: 'ArrowUp', windowsVirtualKeyCode: 38 },
|
|
541
|
+
ArrowDown: { key: 'ArrowDown', code: 'ArrowDown', windowsVirtualKeyCode: 40 },
|
|
542
|
+
ArrowLeft: { key: 'ArrowLeft', code: 'ArrowLeft', windowsVirtualKeyCode: 37 },
|
|
543
|
+
ArrowRight: { key: 'ArrowRight', code: 'ArrowRight', windowsVirtualKeyCode: 39 }
|
|
544
|
+
};
|
|
545
|
+
return aliases[normalized] || { key: normalized, code: normalized, windowsVirtualKeyCode: 0 };
|
|
546
|
+
}
|
|
547
|
+
async function pressProtonKey(key) {
|
|
548
|
+
try {
|
|
549
|
+
const cdp = await ensureClient();
|
|
550
|
+
const params = keyParams(key);
|
|
551
|
+
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', ...params });
|
|
552
|
+
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', ...params });
|
|
553
|
+
return { success: true };
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async function scrollProtonPage(deltaY, x, y) {
|
|
560
|
+
try {
|
|
561
|
+
const cdp = await ensureClient();
|
|
562
|
+
await cdp.send('Input.dispatchMouseEvent', {
|
|
563
|
+
type: 'mouseWheel',
|
|
564
|
+
x: Math.max(0, Math.min(Number(x) || viewport.width / 2, viewport.width)),
|
|
565
|
+
y: Math.max(0, Math.min(Number(y) || viewport.height / 2, viewport.height)),
|
|
566
|
+
deltaX: 0,
|
|
567
|
+
deltaY: Number(deltaY) || 0
|
|
568
|
+
});
|
|
569
|
+
return { success: true };
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async function navigateProton(url) {
|
|
576
|
+
try {
|
|
577
|
+
const target = url && /^https:\/\/(?:mail|account)\.proton\.me\//i.test(url) ? url : PROTON_INBOX_URL;
|
|
578
|
+
await loadAndWait(target);
|
|
579
|
+
return { success: true, loggedIn: await checkLoggedIn(), url: await currentUrl() };
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function buildScanScript(address) {
|
|
586
|
+
const addrFull = JSON.stringify(address.trim().toLowerCase());
|
|
587
|
+
return `(async () => {
|
|
588
|
+
const addrFull = ${addrFull};
|
|
589
|
+
const extractCode = (t) => { const m = (t||'').match(/\\b\\d{6}\\b/g); return m ? m[m.length-1] : ''; };
|
|
590
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
591
|
+
const fire = (el, type) => el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window }));
|
|
592
|
+
const readRecipients = () => {
|
|
593
|
+
const set = new Set();
|
|
594
|
+
document.querySelectorAll('a[href^="mailto:"]').forEach((a) => {
|
|
595
|
+
const m = (a.getAttribute('href') || '').replace(/^mailto:/i, '').trim().toLowerCase();
|
|
596
|
+
if (m.indexOf('@') > 0) set.add(m);
|
|
597
|
+
});
|
|
598
|
+
document.querySelectorAll('[data-testid="recipient-label"], bdi.message-recipient-item-label').forEach((el) => {
|
|
599
|
+
const t = (el.innerText || '').trim().toLowerCase();
|
|
600
|
+
if (t.indexOf('@') > 0) set.add(t);
|
|
601
|
+
});
|
|
602
|
+
document.querySelectorAll('[data-testid^="recipients:item-"]').forEach((el) => {
|
|
603
|
+
const t = (el.getAttribute('data-testid') || '').replace('recipients:item-', '').trim().toLowerCase();
|
|
604
|
+
if (t.indexOf('@') > 0) set.add(t);
|
|
605
|
+
});
|
|
606
|
+
return set;
|
|
607
|
+
};
|
|
608
|
+
const SENDER = 'no-reply@signin.aws';
|
|
609
|
+
const senderOf = (it) => {
|
|
610
|
+
const el = it.querySelector('[data-testid="message-column:sender-address"]');
|
|
611
|
+
return el ? (el.getAttribute('title') || el.innerText || '').trim().toLowerCase() : '';
|
|
612
|
+
};
|
|
613
|
+
const openItem = (it) => {
|
|
614
|
+
let target = it.querySelector('[data-testid="message-column:subject"]')
|
|
615
|
+
|| it.querySelector('[data-testid^="message-row"]')
|
|
616
|
+
|| it.querySelector('.item-subject-wrapper, .subject, span[role="heading"]');
|
|
617
|
+
if (!target) {
|
|
618
|
+
const cand = Array.from(it.querySelectorAll('span, div'))
|
|
619
|
+
.filter((el) => !el.closest('button') && !el.querySelector('button, input') && (el.innerText || '').trim().length > 8);
|
|
620
|
+
target = cand[0] || it;
|
|
621
|
+
}
|
|
622
|
+
fire(target, 'mousedown'); fire(target, 'mouseup'); fire(target, 'click');
|
|
623
|
+
};
|
|
624
|
+
const readBody = () => {
|
|
625
|
+
let body = '';
|
|
626
|
+
const ifr = document.querySelector('iframe[data-testid="content-iframe"], iframe[title], iframe');
|
|
627
|
+
if (ifr) { try { body = (ifr.contentDocument && ifr.contentDocument.body) ? (ifr.contentDocument.body.innerText || '') : ''; } catch (e) {} }
|
|
628
|
+
if (!body) {
|
|
629
|
+
const readSels = ['[data-testid="message-content"]','.message-content','[data-testid="message-view"]','main [role="article"]','main'];
|
|
630
|
+
for (const rs of readSels) { const el = document.querySelector(rs); if (el && el.innerText) { body = el.innerText; break; } }
|
|
631
|
+
}
|
|
632
|
+
if (!body) body = document.body.innerText || '';
|
|
633
|
+
return body;
|
|
634
|
+
};
|
|
635
|
+
const listSels = ['[data-testid="message-item"]','[data-shortcut-target="item-container"]','.items-column-list [role="row"]','.item-container-wrapper','.item-container'];
|
|
636
|
+
let items = [];
|
|
637
|
+
for (const s of listSels) { const e = [...document.querySelectorAll(s)]; if (e.length) { items = e; break; } }
|
|
638
|
+
if (!items[0]) return { code: '', from: 'none', matched: false };
|
|
639
|
+
const awsItems = items.filter((it) => senderOf(it) === SENDER);
|
|
640
|
+
const candidates = (awsItems.length ? awsItems : items).slice(0, 2);
|
|
641
|
+
const results = [];
|
|
642
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
643
|
+
try {
|
|
644
|
+
openItem(candidates[i]);
|
|
645
|
+
let body = '';
|
|
646
|
+
let recipients = new Set();
|
|
647
|
+
for (let t = 0; t < 11; t++) {
|
|
648
|
+
await sleep(t === 0 ? 350 : 170);
|
|
649
|
+
body = readBody();
|
|
650
|
+
recipients = readRecipients();
|
|
651
|
+
if (extractCode(body) || (recipients.size > 0 && body.length > 30)) break;
|
|
652
|
+
}
|
|
653
|
+
const r = {
|
|
654
|
+
i,
|
|
655
|
+
hasRecip: recipients.size > 0,
|
|
656
|
+
match: recipients.has(addrFull),
|
|
657
|
+
code: extractCode(body),
|
|
658
|
+
recipText: Array.from(recipients).join(',').slice(0, 100),
|
|
659
|
+
bodySnip: body.slice(0, 100)
|
|
660
|
+
};
|
|
661
|
+
results.push(r);
|
|
662
|
+
if (r.match && r.code) return { code: r.code, from: 'body', matched: true, snippet: 'aws#' + i + ' ' + r.bodySnip };
|
|
663
|
+
} catch (e) {
|
|
664
|
+
results.push({ i, hasRecip: false, match: false, code: '', recipText: '', bodySnip: 'err=' + String(e) });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const noRecipCode = results.find((r) => !r.hasRecip && r.code);
|
|
668
|
+
if (noRecipCode) return { code: noRecipCode.code, from: 'body', matched: false, snippet: 'aws#' + noRecipCode.i + ' no-recipients; ' + noRecipCode.bodySnip };
|
|
669
|
+
const matchNoCode = results.find((r) => r.match && !r.code);
|
|
670
|
+
if (matchNoCode) return { code: '', from: 'body-nocode', matched: true, snippet: 'aws#' + matchNoCode.i + ' ' + matchNoCode.bodySnip };
|
|
671
|
+
const wrongRecip = results.find((r) => r.code && r.hasRecip && !r.match);
|
|
672
|
+
if (wrongRecip) return { code: '', from: 'wrong-recipient', matched: false, snippet: 'aws#' + wrongRecip.i + ' recipients=' + wrongRecip.recipText };
|
|
673
|
+
return { code: '', from: 'body-nocode', matched: false, snippet: 'awsItems=' + awsItems.length + '; ' + results.map((r) => '#' + r.i + (r.code ? '+code' : '-nocode') + ' r=' + (r.recipText || 'none')).join(' | ').slice(0, 170) };
|
|
674
|
+
})()`;
|
|
675
|
+
}
|
|
676
|
+
function waitProtonOtp(address, opts) {
|
|
677
|
+
const run = otpQueue.then(() => runWaitProtonOtp(address, opts), () => runWaitProtonOtp(address, opts));
|
|
678
|
+
otpQueue = run.catch(() => undefined);
|
|
679
|
+
return run;
|
|
680
|
+
}
|
|
681
|
+
async function runWaitProtonOtp(address, opts) {
|
|
682
|
+
const log = opts.log ?? (() => { });
|
|
683
|
+
await ensureClient(opts.proxy);
|
|
684
|
+
if (!(await checkLoggedIn())) {
|
|
685
|
+
throw new Error('Proton is not logged in. Open the Proton login page and complete the mailbox login first.');
|
|
686
|
+
}
|
|
687
|
+
await loadAndWait(PROTON_INBOX_URL);
|
|
688
|
+
await sleep(1500);
|
|
689
|
+
const pollMs = Math.min(Math.max(opts.intervalSec * 1000, 250), 1000);
|
|
690
|
+
const maxRetries = Math.max(1, Math.floor((opts.timeoutSec * 1000) / pollMs));
|
|
691
|
+
const script = buildScanScript(address);
|
|
692
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
693
|
+
if (opts.signal?.aborted)
|
|
694
|
+
throw new Error('Registration was cancelled');
|
|
695
|
+
if (attempt > 1 && attempt % 20 === 0) {
|
|
696
|
+
await loadAndWait(PROTON_INBOX_URL);
|
|
697
|
+
await sleep(1200);
|
|
698
|
+
}
|
|
699
|
+
try {
|
|
700
|
+
const res = await evaluate(script, true);
|
|
701
|
+
if (res && res.code && res.from === 'body') {
|
|
702
|
+
log(`[Proton] Verification code: ${res.code} (${res.matched ? 'recipient matched' : 'body fallback'})`);
|
|
703
|
+
return res.code;
|
|
704
|
+
}
|
|
705
|
+
if (res && res.from === 'wrong-recipient' && attempt % 8 === 0) {
|
|
706
|
+
log(`[Proton] Latest AWS mail is for another recipient, waiting... ${res.snippet || ''}`);
|
|
707
|
+
}
|
|
708
|
+
else if (res && res.from === 'body-nocode' && attempt % 8 === 0) {
|
|
709
|
+
log(`[Proton] ${res.matched ? 'Matched mail but no code yet' : 'No matching mail yet'}: ${res.snippet || ''}`);
|
|
710
|
+
}
|
|
711
|
+
else if (res && res.from === 'error' && attempt % 10 === 0) {
|
|
712
|
+
log(`[Proton] Scan script failed: ${res.err}`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
if (attempt % 10 === 0)
|
|
717
|
+
log(`[Proton] [${attempt}/${maxRetries}] Read failed: ${error}`);
|
|
718
|
+
}
|
|
719
|
+
if (attempt % 10 === 0)
|
|
720
|
+
log(`[Proton] [${attempt}/${maxRetries}] No verification code yet...`);
|
|
721
|
+
await sleep(pollMs);
|
|
722
|
+
}
|
|
723
|
+
throw new Error(`Timed out waiting for verification code (${opts.timeoutSec}s) on ${os_1.default.hostname()}`);
|
|
724
|
+
}
|