@bobfrankston/mailx 1.0.339 → 1.0.348
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 +87 -7
- package/client/app.js +413 -32
- package/client/components/address-book.js +199 -0
- package/client/components/calendar.js +217 -0
- package/client/components/folder-tree.js +62 -16
- package/client/components/message-list.js +9 -0
- package/client/components/message-viewer.js +41 -8
- package/client/components/outbox-view.js +104 -0
- package/client/components/tasks.js +256 -0
- package/client/compose/compose.html +2 -2
- package/client/compose/compose.js +87 -39
- package/client/compose/editor.js +67 -0
- package/client/index.html +8 -6
- package/client/lib/api-client.js +21 -0
- package/client/lib/mailxapi.js +15 -0
- package/client/styles/components.css +354 -0
- package/package.json +3 -3
- package/packages/mailx-imap/index.d.ts +24 -0
- package/packages/mailx-imap/index.js +132 -6
- package/packages/mailx-service/index.d.ts +25 -0
- package/packages/mailx-service/index.js +142 -5
- package/packages/mailx-service/jsonrpc.js +20 -1
- package/packages/mailx-settings/index.js +18 -3
- package/packages/mailx-store/db.d.ts +17 -0
- package/packages/mailx-store/db.js +122 -4
- package/packages/mailx-types/index.d.ts +1 -0
package/bin/mailx.js
CHANGED
|
@@ -130,6 +130,8 @@ if (!isDaemon && !__isCommandInvocation) {
|
|
|
130
130
|
if (!verbose && !isDaemon && !process.argv.slice(2).some(a => /^-/.test(a))) {
|
|
131
131
|
const { spawn } = await import("node:child_process");
|
|
132
132
|
const child = spawn(process.execPath, [...process.argv.slice(1), "--daemon"], {
|
|
133
|
+
// windowsHide on the spawn options below — prevents the brief
|
|
134
|
+
// console-window flash when the daemon launches.
|
|
133
135
|
detached: true,
|
|
134
136
|
stdio: "ignore",
|
|
135
137
|
windowsHide: true,
|
|
@@ -909,6 +911,53 @@ async function main() {
|
|
|
909
911
|
}
|
|
910
912
|
}
|
|
911
913
|
const db = new MailxDB(getConfigDir());
|
|
914
|
+
// Auto-create the sending/ recovery README on every startup. Stays in
|
|
915
|
+
// sync with the running version of mailx; user can ignore once the
|
|
916
|
+
// disk-staging fallback is no longer needed.
|
|
917
|
+
try {
|
|
918
|
+
const sendingDir = path.join(getConfigDir(), "sending");
|
|
919
|
+
fs.mkdirSync(sendingDir, { recursive: true });
|
|
920
|
+
const readmePath = path.join(sendingDir, "README.md");
|
|
921
|
+
const readmeBody = `# \`~/.mailx/sending/\` and \`~/.mailx/outbox/\` — outgoing-mail staging
|
|
922
|
+
|
|
923
|
+
Auto-generated by mailx on startup. Manual recovery reference for when mailx is broken or you need to feed an outgoing message into another mail program.
|
|
924
|
+
|
|
925
|
+
## Layout
|
|
926
|
+
|
|
927
|
+
\`\`\`
|
|
928
|
+
~/.mailx/
|
|
929
|
+
├── outbox/<account>/
|
|
930
|
+
│ └── *.ltr ← THE QUEUE. Worker scans every 10s, sends, deletes on success.
|
|
931
|
+
└── sending/<account>/
|
|
932
|
+
├── editing/ ← Last 3 draft autosaves while composing.
|
|
933
|
+
├── queued/ ← Manual drop-in / crash-recovery copies.
|
|
934
|
+
└── sent/ ← Audit trail of successfully sent messages.
|
|
935
|
+
\`\`\`
|
|
936
|
+
|
|
937
|
+
In-flight files are atomically renamed to \`<file>.sending-<host>-<pid>\` while the worker is processing them — same-machine claim so two mailx instances don't double-send. Stale claims (dead PIDs on this host) are recovered on the next tick.
|
|
938
|
+
|
|
939
|
+
## Manual fallback
|
|
940
|
+
|
|
941
|
+
- **mailx is dead, need to send a draft** — most recent file in \`sending/<account>/editing/\` is a complete RFC 822 message; copy the body into another mail client and resend.
|
|
942
|
+
- **Feed a raw .eml to mailx** — drop into \`sending/<account>/queued/\`. Picked up within 10s.
|
|
943
|
+
- **mailx says queued but server doesn't have it** — look in \`outbox/<account>/\`. \`.ltr\` still there → worker hasn't sent yet (check \`~/.mailx/logs/\`). \`.sending-<host>-<pid>\` → in flight. Gone → success.
|
|
944
|
+
|
|
945
|
+
## Format
|
|
946
|
+
|
|
947
|
+
RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable in a text editor). Every message carries \`Message-ID:\` for cross-device dedup; \`X-Mailx-Retry\` marks retry attempts.
|
|
948
|
+
`;
|
|
949
|
+
// Only rewrite if content drifted (avoids gratuitous mtime updates).
|
|
950
|
+
let existing = "";
|
|
951
|
+
try {
|
|
952
|
+
existing = fs.readFileSync(readmePath, "utf-8");
|
|
953
|
+
}
|
|
954
|
+
catch { /* missing */ }
|
|
955
|
+
if (existing !== readmeBody)
|
|
956
|
+
fs.writeFileSync(readmePath, readmeBody);
|
|
957
|
+
}
|
|
958
|
+
catch (e) {
|
|
959
|
+
console.error(` [readme] Could not write sending README: ${e?.message || e}`);
|
|
960
|
+
}
|
|
912
961
|
const { NodeTcpTransport } = await import("@bobfrankston/node-tcp-transport");
|
|
913
962
|
const imapManager = new ImapManager(db, () => new NodeTcpTransport());
|
|
914
963
|
// Native client is the only option (iflow-direct)
|
|
@@ -1023,7 +1072,7 @@ async function main() {
|
|
|
1023
1072
|
try {
|
|
1024
1073
|
clearInstanceFile();
|
|
1025
1074
|
const { spawn: spawnChild } = await import("child_process");
|
|
1026
|
-
const child = spawnChild("mailx", [], { detached: true, stdio: "ignore", shell: true });
|
|
1075
|
+
const child = spawnChild("mailx", [], { detached: true, stdio: "ignore", shell: true, windowsHide: true });
|
|
1027
1076
|
child.unref();
|
|
1028
1077
|
console.log(" [restart] Spawned fresh daemon; shutting down current");
|
|
1029
1078
|
// Give the spawn a moment to take hold before we start
|
|
@@ -1043,10 +1092,10 @@ async function main() {
|
|
|
1043
1092
|
try {
|
|
1044
1093
|
const { execSync, spawn: spawnChild } = await import("child_process");
|
|
1045
1094
|
console.log(" [update] Installing latest version...");
|
|
1046
|
-
execSync("npm install -g @bobfrankston/mailx", { encoding: "utf-8", timeout: 120_000, stdio: "inherit" });
|
|
1095
|
+
execSync("npm install -g @bobfrankston/mailx", { encoding: "utf-8", timeout: 120_000, stdio: "inherit", windowsHide: true });
|
|
1047
1096
|
console.log(" [update] Install complete — relaunching");
|
|
1048
1097
|
// Spawn the new version detached so it outlives this process
|
|
1049
|
-
const child = spawnChild("mailx", [], { detached: true, stdio: "ignore", shell: true });
|
|
1098
|
+
const child = spawnChild("mailx", [], { detached: true, stdio: "ignore", shell: true, windowsHide: true });
|
|
1050
1099
|
child.unref();
|
|
1051
1100
|
}
|
|
1052
1101
|
catch (e) {
|
|
@@ -1055,13 +1104,18 @@ async function main() {
|
|
|
1055
1104
|
gracefulShutdown("Update applied");
|
|
1056
1105
|
return;
|
|
1057
1106
|
}
|
|
1107
|
+
// Per-action wall-clock timing so a "took N seconds" report tells us
|
|
1108
|
+
// where between Rust→stdin→dispatch→service the time actually went.
|
|
1109
|
+
const ipcT0 = Date.now();
|
|
1058
1110
|
try {
|
|
1059
1111
|
const response = await dispatch(svc, req);
|
|
1060
|
-
|
|
1112
|
+
const elapsed = Date.now() - ipcT0;
|
|
1113
|
+
console.log(`[ipc] → ${req._action} (${req._cbid}) ok in ${elapsed}ms`);
|
|
1061
1114
|
handle.send(response);
|
|
1062
1115
|
}
|
|
1063
1116
|
catch (e) {
|
|
1064
|
-
|
|
1117
|
+
const elapsed = Date.now() - ipcT0;
|
|
1118
|
+
console.error(`[ipc] → ${req._action} (${req._cbid}) error in ${elapsed}ms: ${e.message}`);
|
|
1065
1119
|
handle.send({ _cbid: req._cbid, error: e.message });
|
|
1066
1120
|
}
|
|
1067
1121
|
});
|
|
@@ -1119,6 +1173,9 @@ async function main() {
|
|
|
1119
1173
|
imapManager.on("configChanged", (filename) => {
|
|
1120
1174
|
handle.send({ _event: "configChanged", type: "configChanged", filename });
|
|
1121
1175
|
});
|
|
1176
|
+
imapManager.on("outboxStatus", (status) => {
|
|
1177
|
+
handle.send({ _event: "outboxStatus", type: "outboxStatus", ...status });
|
|
1178
|
+
});
|
|
1122
1179
|
// syncComplete drives the folder-tree refresh that picks up newly-discovered
|
|
1123
1180
|
// folders on first run (Gmail accounts have no folders in the DB until the
|
|
1124
1181
|
// first sync fetches the labels). Without this forward, the UI shows the
|
|
@@ -1237,8 +1294,31 @@ async function main() {
|
|
|
1237
1294
|
const UPDATE_CHECK_MS = 30 * 60_000; // 30 minutes
|
|
1238
1295
|
async function checkForUpdate() {
|
|
1239
1296
|
try {
|
|
1240
|
-
|
|
1241
|
-
|
|
1297
|
+
// spawn with windowsHide:true — execSync briefly flashes a cmd
|
|
1298
|
+
// window on Windows every time the periodic check fires.
|
|
1299
|
+
const { spawn } = await import("child_process");
|
|
1300
|
+
const latest = await new Promise((resolve, reject) => {
|
|
1301
|
+
const child = spawn("npm", ["view", "@bobfrankston/mailx", "version"], {
|
|
1302
|
+
windowsHide: true,
|
|
1303
|
+
shell: true,
|
|
1304
|
+
});
|
|
1305
|
+
let out = "";
|
|
1306
|
+
let err = "";
|
|
1307
|
+
child.stdout.on("data", (d) => { out += d.toString(); });
|
|
1308
|
+
child.stderr.on("data", (d) => { err += d.toString(); });
|
|
1309
|
+
const killer = setTimeout(() => { try {
|
|
1310
|
+
child.kill();
|
|
1311
|
+
}
|
|
1312
|
+
catch { /* */ } reject(new Error("npm view timed out")); }, 15_000);
|
|
1313
|
+
child.on("error", (e) => { clearTimeout(killer); reject(e); });
|
|
1314
|
+
child.on("exit", (code) => {
|
|
1315
|
+
clearTimeout(killer);
|
|
1316
|
+
if (code === 0)
|
|
1317
|
+
resolve(out.trim());
|
|
1318
|
+
else
|
|
1319
|
+
reject(new Error(err.trim() || `npm view exit ${code}`));
|
|
1320
|
+
});
|
|
1321
|
+
});
|
|
1242
1322
|
const current = rootPkgVersion;
|
|
1243
1323
|
if (latest && latest !== current) {
|
|
1244
1324
|
console.log(` [update] New version available: ${current} → ${latest}`);
|