@bobfrankston/mailx 1.0.143 → 1.0.144
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/bin/mailx.js +85 -27
- package/package.json +1 -1
- package/packages/mailx-service/jsonrpc.d.ts +25 -0
- package/packages/mailx-service/jsonrpc.js +105 -0
package/bin/mailx.js
CHANGED
|
@@ -18,7 +18,7 @@ import fs from "node:fs";
|
|
|
18
18
|
import path from "node:path";
|
|
19
19
|
import net from "node:net";
|
|
20
20
|
import { ports } from "@bobfrankston/miscinfo";
|
|
21
|
-
import { showMessageBox } from "@bobfrankston/msger";
|
|
21
|
+
import { showMessageBox, showService } from "@bobfrankston/msger";
|
|
22
22
|
const PORT = ports.mailx;
|
|
23
23
|
const args = process.argv.slice(2);
|
|
24
24
|
// Normalize: accept both -flag and --flag
|
|
@@ -488,16 +488,7 @@ async function main() {
|
|
|
488
488
|
console.log("No mailx configuration found.");
|
|
489
489
|
await runSetup();
|
|
490
490
|
}
|
|
491
|
-
// Check if server is already running
|
|
492
|
-
const inUse = await isPortInUse(PORT);
|
|
493
|
-
if (inUse) {
|
|
494
|
-
const url = `http://127.0.0.1:${PORT}`;
|
|
495
|
-
if (!noBrowser)
|
|
496
|
-
launchMsger(url);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
491
|
// Redirect console to log file — keep terminal clean
|
|
500
|
-
// Server also logs, but this catches CLI startup messages
|
|
501
492
|
if (!verbose) {
|
|
502
493
|
const home = process.env.USERPROFILE || process.env.HOME || ".";
|
|
503
494
|
const logDir = path.join(home, ".mailx", "logs");
|
|
@@ -505,28 +496,95 @@ async function main() {
|
|
|
505
496
|
const logDate = new Date().toISOString().slice(0, 10);
|
|
506
497
|
const logPath = path.join(logDir, `mailx-${logDate}.log`);
|
|
507
498
|
const logStream = fs.createWriteStream(logPath, { flags: "a" });
|
|
508
|
-
const origLog = console.log;
|
|
509
|
-
const origErr = console.error;
|
|
510
499
|
console.log = (...a) => { logStream.write(a.join(" ") + "\n"); };
|
|
511
500
|
console.error = (...a) => { logStream.write("ERROR " + a.join(" ") + "\n"); };
|
|
512
501
|
}
|
|
513
|
-
//
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
502
|
+
// --server mode: Express + HTTP (for dev/remote access)
|
|
503
|
+
if (serverMode) {
|
|
504
|
+
console.log("Starting mailx HTTP server...");
|
|
505
|
+
if (hasFlag("external"))
|
|
506
|
+
process.argv.push("--external");
|
|
507
|
+
await import("../packages/mailx-server/index.js");
|
|
508
|
+
if (!noBrowser)
|
|
509
|
+
launchMsger(`http://127.0.0.1:${PORT}`);
|
|
510
|
+
await new Promise(() => { }); // keep alive
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
// Default: service mode — no TCP, IPC via msger
|
|
514
|
+
console.log("Starting mailx service...");
|
|
515
|
+
const { MailxDB } = await import("@bobfrankston/mailx-store");
|
|
516
|
+
const { ImapManager } = await import("@bobfrankston/mailx-imap");
|
|
517
|
+
const { MailxService } = await import("@bobfrankston/mailx-service");
|
|
518
|
+
const { dispatch } = await import("@bobfrankston/mailx-service/jsonrpc.js");
|
|
519
|
+
const { loadSettings, loadAccountsAsync, getConfigDir, getStorageInfo } = await import("@bobfrankston/mailx-settings");
|
|
520
|
+
let settings = loadSettings();
|
|
521
|
+
if (settings.accounts.length === 0) {
|
|
522
|
+
const cloudAccounts = await loadAccountsAsync();
|
|
523
|
+
if (cloudAccounts.length > 0) {
|
|
524
|
+
settings = { ...settings, accounts: cloudAccounts };
|
|
525
|
+
console.log(` Loaded ${cloudAccounts.length} account(s) from cloud`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const db = new MailxDB(getConfigDir());
|
|
529
|
+
const imapManager = new ImapManager(db);
|
|
530
|
+
imapManager.useNativeClient = true;
|
|
531
|
+
const svc = new MailxService(db, imapManager);
|
|
532
|
+
// Read mailxapi.js for injection into WebView
|
|
533
|
+
const mailxapiPath = path.join(import.meta.dirname, "..", "client", "lib", "mailxapi.js");
|
|
534
|
+
const mailxapiScript = fs.readFileSync(mailxapiPath, "utf-8");
|
|
535
|
+
// Open msger in service mode — file:// URL, no HTTP
|
|
536
|
+
const clientDir = path.join(import.meta.dirname, "..", "client");
|
|
537
|
+
const indexPath = path.join(clientDir, "index.html");
|
|
538
|
+
const handle = showService({
|
|
539
|
+
url: indexPath,
|
|
540
|
+
initScript: mailxapiScript,
|
|
541
|
+
size: { width: 1400, height: 900 },
|
|
542
|
+
});
|
|
543
|
+
// Handle requests from WebView → dispatch to MailxService
|
|
544
|
+
handle.onRequest(async (req) => {
|
|
545
|
+
const response = await dispatch(svc, req);
|
|
546
|
+
handle.send(response);
|
|
547
|
+
});
|
|
548
|
+
// Wire IMAP events → push to WebView
|
|
549
|
+
imapManager.on("syncProgress", (accountId, phase, progress) => {
|
|
550
|
+
handle.send({ _event: "syncProgress", type: "syncProgress", accountId, phase, progress });
|
|
551
|
+
});
|
|
552
|
+
imapManager.on("folderCountsChanged", (accountId, counts) => {
|
|
553
|
+
handle.send({ _event: "folderCountsChanged", type: "folderCountsChanged", accountId, counts });
|
|
554
|
+
});
|
|
555
|
+
imapManager.on("syncError", (accountId, error) => {
|
|
556
|
+
handle.send({ _event: "error", type: "error", message: `${accountId}: ${error}` });
|
|
557
|
+
});
|
|
558
|
+
imapManager.on("accountError", (accountId, error, hint, isOAuth) => {
|
|
559
|
+
handle.send({ _event: "accountError", type: "accountError", accountId, error, hint, isOAuth });
|
|
560
|
+
});
|
|
561
|
+
// Add accounts and start sync
|
|
562
|
+
for (const account of settings.accounts) {
|
|
563
|
+
if (!account.enabled)
|
|
564
|
+
continue;
|
|
565
|
+
try {
|
|
566
|
+
await imapManager.addAccount(account);
|
|
567
|
+
console.log(` Account: ${account.label || account.name} (${account.id})`);
|
|
525
568
|
}
|
|
526
|
-
|
|
569
|
+
catch (e) {
|
|
570
|
+
console.error(` Failed: ${account.id}: ${e.message}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (settings.accounts.some(a => a.enabled)) {
|
|
574
|
+
imapManager.syncAll().catch(e => console.error(` Sync error: ${e.message}`));
|
|
527
575
|
}
|
|
528
|
-
|
|
529
|
-
|
|
576
|
+
imapManager.startPeriodicSync(settings.sync.intervalMinutes);
|
|
577
|
+
imapManager.startOutboxWorker();
|
|
578
|
+
// Wait for window close, then shut down
|
|
579
|
+
await handle.closed;
|
|
580
|
+
console.log("Window closed — shutting down");
|
|
581
|
+
imapManager.stopPeriodicSync();
|
|
582
|
+
try {
|
|
583
|
+
await imapManager.shutdown();
|
|
584
|
+
}
|
|
585
|
+
catch { /* proceed */ }
|
|
586
|
+
db.close();
|
|
587
|
+
process.exit(0);
|
|
530
588
|
}
|
|
531
589
|
main().catch(console.error);
|
|
532
590
|
//# sourceMappingURL=mailx.js.map
|
package/package.json
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC dispatcher for MailxService.
|
|
3
|
+
* Maps mailxapi action names to MailxService method calls.
|
|
4
|
+
* Used by msger's bidirectional IPC (Phase 3) and can also
|
|
5
|
+
* serve as a stdio JSON-RPC interface for testing.
|
|
6
|
+
*/
|
|
7
|
+
type ServiceLike = Record<string, (...args: any[]) => any>;
|
|
8
|
+
export interface RpcRequest {
|
|
9
|
+
_action: string;
|
|
10
|
+
_cbid: string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
export interface RpcResponse {
|
|
14
|
+
_cbid: string;
|
|
15
|
+
result?: any;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface RpcEvent {
|
|
19
|
+
_event: string;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
/** Dispatch an incoming mailxapi call to the appropriate MailxService method */
|
|
23
|
+
export declare function dispatch(svc: ServiceLike, req: RpcRequest): Promise<RpcResponse>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=jsonrpc.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC dispatcher for MailxService.
|
|
3
|
+
* Maps mailxapi action names to MailxService method calls.
|
|
4
|
+
* Used by msger's bidirectional IPC (Phase 3) and can also
|
|
5
|
+
* serve as a stdio JSON-RPC interface for testing.
|
|
6
|
+
*/
|
|
7
|
+
/** Dispatch an incoming mailxapi call to the appropriate MailxService method */
|
|
8
|
+
export async function dispatch(svc, req) {
|
|
9
|
+
const { _action, _cbid, ...params } = req;
|
|
10
|
+
try {
|
|
11
|
+
const result = await dispatchAction(svc, _action, params);
|
|
12
|
+
return { _cbid, result };
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
return { _cbid, error: e.message || String(e) };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function dispatchAction(svc, action, p) {
|
|
19
|
+
switch (action) {
|
|
20
|
+
// Accounts
|
|
21
|
+
case "getAccounts":
|
|
22
|
+
return svc.getAccounts();
|
|
23
|
+
case "getFolders":
|
|
24
|
+
return svc.getFolders(p.accountId);
|
|
25
|
+
// Messages
|
|
26
|
+
case "getMessages":
|
|
27
|
+
return svc.getMessages(p.accountId, p.folderId, p.page, p.pageSize);
|
|
28
|
+
case "getUnifiedInbox":
|
|
29
|
+
return svc.getUnifiedInbox(p.page || 1, p.pageSize || 50);
|
|
30
|
+
case "getMessage":
|
|
31
|
+
return svc.getMessage(p.accountId, p.uid, p.allowRemote, p.folderId);
|
|
32
|
+
// Flags & actions
|
|
33
|
+
case "updateFlags":
|
|
34
|
+
await svc.updateFlags(p.accountId, p.uid, p.flags);
|
|
35
|
+
return { ok: true };
|
|
36
|
+
case "deleteMessage":
|
|
37
|
+
await svc.deleteMessage(p.accountId, p.uid);
|
|
38
|
+
return { ok: true };
|
|
39
|
+
case "deleteMessages":
|
|
40
|
+
await svc.deleteMessages(p.accountId, p.uids);
|
|
41
|
+
return { ok: true, count: p.uids.length };
|
|
42
|
+
case "undeleteMessage":
|
|
43
|
+
await svc.undeleteMessage(p.accountId, p.uid, p.folderId);
|
|
44
|
+
return { ok: true };
|
|
45
|
+
case "moveMessage":
|
|
46
|
+
await svc.moveMessage(p.accountId, p.uid, p.targetFolderId, p.targetAccountId);
|
|
47
|
+
return { ok: true };
|
|
48
|
+
case "moveMessages":
|
|
49
|
+
await svc.moveMessages(p.accountId, p.uids, p.targetFolderId);
|
|
50
|
+
return { ok: true, count: p.uids.length };
|
|
51
|
+
// Folders
|
|
52
|
+
case "markFolderRead":
|
|
53
|
+
svc.markFolderRead(p.folderId);
|
|
54
|
+
return { ok: true };
|
|
55
|
+
case "createFolder":
|
|
56
|
+
await svc.createFolder(p.accountId, p.parentPath, p.name);
|
|
57
|
+
return { ok: true };
|
|
58
|
+
case "renameFolder":
|
|
59
|
+
await svc.renameFolder(p.accountId, p.folderId, p.newName);
|
|
60
|
+
return { ok: true };
|
|
61
|
+
case "deleteFolder":
|
|
62
|
+
await svc.deleteFolder(p.accountId, p.folderId);
|
|
63
|
+
return { ok: true };
|
|
64
|
+
case "emptyFolder":
|
|
65
|
+
await svc.emptyFolder(p.accountId, p.folderId);
|
|
66
|
+
return { ok: true };
|
|
67
|
+
// Compose
|
|
68
|
+
case "sendMessage":
|
|
69
|
+
await svc.send(p);
|
|
70
|
+
return { ok: true };
|
|
71
|
+
case "saveDraft":
|
|
72
|
+
return svc.saveDraft(p.accountId, p.subject, p.bodyHtml, p.bodyText, p.to, p.cc, p.previousDraftUid, p.draftId);
|
|
73
|
+
case "deleteDraft":
|
|
74
|
+
await svc.deleteDraft(p.accountId, p.draftUid);
|
|
75
|
+
return { ok: true };
|
|
76
|
+
// Sync
|
|
77
|
+
case "syncAll":
|
|
78
|
+
await svc.syncAll();
|
|
79
|
+
return { ok: true };
|
|
80
|
+
case "getSyncPending":
|
|
81
|
+
return svc.getSyncPending();
|
|
82
|
+
// Search & contacts
|
|
83
|
+
case "searchMessages":
|
|
84
|
+
return svc.search(p.query, p.page, p.pageSize, p.scope, p.accountId, p.folderId);
|
|
85
|
+
case "searchContacts":
|
|
86
|
+
return svc.searchContacts(p.query);
|
|
87
|
+
// Settings
|
|
88
|
+
case "getSettings":
|
|
89
|
+
return svc.getSettings();
|
|
90
|
+
case "saveSettingsData":
|
|
91
|
+
svc.saveSettings(p);
|
|
92
|
+
return { ok: true };
|
|
93
|
+
case "allowRemoteContent":
|
|
94
|
+
svc.allowRemoteContent(p.type, p.value);
|
|
95
|
+
return { ok: true };
|
|
96
|
+
case "getVersion":
|
|
97
|
+
return svc.getStorageInfo();
|
|
98
|
+
// Autocomplete
|
|
99
|
+
case "autocomplete":
|
|
100
|
+
return svc.autocomplete(p);
|
|
101
|
+
default:
|
|
102
|
+
throw new Error(`Unknown action: ${action}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=jsonrpc.js.map
|