@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.
Files changed (61) hide show
  1. package/LICENSE +679 -0
  2. package/README.md +238 -0
  3. package/dist-web/assets/index-CM4-0adf.css +1 -0
  4. package/dist-web/assets/index-DCslvfUR.js +139 -0
  5. package/dist-web/favicon.svg +9 -0
  6. package/dist-web/icon.svg +9 -0
  7. package/dist-web/index.html +19 -0
  8. package/out-server/main/kiroAuthSync.js +249 -0
  9. package/out-server/main/kproxy/certManager.js +262 -0
  10. package/out-server/main/kproxy/index.js +254 -0
  11. package/out-server/main/kproxy/mitmProxy.js +475 -0
  12. package/out-server/main/kproxy/types.js +23 -0
  13. package/out-server/main/proxy/accountPool.js +543 -0
  14. package/out-server/main/proxy/clientConfig.js +596 -0
  15. package/out-server/main/proxy/index.js +25 -0
  16. package/out-server/main/proxy/kiroApi.js +1996 -0
  17. package/out-server/main/proxy/logger.js +407 -0
  18. package/out-server/main/proxy/modelCatalog.js +75 -0
  19. package/out-server/main/proxy/promptCacheTracker.js +301 -0
  20. package/out-server/main/proxy/proxyServer.js +3543 -0
  21. package/out-server/main/proxy/selfSignedCert.js +179 -0
  22. package/out-server/main/proxy/systemProxy.js +250 -0
  23. package/out-server/main/proxy/tokenCounter.js +164 -0
  24. package/out-server/main/proxy/toolNameRegistry.js +57 -0
  25. package/out-server/main/proxy/translator.js +1084 -0
  26. package/out-server/main/proxy/types.js +3 -0
  27. package/out-server/main/registration/browser-identity.js +184 -0
  28. package/out-server/main/registration/chainProxy.js +349 -0
  29. package/out-server/main/registration/config.js +58 -0
  30. package/out-server/main/registration/email-service.js +801 -0
  31. package/out-server/main/registration/fingerprint.js +352 -0
  32. package/out-server/main/registration/http-utils.js +148 -0
  33. package/out-server/main/registration/jwe.js +74 -0
  34. package/out-server/main/registration/names.js +142 -0
  35. package/out-server/main/registration/proton-mail-window.js +339 -0
  36. package/out-server/main/registration/registrar.js +1715 -0
  37. package/out-server/main/registration/tlsClientPool.js +70 -0
  38. package/out-server/main/registration/xxtea.js +161 -0
  39. package/out-server/main/runtimePaths.js +19 -0
  40. package/out-server/main/utils/redact.js +95 -0
  41. package/out-server/server/index.js +1272 -0
  42. package/out-server/server/services/accountExtras.js +105 -0
  43. package/out-server/server/services/accountProfileHydration.js +95 -0
  44. package/out-server/server/services/authFlows.js +509 -0
  45. package/out-server/server/services/dashboardTunnel.js +315 -0
  46. package/out-server/server/services/diagnostics.js +326 -0
  47. package/out-server/server/services/kiroAccounts.js +431 -0
  48. package/out-server/server/services/kiroSettings.js +260 -0
  49. package/out-server/server/services/kproxyRuntime.js +264 -0
  50. package/out-server/server/services/localKiroCredentials.js +320 -0
  51. package/out-server/server/services/machineIdRuntime.js +327 -0
  52. package/out-server/server/services/protonBrowserRuntime.js +724 -0
  53. package/out-server/server/services/proxyRuntime.js +523 -0
  54. package/out-server/server/services/registrationRuntime.js +106 -0
  55. package/out-server/server/store.js +266 -0
  56. package/package.json +113 -0
  57. package/resources/tls-client-xgo-1.14.0-windows-amd64.dll +0 -0
  58. package/scripts/kiro-manager-cli.cjs +3 -0
  59. package/scripts/krouter-cli.cjs +509 -0
  60. package/src/renderer/src/assets/krouter-logo.svg +11 -0
  61. package/src/renderer/src/assets/krouter-mark.svg +9 -0
@@ -0,0 +1,801 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ProtonWebviewService = exports.TingamefiMailService = exports.TempMailPlusService = exports.MoEmailService = void 0;
37
+ exports.extractCode = extractCode;
38
+ exports.parseOutlookLines = parseOutlookLines;
39
+ exports.refreshOutlookToken = refreshOutlookToken;
40
+ exports.getInboxCount = getInboxCount;
41
+ exports.waitForOTP = waitForOTP;
42
+ const tls = __importStar(require("tls"));
43
+ const undici_1 = require("undici");
44
+ const systemProxy_1 = require("../proxy/systemProxy");
45
+ const names_1 = require("./names");
46
+ function getRegistrationProxyUrl() {
47
+ return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || (0, systemProxy_1.getSystemProxy)() || undefined;
48
+ }
49
+ async function proxyFetch(url, options) {
50
+ const agent = (0, systemProxy_1.safeCreateProxyAgent)(getRegistrationProxyUrl());
51
+ if (agent) {
52
+ return await (0, undici_1.fetch)(url, { ...options, dispatcher: agent });
53
+ }
54
+ return await fetch(url, options);
55
+ }
56
+ // ============ 验证码提取 ============
57
+ const OTP_PATTERN = /\b(\d{6})\b/g;
58
+ function extractCode(body) {
59
+ const matches = body.match(OTP_PATTERN);
60
+ if (!matches || matches.length === 0)
61
+ return '';
62
+ return matches[matches.length - 1];
63
+ }
64
+ /** 可被 AbortSignal 中断的 sleep:停止注册时立刻 reject,不再傻等 */
65
+ function abortableSleep(ms, signal) {
66
+ if (signal?.aborted)
67
+ return Promise.reject(new Error('Đăng ký đã bị hủy'));
68
+ return new Promise((resolve, reject) => {
69
+ const timer = setTimeout(() => {
70
+ signal?.removeEventListener('abort', onAbort);
71
+ resolve();
72
+ }, ms);
73
+ const onAbort = () => {
74
+ clearTimeout(timer);
75
+ reject(new Error('Đăng ký đã bị hủy'));
76
+ };
77
+ signal?.addEventListener('abort', onAbort, { once: true });
78
+ });
79
+ }
80
+ // ============ MoEmail 临时邮箱 ============
81
+ class MoEmailService {
82
+ baseURL;
83
+ apiKey;
84
+ address = '';
85
+ constructor(baseURL, apiKey) {
86
+ this.baseURL = MoEmailService.normalizeBaseURL(baseURL);
87
+ this.apiKey = apiKey;
88
+ }
89
+ /**
90
+ * 归一化用户输入的 baseURL:
91
+ * - 去除首尾空白与末尾斜杠
92
+ * - 缺少 protocol 时补 `https://`
93
+ * - 校验协议仅允许 http / https,否则抛清晰错误
94
+ * 用于规避 fetch 因协议不合法抛出
95
+ * "Invalid URL protocol: the URL must start with `http:` or `https:`."
96
+ */
97
+ static normalizeBaseURL(raw) {
98
+ const trimmed = (raw || '').trim().replace(/\/+$/, '');
99
+ if (!trimmed)
100
+ throw new Error('Chưa cấu hình MoEmail BaseURL');
101
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
102
+ let u;
103
+ try {
104
+ u = new URL(withScheme);
105
+ }
106
+ catch {
107
+ throw new Error(`Định dạng MoEmail BaseURL không hợp lệ: ${raw}`);
108
+ }
109
+ if (u.protocol !== 'http:' && u.protocol !== 'https:') {
110
+ throw new Error(`MoEmail BaseURL không hỗ trợ giao thức này (chỉ hỗ trợ http/https): ${u.protocol}`);
111
+ }
112
+ return withScheme;
113
+ }
114
+ async create() {
115
+ const url = `${this.baseURL}/api/mail/create`;
116
+ const headers = { 'Content-Type': 'application/json' };
117
+ if (this.apiKey)
118
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
119
+ const resp = await proxyFetch(url, { method: 'POST', headers, signal: AbortSignal.timeout(30000) });
120
+ const data = (await resp.json());
121
+ const addr = data.address ||
122
+ data.email ||
123
+ data.data?.address ||
124
+ data.data?.email ||
125
+ '';
126
+ if (!addr) {
127
+ console.log('[MoEmail] Tạo email thất bại:', JSON.stringify(data));
128
+ return '';
129
+ }
130
+ this.address = addr;
131
+ return addr;
132
+ }
133
+ async waitForCode(timeoutSec, intervalSec, signal) {
134
+ if (!this.address)
135
+ throw new Error('Địa chỉ email đang trống');
136
+ const maxRetries = Math.floor(timeoutSec / intervalSec);
137
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
138
+ if (signal?.aborted)
139
+ throw new Error('Đăng ký đã bị hủy');
140
+ await abortableSleep(intervalSec * 1000, signal);
141
+ try {
142
+ const code = await this.fetchCode();
143
+ if (code)
144
+ return code;
145
+ }
146
+ catch (err) {
147
+ if (attempt % 5 === 0)
148
+ console.log(`[MoEmail] [${attempt}/${maxRetries}] Truy vấn thất bại:`, err);
149
+ }
150
+ if (attempt % 5 === 0)
151
+ console.log(`[MoEmail] [${attempt}/${maxRetries}] Chưa có mã xác minh...`);
152
+ }
153
+ throw new Error(`Hết thời gian chờ mã xác minh (${timeoutSec} giây)`);
154
+ }
155
+ getAddress() {
156
+ return this.address;
157
+ }
158
+ async fetchCode() {
159
+ const url = `${this.baseURL}/api/mail/messages?address=${this.address}`;
160
+ const headers = {};
161
+ if (this.apiKey)
162
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
163
+ const resp = await proxyFetch(url, { headers, signal: AbortSignal.timeout(15000) });
164
+ const raw = await resp.json();
165
+ let messages = [];
166
+ if (Array.isArray(raw)) {
167
+ messages = raw;
168
+ }
169
+ else if (typeof raw === 'object' && raw !== null) {
170
+ const wrapper = raw;
171
+ if (Array.isArray(wrapper.data)) {
172
+ messages = wrapper.data;
173
+ }
174
+ }
175
+ for (const msg of messages) {
176
+ const text = msg.text || msg.body || msg.html || '';
177
+ if (text) {
178
+ const code = extractCode(text);
179
+ if (code)
180
+ return code;
181
+ }
182
+ }
183
+ return '';
184
+ }
185
+ }
186
+ exports.MoEmailService = MoEmailService;
187
+ // ============ TempMail.Plus + 自建域名 ============
188
+ class TempMailPlusService {
189
+ static BASE_URL = 'https://tempmail.plus/api';
190
+ tmEmail; // tempmail.plus 用户名(不含 @mailto.plus)
191
+ epin;
192
+ /** 支持多域名(用户填多行/逗号/空格分隔),每次 create 随机挑一个,降低单域名被风控关联 */
193
+ domains;
194
+ domain = '';
195
+ address = '';
196
+ constructor(tmEmail, epin, domain) {
197
+ this.tmEmail = tmEmail;
198
+ this.epin = epin;
199
+ this.domains = domain
200
+ .split(/[\s,;]+/)
201
+ .map((d) => d.trim().replace(/^@/, ''))
202
+ .filter(Boolean);
203
+ if (this.domains.length === 0) {
204
+ throw new Error('Tên miền riêng của TempMail.Plus đang trống');
205
+ }
206
+ }
207
+ get headers() {
208
+ return {
209
+ 'accept': 'application/json, text/javascript, */*; q=0.01',
210
+ 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
211
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
212
+ 'x-requested-with': 'XMLHttpRequest',
213
+ 'Referer': 'https://tempmail.plus/zh/',
214
+ 'cookie': `email=${encodeURIComponent(this.fullEmail)}`
215
+ };
216
+ }
217
+ async create() {
218
+ const prefix = (0, names_1.randomEmailPrefix)();
219
+ this.domain = this.domains[Math.floor(Math.random() * this.domains.length)];
220
+ this.address = `${prefix}@${this.domain}`;
221
+ if (this.domains.length > 1) {
222
+ console.log(`[TempMailPlus] Đã tạo email: ${this.address} (kho tên miền có ${this.domains.length} mục)`);
223
+ }
224
+ else {
225
+ console.log(`[TempMailPlus] Đã tạo email: ${this.address}`);
226
+ }
227
+ return this.address;
228
+ }
229
+ getAddress() {
230
+ return this.address;
231
+ }
232
+ async waitForCode(timeoutSec, intervalSec, signal) {
233
+ if (!this.address)
234
+ throw new Error('Địa chỉ email đang trống');
235
+ const maxRetries = Math.floor(timeoutSec / intervalSec);
236
+ const checkedIds = new Set();
237
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
238
+ if (signal?.aborted)
239
+ throw new Error('Đăng ký đã bị hủy');
240
+ await abortableSleep(intervalSec * 1000, signal);
241
+ try {
242
+ const mails = await this.fetchMailList();
243
+ if (attempt === 1 || attempt % 5 === 0) {
244
+ console.log(`[TempMailPlus] [${attempt}/${maxRetries}] Số email: ${mails.length}`);
245
+ }
246
+ for (const mail of mails) {
247
+ const mailId = mail.mail_id;
248
+ if (checkedIds.has(mailId))
249
+ continue;
250
+ checkedIds.add(mailId);
251
+ const detail = await this.fetchMailDetail(mailId);
252
+ if (!detail)
253
+ continue;
254
+ // 验证收件人匹配
255
+ const toField = String(detail.to || '').toLowerCase();
256
+ if (!toField.includes(this.address.toLowerCase())) {
257
+ console.log(`[TempMailPlus] Người nhận không khớp: ${toField} (cần chứa: ${this.address})`);
258
+ continue;
259
+ }
260
+ // 提取验证码
261
+ const code = this.extractOTP(detail);
262
+ if (code) {
263
+ console.log(`[TempMailPlus] Mã xác minh: ${code}`);
264
+ await this.deleteMail(mailId);
265
+ return code;
266
+ }
267
+ else {
268
+ console.log(`[TempMailPlus] Không lấy được mã xác minh từ email ${mailId}`);
269
+ }
270
+ }
271
+ }
272
+ catch (err) {
273
+ console.log(`[TempMailPlus] [${attempt}/${maxRetries}] Truy vấn thất bại:`, err);
274
+ }
275
+ if (attempt % 5 === 0)
276
+ console.log(`[TempMailPlus] [${attempt}/${maxRetries}] Chưa có mã xác minh...`);
277
+ }
278
+ throw new Error(`Hết thời gian chờ mã xác minh (${timeoutSec} giây)`);
279
+ }
280
+ get fullEmail() {
281
+ return `${this.tmEmail}@mailto.plus`;
282
+ }
283
+ async fetchMailList() {
284
+ const url = `${TempMailPlusService.BASE_URL}/mails?email=${encodeURIComponent(this.fullEmail)}&first_id=0&epin=${encodeURIComponent(this.epin)}`;
285
+ const resp = await proxyFetch(url, { headers: this.headers, signal: AbortSignal.timeout(15000) });
286
+ const data = (await resp.json());
287
+ if (!data.result)
288
+ return [];
289
+ return data.mail_list || [];
290
+ }
291
+ async fetchMailDetail(mailId) {
292
+ const url = `${TempMailPlusService.BASE_URL}/mails/${mailId}?email=${encodeURIComponent(this.fullEmail)}&epin=${encodeURIComponent(this.epin)}`;
293
+ const resp = await proxyFetch(url, { headers: this.headers, signal: AbortSignal.timeout(15000) });
294
+ const data = (await resp.json());
295
+ return data.result ? data : null;
296
+ }
297
+ async deleteMail(mailId) {
298
+ const url = `${TempMailPlusService.BASE_URL}/mails/${mailId}`;
299
+ const headers = { ...this.headers, 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' };
300
+ const body = `email=${encodeURIComponent(this.fullEmail)}&epin=${encodeURIComponent(this.epin)}`;
301
+ try {
302
+ await proxyFetch(url, { method: 'DELETE', headers, body, signal: AbortSignal.timeout(10000) });
303
+ console.log(`[TempMailPlus] Đã xóa email: ${mailId}`);
304
+ }
305
+ catch (err) {
306
+ console.log(`[TempMailPlus] Xóa email thất bại:`, err);
307
+ }
308
+ }
309
+ extractOTP(detail) {
310
+ // 从主题提取
311
+ const subject = String(detail.subject || '');
312
+ const subjectMatch = subject.match(/(\d{6})/);
313
+ if (subjectMatch)
314
+ return subjectMatch[1];
315
+ // 从正文提取
316
+ const text = String(detail.text || '');
317
+ const code = extractCode(text);
318
+ if (code)
319
+ return code;
320
+ // 从 HTML 提取
321
+ const html = String(detail.html || '');
322
+ return extractCode(html);
323
+ }
324
+ }
325
+ exports.TempMailPlusService = TempMailPlusService;
326
+ // ============ Tingamefi Temp Email / Cloudflare Email Worker ============
327
+ class TingamefiMailService {
328
+ apiUrl;
329
+ adminPassword;
330
+ configuredDomain;
331
+ address = '';
332
+ constructor(apiUrl, adminPassword, domain) {
333
+ this.apiUrl = TingamefiMailService.normalizeApiUrl(apiUrl || 'https://temp-email-worker.thienp1301.workers.dev');
334
+ this.adminPassword = (adminPassword || '').trim();
335
+ this.configuredDomain = (domain || 'mail.tingamefi.com').trim().replace(/^@/, '');
336
+ if (!this.adminPassword)
337
+ throw new Error('Tingamefi mail admin password is empty');
338
+ }
339
+ static normalizeApiUrl(raw) {
340
+ const trimmed = raw.trim().replace(/\/+$/, '');
341
+ if (!trimmed)
342
+ return 'https://temp-email-worker.thienp1301.workers.dev';
343
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
344
+ }
345
+ get headers() {
346
+ return {
347
+ 'Content-Type': 'application/json',
348
+ 'x-admin-auth': this.adminPassword,
349
+ 'x-lang': 'en',
350
+ 'x-fingerprint': 'kiro-account-manager-web'
351
+ };
352
+ }
353
+ async create() {
354
+ const domain = await this.resolveDomain();
355
+ let lastError = '';
356
+ for (let attempt = 1; attempt <= 5; attempt++) {
357
+ const name = (0, names_1.randomEmailPrefix)();
358
+ try {
359
+ const data = await this.fetchJson('/admin/new_address', {
360
+ method: 'POST',
361
+ headers: this.headers,
362
+ body: JSON.stringify({
363
+ enablePrefix: true,
364
+ enableRandomSubdomain: false,
365
+ name,
366
+ domain
367
+ }),
368
+ signal: AbortSignal.timeout(30000)
369
+ });
370
+ const address = String(data.address || '');
371
+ if (address) {
372
+ this.address = address;
373
+ console.log(`[TingamefiMail] created address: ${address}`);
374
+ return address;
375
+ }
376
+ lastError = JSON.stringify(data).slice(0, 300);
377
+ }
378
+ catch (error) {
379
+ lastError = error instanceof Error ? error.message : String(error);
380
+ }
381
+ }
382
+ throw new Error(`Tingamefi mail address creation failed: ${lastError || 'empty response'}`);
383
+ }
384
+ getAddress() {
385
+ return this.address;
386
+ }
387
+ async waitForCode(timeoutSec, intervalSec, signal) {
388
+ if (!this.address)
389
+ throw new Error('Tingamefi mail address is empty');
390
+ const maxRetries = Math.max(1, Math.floor(timeoutSec / intervalSec));
391
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
392
+ if (signal?.aborted)
393
+ throw new Error('Registration was cancelled');
394
+ await abortableSleep(intervalSec * 1000, signal);
395
+ try {
396
+ const messages = await this.fetchMessages();
397
+ if (attempt === 1 || attempt % 5 === 0) {
398
+ console.log(`[TingamefiMail] [${attempt}/${maxRetries}] messages: ${messages.length}`);
399
+ }
400
+ const preferred = messages.filter((message) => /no-reply@signin\.aws/i.test(String(message.source || message.raw || '')));
401
+ for (const message of [...preferred, ...messages.filter((message) => !preferred.includes(message))]) {
402
+ const text = [
403
+ message.subject,
404
+ message.text,
405
+ message.html,
406
+ message.message,
407
+ message.raw
408
+ ].map((value) => String(value || '')).join('\n');
409
+ const code = extractCode(text);
410
+ if (code) {
411
+ console.log(`[TingamefiMail] verification code: ${code}`);
412
+ await this.deleteMail(message.id).catch(() => undefined);
413
+ return code;
414
+ }
415
+ }
416
+ }
417
+ catch (error) {
418
+ if (attempt % 5 === 0)
419
+ console.log(`[TingamefiMail] [${attempt}/${maxRetries}] query failed:`, error);
420
+ }
421
+ if (attempt % 5 === 0)
422
+ console.log(`[TingamefiMail] [${attempt}/${maxRetries}] no verification code yet...`);
423
+ }
424
+ throw new Error(`Timed out waiting for verification code (${timeoutSec}s)`);
425
+ }
426
+ async resolveDomain() {
427
+ if (this.configuredDomain)
428
+ return this.configuredDomain;
429
+ const settings = await this.fetchJson('/open_api/settings', {
430
+ headers: { 'x-lang': 'en', 'x-fingerprint': 'kiro-account-manager-web' },
431
+ signal: AbortSignal.timeout(15000)
432
+ });
433
+ const domains = Array.isArray(settings.defaultDomains) ? settings.defaultDomains : settings.domains;
434
+ const first = Array.isArray(domains) ? String(domains[0] || '') : '';
435
+ if (!first)
436
+ throw new Error('Tingamefi mail domain is empty');
437
+ return first.replace(/^@/, '');
438
+ }
439
+ async fetchMessages() {
440
+ const query = `/admin/mails?limit=10&offset=0&address=${encodeURIComponent(this.address)}`;
441
+ const data = await this.fetchJson(query, {
442
+ headers: this.headers,
443
+ signal: AbortSignal.timeout(15000)
444
+ });
445
+ return Array.isArray(data.results) ? data.results : [];
446
+ }
447
+ async deleteMail(id) {
448
+ if (id === undefined || id === null || id === '')
449
+ return;
450
+ await this.fetchJson(`/admin/mails/${encodeURIComponent(String(id))}`, {
451
+ method: 'DELETE',
452
+ headers: this.headers,
453
+ signal: AbortSignal.timeout(10000)
454
+ });
455
+ }
456
+ async fetchJson(pathname, init) {
457
+ const response = await proxyFetch(`${this.apiUrl}${pathname}`, init);
458
+ const text = await response.text();
459
+ if (!response.ok) {
460
+ throw new Error(`Tingamefi mail API ${response.status}: ${text.slice(0, 300)}`);
461
+ }
462
+ return (text ? JSON.parse(text) : {});
463
+ }
464
+ }
465
+ exports.TingamefiMailService = TingamefiMailService;
466
+ /** 按 ---- 拆分;多出的连字符(N-4)归还前一字段(refreshToken 等 base64url 可能以 '-' 结尾) */
467
+ function splitByDashes(line) {
468
+ const parts = [];
469
+ const re = /-{4,}/g;
470
+ let last = 0;
471
+ let m;
472
+ while ((m = re.exec(line)) !== null) {
473
+ parts.push(line.slice(last, m.index) + '-'.repeat(m[0].length - 4));
474
+ last = m.index + m[0].length;
475
+ }
476
+ parts.push(line.slice(last));
477
+ return parts;
478
+ }
479
+ function parseOutlookLines(data) {
480
+ const accounts = [];
481
+ data = data.trim();
482
+ if (!data)
483
+ return accounts;
484
+ const lines = data.split('\n');
485
+ const parseEntry = (entry) => {
486
+ entry = entry.trim();
487
+ if (!entry)
488
+ return;
489
+ const parts = splitByDashes(entry);
490
+ if (parts.length === 4) {
491
+ accounts.push({
492
+ email: parts[0].trim(),
493
+ password: parts[1].trim(),
494
+ clientId: parts[2].trim(),
495
+ refreshToken: parts[3].trim()
496
+ });
497
+ }
498
+ };
499
+ if (lines.length === 1) {
500
+ for (const part of data.split(/\s+/))
501
+ parseEntry(part);
502
+ }
503
+ else {
504
+ for (const line of lines)
505
+ parseEntry(line);
506
+ }
507
+ return accounts;
508
+ }
509
+ async function refreshOutlookToken(acc) {
510
+ const form = new URLSearchParams({
511
+ client_id: acc.clientId,
512
+ refresh_token: acc.refreshToken,
513
+ grant_type: 'refresh_token',
514
+ scope: 'https://outlook.office.com/IMAP.AccessAsUser.All offline_access'
515
+ });
516
+ const resp = await proxyFetch('https://login.microsoftonline.com/consumers/oauth2/v2.0/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: form.toString() });
517
+ const data = (await resp.json());
518
+ if (resp.status !== 200)
519
+ throw new Error(`Làm mới thất bại ${resp.status}: ${JSON.stringify(data).slice(0, 300)}`);
520
+ const token = data.access_token;
521
+ if (!token)
522
+ throw new Error('Phản hồi không có access_token');
523
+ return token;
524
+ }
525
+ function buildXOAuth2(email, accessToken) {
526
+ const auth = `user=${email}\x01auth=Bearer ${accessToken}\x01\x01`;
527
+ return Buffer.from(auth).toString('base64');
528
+ }
529
+ class IMAPClient {
530
+ socket = null;
531
+ buffer = '';
532
+ tag = 0;
533
+ async connect() {
534
+ return new Promise((resolve, reject) => {
535
+ const socket = tls.connect(993, 'outlook.office365.com', { servername: 'outlook.office365.com' });
536
+ const timer = setTimeout(() => {
537
+ socket.destroy();
538
+ reject(new Error('Kết nối hết thời gian chờ'));
539
+ }, 15000);
540
+ socket.once('error', (err) => { clearTimeout(timer); reject(err); });
541
+ socket.once('secureConnect', () => {
542
+ clearTimeout(timer);
543
+ this.socket = socket;
544
+ this.readLine().then(() => resolve()).catch(reject);
545
+ });
546
+ });
547
+ }
548
+ readLine(timeoutMs = 30000) {
549
+ return new Promise((resolve, reject) => {
550
+ if (!this.socket)
551
+ return reject(new Error('Chưa kết nối'));
552
+ let settled = false;
553
+ const timer = setTimeout(() => {
554
+ if (settled)
555
+ return;
556
+ settled = true;
557
+ this.socket?.removeListener('data', onData);
558
+ this.socket?.removeListener('error', onError);
559
+ reject(new Error('IMAP readLine 超时'));
560
+ }, timeoutMs);
561
+ const done = (line) => {
562
+ if (settled)
563
+ return;
564
+ settled = true;
565
+ clearTimeout(timer);
566
+ this.socket?.removeListener('data', onData);
567
+ this.socket?.removeListener('error', onError);
568
+ resolve(line);
569
+ };
570
+ const onError = (err) => {
571
+ if (settled)
572
+ return;
573
+ settled = true;
574
+ clearTimeout(timer);
575
+ this.socket?.removeListener('data', onData);
576
+ reject(err);
577
+ };
578
+ const check = () => {
579
+ const idx = this.buffer.indexOf('\r\n');
580
+ if (idx >= 0) {
581
+ const line = this.buffer.slice(0, idx);
582
+ this.buffer = this.buffer.slice(idx + 2);
583
+ done(line);
584
+ return true;
585
+ }
586
+ return false;
587
+ };
588
+ if (check())
589
+ return;
590
+ const onData = (chunk) => {
591
+ this.buffer += chunk.toString();
592
+ check();
593
+ };
594
+ this.socket.on('data', onData);
595
+ this.socket.once('error', onError);
596
+ });
597
+ }
598
+ async sendCommand(cmd) {
599
+ if (!this.socket)
600
+ throw new Error('Chưa kết nối');
601
+ this.tag++;
602
+ const tagStr = `A${String(this.tag).padStart(3, '0')}`;
603
+ this.socket.write(`${tagStr} ${cmd}\r\n`);
604
+ return tagStr;
605
+ }
606
+ async readUntilTag(tag) {
607
+ const lines = [];
608
+ while (true) {
609
+ const line = await this.readLine();
610
+ if (line.startsWith(`${tag} `))
611
+ return { lines, result: line };
612
+ lines.push(line);
613
+ }
614
+ }
615
+ async authenticate(email, accessToken) {
616
+ const xoauth2 = buildXOAuth2(email, accessToken);
617
+ const tag = await this.sendCommand(`AUTHENTICATE XOAUTH2 ${xoauth2}`);
618
+ const { result } = await this.readUntilTag(tag);
619
+ if (!result.includes('OK'))
620
+ throw new Error(`Xác thực thất bại: ${result}`);
621
+ console.log('[IMAP] Xác thực thành công');
622
+ await sleep(800);
623
+ }
624
+ async selectInbox() {
625
+ for (let retry = 0; retry < 3; retry++) {
626
+ const tag = await this.sendCommand('SELECT INBOX');
627
+ const { lines, result } = await this.readUntilTag(tag);
628
+ if (result.includes('OK')) {
629
+ for (const line of lines) {
630
+ const m = line.match(/\*\s+(\d+)\s+EXISTS/);
631
+ if (m)
632
+ return parseInt(m[1], 10);
633
+ }
634
+ return 0;
635
+ }
636
+ if (retry < 2) {
637
+ console.log(`[IMAP] SELECT INBOX thất bại (${result}), thử lại ${retry + 1}/3...`);
638
+ await sleep((1 + retry) * 1000);
639
+ }
640
+ }
641
+ throw new Error('SELECT INBOX vẫn thất bại sau tất cả lần thử');
642
+ }
643
+ async fetchLatestBody(seq) {
644
+ if (seq <= 0)
645
+ throw new Error('Số thứ tự email không hợp lệ');
646
+ const tag = await this.sendCommand(`FETCH ${seq} (BODY.PEEK[TEXT])`);
647
+ const { lines, result } = await this.readUntilTag(tag);
648
+ if (!result.includes('OK'))
649
+ throw new Error(`FETCH TEXT thất bại: ${result}`);
650
+ const rawLines = [];
651
+ let inBody = false;
652
+ for (const line of lines) {
653
+ if (line.includes('FETCH')) {
654
+ inBody = true;
655
+ continue;
656
+ }
657
+ if (line === ')')
658
+ continue;
659
+ if (inBody)
660
+ rawLines.push(line);
661
+ }
662
+ const raw = rawLines.join('\n');
663
+ // 尝试解码 MIME base64
664
+ const parts = raw.split('------=_Part_');
665
+ let decoded = '';
666
+ for (const part of parts) {
667
+ if (part.includes('base64')) {
668
+ const idx = part.indexOf('base64');
669
+ const content = part.slice(idx + 6);
670
+ const b64 = content.replace(/[\s]/g, '');
671
+ try {
672
+ decoded += Buffer.from(b64, 'base64').toString() + ' ';
673
+ }
674
+ catch { /* ignore */ }
675
+ }
676
+ }
677
+ if (decoded)
678
+ return decoded;
679
+ // 整体 base64 解码
680
+ const cleaned = raw.replace(/[\s]/g, '');
681
+ try {
682
+ return Buffer.from(cleaned, 'base64').toString();
683
+ }
684
+ catch {
685
+ return raw;
686
+ }
687
+ }
688
+ close() {
689
+ if (this.socket) {
690
+ try {
691
+ this.socket.write('A999 LOGOUT\r\n');
692
+ }
693
+ catch { /* ignore */ }
694
+ this.socket.destroy();
695
+ this.socket = null;
696
+ }
697
+ }
698
+ }
699
+ async function getInboxCount(acc) {
700
+ const accessToken = await refreshOutlookToken(acc);
701
+ const client = new IMAPClient();
702
+ try {
703
+ await client.connect();
704
+ await client.authenticate(acc.email, accessToken);
705
+ return await client.selectInbox();
706
+ }
707
+ finally {
708
+ client.close();
709
+ }
710
+ }
711
+ async function waitForOTP(acc, beforeCount, timeout, interval, signal) {
712
+ console.log(`[Outlook IMAP] Đang chờ mã xác minh, email=${acc.email}, số email trước khi gửi=${beforeCount}`);
713
+ let accessToken = await refreshOutlookToken(acc);
714
+ const maxRetries = Math.floor(timeout / interval);
715
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
716
+ if (signal?.aborted)
717
+ throw new Error('Đăng ký đã bị hủy');
718
+ let client = null;
719
+ try {
720
+ client = new IMAPClient();
721
+ await client.connect();
722
+ await client.authenticate(acc.email, accessToken);
723
+ const total = await client.selectInbox();
724
+ if (total <= beforeCount) {
725
+ if (attempt % 5 === 0)
726
+ console.log(`[Outlook IMAP] [${attempt}/${maxRetries}] Chưa có email mới (hiện có ${total})...`);
727
+ await abortableSleep(interval * 1000, signal);
728
+ continue;
729
+ }
730
+ for (let i = total; i > beforeCount; i--) {
731
+ try {
732
+ const body = await client.fetchLatestBody(i);
733
+ const code = extractCode(body);
734
+ if (code) {
735
+ console.log(`[Outlook IMAP] Đã lấy mã xác minh: ${code}`);
736
+ return code;
737
+ }
738
+ }
739
+ catch { /* continue */ }
740
+ }
741
+ if (attempt % 5 === 0)
742
+ console.log(`[Outlook IMAP] [${attempt}/${maxRetries}] Không tìm thấy mã xác minh trong email mới...`);
743
+ }
744
+ catch (err) {
745
+ if (attempt % 5 === 0)
746
+ console.log(`[Outlook IMAP] Kết nối thất bại:`, err);
747
+ try {
748
+ accessToken = await refreshOutlookToken(acc);
749
+ }
750
+ catch { /* ignore */ }
751
+ }
752
+ finally {
753
+ client?.close();
754
+ }
755
+ await abortableSleep(interval * 1000, signal);
756
+ }
757
+ throw new Error(`Hết thời gian chờ mã xác minh (${timeout} giây)`);
758
+ }
759
+ // ============ Proton 邮箱(webview 借壳官方网页,轻量读 DOM 取码) ============
760
+ /**
761
+ * Proton 点号别名取码源:用一个 Proton 母邮箱(如 evanbartellchae@protonmail.com),
762
+ * 前端用 dotVariants 生成点号变体(evanbar.tellcha.e@protonmail.com)作为每个账号的注册邮箱,
763
+ * 所有变体都进同一个 Proton 收件箱。读码经由主进程的隐藏 Proton 窗口(见 proton-mail-window.ts),
764
+ * 官方网页负责登录与 PGP 解密,本类只接收前端生成好的具体地址并等待取码。
765
+ */
766
+ class ProtonWebviewService {
767
+ /** 本次注册使用的具体邮箱地址(母邮箱或其点号变体,由前端生成传入) */
768
+ address;
769
+ /** 日志回调:传入 registrar.this.log 时,取码日志会推送到注册页面日志面板;缺省回退 console */
770
+ log;
771
+ constructor(presetAddress, log) {
772
+ this.address = (presetAddress || '').trim();
773
+ if (!this.address) {
774
+ throw new Error('Địa chỉ email Proton đang trống');
775
+ }
776
+ this.log = log || ((m) => console.log(m));
777
+ }
778
+ async create() {
779
+ this.log(`[Proton] Sử dụng email: ${this.address}`);
780
+ return this.address;
781
+ }
782
+ getAddress() {
783
+ return this.address;
784
+ }
785
+ async waitForCode(timeoutSec, intervalSec, signal) {
786
+ const runtime = process.versions.electron
787
+ ? await Promise.resolve().then(() => __importStar(require('./proton-mail-window')))
788
+ : await Promise.resolve().then(() => __importStar(require('../../server/services/protonBrowserRuntime')));
789
+ const { waitProtonOtp } = runtime;
790
+ return waitProtonOtp(this.address, {
791
+ timeoutSec,
792
+ intervalSec,
793
+ signal,
794
+ log: this.log
795
+ });
796
+ }
797
+ }
798
+ exports.ProtonWebviewService = ProtonWebviewService;
799
+ function sleep(ms) {
800
+ return new Promise((r) => setTimeout(r, ms));
801
+ }