@bobfrankston/mailx 1.0.159 → 1.0.161
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/README.md +2 -2
- package/again.cmd +3 -0
- package/bin/ager.js +57 -0
- package/bin/mailx.js +16 -9
- package/client/lib/mailxapi.js +35 -28
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -200,10 +200,10 @@ Under **Settings** in the toolbar:
|
|
|
200
200
|
|
|
201
201
|
```
|
|
202
202
|
mailx Start the app (native window via IPC)
|
|
203
|
-
mailx --
|
|
204
|
-
mailx --no-browser Server mode without opening browser
|
|
203
|
+
mailx --email <addr> First-time setup with this email (skips prompt)
|
|
205
204
|
mailx --verbose Show log output in terminal (default: log file only)
|
|
206
205
|
mailx --import <file> Import accounts.jsonc into Google Drive
|
|
206
|
+
mailx --server Start HTTP server for dev/remote (http://localhost:9333)
|
|
207
207
|
|
|
208
208
|
mailx -kill Kill running mailx processes + clean up WAL files
|
|
209
209
|
mailx -repair Re-sync message metadata (fix garbled subjects)
|
package/again.cmd
ADDED
package/bin/ager.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ager — archive .mailx to c:\temp\.mailx## and preserve OAuth tokens
|
|
3
|
+
*
|
|
4
|
+
* Moves ~/.mailx to c:\temp\.mailx01 (or next available number),
|
|
5
|
+
* then copies the tokens directory back so OAuth doesn't need re-consent.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node ager.js
|
|
8
|
+
*/
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
const home = process.env.USERPROFILE || process.env.HOME || ".";
|
|
12
|
+
const source = path.join(home, ".mailx");
|
|
13
|
+
const tempDir = "C:\\temp";
|
|
14
|
+
if (!fs.existsSync(source)) {
|
|
15
|
+
console.error(`No .mailx directory at ${source}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// Find next available number
|
|
19
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
20
|
+
let num = 1;
|
|
21
|
+
while (fs.existsSync(path.join(tempDir, `.mailx${String(num).padStart(2, "0")}`))) {
|
|
22
|
+
num++;
|
|
23
|
+
}
|
|
24
|
+
const dest = path.join(tempDir, `.mailx${String(num).padStart(2, "0")}`);
|
|
25
|
+
// Save tokens before moving
|
|
26
|
+
const tokensDir = path.join(source, "tokens");
|
|
27
|
+
const hasTokens = fs.existsSync(tokensDir);
|
|
28
|
+
const tokenFiles = [];
|
|
29
|
+
if (hasTokens) {
|
|
30
|
+
const walk = (dir, base) => {
|
|
31
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
32
|
+
const full = path.join(dir, entry.name);
|
|
33
|
+
const rel = path.join(base, entry.name);
|
|
34
|
+
if (entry.isDirectory())
|
|
35
|
+
walk(full, rel);
|
|
36
|
+
else
|
|
37
|
+
tokenFiles.push({ rel, data: fs.readFileSync(full) });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
walk(tokensDir, "");
|
|
41
|
+
console.log(` Saved ${tokenFiles.length} token file(s)`);
|
|
42
|
+
}
|
|
43
|
+
// Move .mailx to temp
|
|
44
|
+
fs.renameSync(source, dest);
|
|
45
|
+
console.log(`Moved ${source} → ${dest}`);
|
|
46
|
+
// Restore tokens to fresh .mailx
|
|
47
|
+
if (tokenFiles.length > 0) {
|
|
48
|
+
const newTokens = path.join(source, "tokens");
|
|
49
|
+
for (const { rel, data } of tokenFiles) {
|
|
50
|
+
const target = path.join(newTokens, rel);
|
|
51
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
52
|
+
fs.writeFileSync(target, data);
|
|
53
|
+
}
|
|
54
|
+
console.log(` Restored tokens to ${newTokens}`);
|
|
55
|
+
}
|
|
56
|
+
console.log("Ready for fresh start.");
|
|
57
|
+
//# sourceMappingURL=ager.js.map
|
package/bin/mailx.js
CHANGED
|
@@ -35,7 +35,7 @@ const rebuildMode = hasFlag("rebuild");
|
|
|
35
35
|
const repairMode = hasFlag("repair");
|
|
36
36
|
const importMode = hasFlag("import");
|
|
37
37
|
// Validate arguments
|
|
38
|
-
const knownFlags = ["server", "no-browser", "verbose", "external", "kill", "v", "version", "setup", "add", "test", "rebuild", "repair", "native-imap", "log", "import", "email"];
|
|
38
|
+
const knownFlags = ["server", "no-browser", "verbose", "external", "kill", "v", "version", "setup", "add", "test", "rebuild", "repair", "native-imap", "log", "import", "email", "mail"];
|
|
39
39
|
for (const arg of args) {
|
|
40
40
|
const flag = arg.replace(/^--?/, "");
|
|
41
41
|
if (arg.startsWith("-") && !knownFlags.includes(flag)) {
|
|
@@ -599,9 +599,11 @@ async function main() {
|
|
|
599
599
|
if (setupMode || !hasConfig()) {
|
|
600
600
|
if (!setupMode)
|
|
601
601
|
console.log("No mailx configuration found.");
|
|
602
|
-
// --email flag skips the interactive prompt
|
|
602
|
+
// --email (or -mail) flag skips the interactive prompt
|
|
603
603
|
const emailArg = args.find(a => a.startsWith("--email="))?.split("=")[1]
|
|
604
|
-
|| (
|
|
604
|
+
|| args.find(a => a.startsWith("-mail="))?.split("=")[1]
|
|
605
|
+
|| (hasFlag("email") ? args[args.indexOf("--email") + 1] || args[args.indexOf("-email") + 1] : undefined)
|
|
606
|
+
|| (hasFlag("mail") ? args[args.indexOf("--mail") + 1] || args[args.indexOf("-mail") + 1] : undefined);
|
|
605
607
|
await runSetup(emailArg);
|
|
606
608
|
}
|
|
607
609
|
// Redirect console to log file — keep terminal clean
|
|
@@ -708,9 +710,8 @@ async function main() {
|
|
|
708
710
|
imapManager.on("accountError", (accountId, error, hint, isOAuth) => {
|
|
709
711
|
handle.send({ _event: "accountError", type: "accountError", accountId, error, hint, isOAuth });
|
|
710
712
|
});
|
|
711
|
-
//
|
|
712
|
-
await new Promise(r => setTimeout(r,
|
|
713
|
-
handle.send({ _event: "ready", type: "ready" });
|
|
713
|
+
// Brief pause for WebView2 to initialize before starting IMAP (avoids stdin writes during init)
|
|
714
|
+
await new Promise(r => setTimeout(r, 500));
|
|
714
715
|
// Register all accounts (OAuth may open browser for Gmail — event loop stays free for IPC)
|
|
715
716
|
for (const account of settings.accounts) {
|
|
716
717
|
if (!account.enabled)
|
|
@@ -747,10 +748,16 @@ async function main() {
|
|
|
747
748
|
}
|
|
748
749
|
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
749
750
|
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
750
|
-
process.on("
|
|
751
|
+
process.on("uncaughtException", (err) => {
|
|
752
|
+
console.error(`UNCAUGHT EXCEPTION: ${err.stack || err.message}`);
|
|
753
|
+
gracefulShutdown("uncaughtException");
|
|
754
|
+
});
|
|
755
|
+
process.on("unhandledRejection", (reason) => {
|
|
756
|
+
console.error(`UNHANDLED REJECTION: ${reason?.stack || reason?.message || reason}`);
|
|
757
|
+
});
|
|
758
|
+
process.on("exit", (code) => {
|
|
759
|
+
console.log(`Process exit (code ${code})`);
|
|
751
760
|
if (!shuttingDown) {
|
|
752
|
-
// Sync fallback — shutdown() is async so can't fully run here,
|
|
753
|
-
// but at least stop timers and release semaphores
|
|
754
761
|
imapManager.stopPeriodicSync();
|
|
755
762
|
imapManager.stopOutboxWorker();
|
|
756
763
|
db.close();
|
package/client/lib/mailxapi.js
CHANGED
|
@@ -12,9 +12,6 @@
|
|
|
12
12
|
var _callbacks = {};
|
|
13
13
|
var _callbackId = 0;
|
|
14
14
|
var _eventHandlers = [];
|
|
15
|
-
var _ready = false;
|
|
16
|
-
var _pendingCalls = []; // buffered until server sends "ready"
|
|
17
|
-
|
|
18
15
|
function callNode(action, params) {
|
|
19
16
|
var id = String(++_callbackId);
|
|
20
17
|
return new Promise(function(resolve, reject) {
|
|
@@ -24,11 +21,6 @@
|
|
|
24
21
|
}, 120000);
|
|
25
22
|
_callbacks[id] = { resolve: resolve, reject: reject, timer: timer };
|
|
26
23
|
var msg = Object.assign({ _action: action, _cbid: id }, params || {});
|
|
27
|
-
if (!_ready) {
|
|
28
|
-
// Buffer until server is ready (early calls are lost in the pipe)
|
|
29
|
-
_pendingCalls.push(msg);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
24
|
if (window.ipc && window.ipc.postMessage) {
|
|
33
25
|
window.ipc.postMessage(JSON.stringify(msg));
|
|
34
26
|
} else {
|
|
@@ -39,20 +31,6 @@
|
|
|
39
31
|
});
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
function flushPending() {
|
|
43
|
-
_ready = true;
|
|
44
|
-
var pending = _pendingCalls.splice(0);
|
|
45
|
-
// Send diagnostic so it shows in Node.js IPC log
|
|
46
|
-
if (window.ipc && window.ipc.postMessage) {
|
|
47
|
-
window.ipc.postMessage(JSON.stringify({ _action: "_debug", _cbid: "0", info: "flush " + pending.length + " calls: " + pending.map(function(m) { return m._action; }).join(", ") }));
|
|
48
|
-
}
|
|
49
|
-
for (var i = 0; i < pending.length; i++) {
|
|
50
|
-
if (window.ipc && window.ipc.postMessage) {
|
|
51
|
-
window.ipc.postMessage(JSON.stringify(pending[i]));
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
34
|
// Called by Rust to resolve promises
|
|
57
35
|
window._mailxapiResolve = function(id, value) {
|
|
58
36
|
var cb = _callbacks[id];
|
|
@@ -73,11 +51,6 @@
|
|
|
73
51
|
|
|
74
52
|
// Called by Rust to push events (new mail, sync progress, etc.)
|
|
75
53
|
window._mailxapiEvent = function(event) {
|
|
76
|
-
// "ready" signal from server — flush buffered IPC calls
|
|
77
|
-
if (event && event.type === "ready") {
|
|
78
|
-
flushPending();
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
54
|
for (var i = 0; i < _eventHandlers.length; i++) {
|
|
82
55
|
try { _eventHandlers[i](event); } catch(e) { /* ignore */ }
|
|
83
56
|
}
|
|
@@ -132,13 +105,47 @@
|
|
|
132
105
|
syncAll: function() { return callNode("syncAll"); },
|
|
133
106
|
getSyncPending: function() { return callNode("getSyncPending"); },
|
|
134
107
|
|
|
108
|
+
// Bulk operations
|
|
109
|
+
deleteMessages: function(accountId, uids) {
|
|
110
|
+
return callNode("deleteMessages", { accountId: accountId, uids: uids });
|
|
111
|
+
},
|
|
112
|
+
moveMessage: function(accountId, uid, targetFolderId, targetAccountId) {
|
|
113
|
+
return callNode("moveMessage", { accountId: accountId, uid: uid, targetFolderId: targetFolderId, targetAccountId: targetAccountId });
|
|
114
|
+
},
|
|
115
|
+
moveMessages: function(accountId, uids, targetFolderId) {
|
|
116
|
+
return callNode("moveMessages", { accountId: accountId, uids: uids, targetFolderId: targetFolderId });
|
|
117
|
+
},
|
|
118
|
+
markFolderRead: function(accountId, folderId) {
|
|
119
|
+
return callNode("markFolderRead", { folderId: folderId });
|
|
120
|
+
},
|
|
121
|
+
createFolder: function(accountId, parentPath, name) {
|
|
122
|
+
return callNode("createFolder", { accountId: accountId, parentPath: parentPath, name: name });
|
|
123
|
+
},
|
|
124
|
+
renameFolder: function(accountId, folderId, newName) {
|
|
125
|
+
return callNode("renameFolder", { accountId: accountId, folderId: folderId, newName: newName });
|
|
126
|
+
},
|
|
127
|
+
deleteFolder: function(accountId, folderId) {
|
|
128
|
+
return callNode("deleteFolder", { accountId: accountId, folderId: folderId });
|
|
129
|
+
},
|
|
130
|
+
emptyFolder: function(accountId, folderId) {
|
|
131
|
+
return callNode("emptyFolder", { accountId: accountId, folderId: folderId });
|
|
132
|
+
},
|
|
133
|
+
|
|
135
134
|
// Settings
|
|
136
135
|
allowRemoteContent: function(type, value) {
|
|
137
136
|
return callNode("allowRemoteContent", { type: type, value: value });
|
|
138
137
|
},
|
|
139
138
|
getSettings: function() { return callNode("getSettings"); },
|
|
140
|
-
|
|
139
|
+
saveSettingsData: function(data) { return callNode("saveSettingsData", data); },
|
|
141
140
|
getVersion: function() { return callNode("getVersion"); },
|
|
141
|
+
getAutocompleteSettings: function() { return callNode("getAutocompleteSettings"); },
|
|
142
|
+
saveAutocompleteSettings: function(settings) { return callNode("saveAutocompleteSettings", settings); },
|
|
143
|
+
|
|
144
|
+
// Setup & Repair
|
|
145
|
+
setupAccount: function(name, email, password) {
|
|
146
|
+
return callNode("setupAccount", { name: name, email: email, password: password });
|
|
147
|
+
},
|
|
148
|
+
repairAccounts: function() { return callNode("repairAccounts"); },
|
|
142
149
|
|
|
143
150
|
// Events
|
|
144
151
|
onEvent: function(handler) { _eventHandlers.push(handler); },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.161",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"postinstall": "node bin/postinstall.js"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@bobfrankston/iflow": "^1.0.
|
|
23
|
+
"@bobfrankston/iflow": "^1.0.55",
|
|
24
24
|
"@bobfrankston/miscinfo": "^1.0.7",
|
|
25
25
|
"@bobfrankston/oauthsupport": "^1.0.20",
|
|
26
|
-
"@bobfrankston/msger": "^0.1.
|
|
26
|
+
"@bobfrankston/msger": "^0.1.211",
|
|
27
27
|
"@capacitor/android": "^8.3.0",
|
|
28
28
|
"@capacitor/cli": "^8.3.0",
|
|
29
29
|
"@capacitor/core": "^8.3.0",
|