@bobfrankston/mailx 1.0.351 → 1.0.353
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/client/app.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { initFolderTree, refreshFolderTree, updateFolderCounts, setFolderSynced, getFolderSynced } from "./components/folder-tree.js";
|
|
6
6
|
import { initMessageList, loadMessages, loadUnifiedInbox, loadSearchResults, reloadCurrentFolder, getSelectedMessages, markBodiesCached } from "./components/message-list.js";
|
|
7
7
|
import { showMessage, getCurrentMessage, initViewer } from "./components/message-viewer.js";
|
|
8
|
-
import { connectWebSocket, onWsEvent, triggerSync, syncAccount, reauthenticate, getAccounts, getFolders, deleteMessages, undeleteMessage, restartServer, getSyncPending, getVersion, getSettings, saveSettings, getAutocompleteSettings, saveAutocompleteSettings, repairAccounts, updateFlags, markAsSpamMessages } from "./lib/api-client.js";
|
|
8
|
+
import { connectWebSocket, onWsEvent, triggerSync, syncAccount, reauthenticate, getAccounts, getFolders, deleteMessages, undeleteMessage, restartServer, getSyncPending, getVersion, getSettings, saveSettings, getAutocompleteSettings, saveAutocompleteSettings, repairAccounts, updateFlags, markAsSpamMessages, logClientEvent } from "./lib/api-client.js";
|
|
9
9
|
import * as messageState from "./lib/message-state.js";
|
|
10
10
|
// ── New message badge (favicon + title) ──
|
|
11
11
|
let baseTitle = "mailx";
|
|
@@ -524,6 +524,7 @@ document.getElementById("btn-factory-reset")?.addEventListener("click", async ()
|
|
|
524
524
|
}
|
|
525
525
|
});
|
|
526
526
|
async function openCompose(mode) {
|
|
527
|
+
logClientEvent("openCompose-entry", { mode });
|
|
527
528
|
const current = getCurrentMessage();
|
|
528
529
|
// Local-first: if the row is selected we already have its headers in the
|
|
529
530
|
// local DB. Populate the compose form unconditionally; the user can edit
|
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
* Receives init data via window.opener.postMessage or URL params.
|
|
5
5
|
*/
|
|
6
6
|
import { createEditor } from "./editor.js";
|
|
7
|
-
import { getSettings, getAccounts, searchContacts, sendMessage, saveDraft as apiSaveDraft, deleteDraft } from "../lib/api-client.js";
|
|
7
|
+
import { getSettings, getAccounts, searchContacts, sendMessage, saveDraft as apiSaveDraft, deleteDraft, logClientEvent } from "../lib/api-client.js";
|
|
8
|
+
// Very first line the iframe runs — if this doesn't reach Node, the iframe
|
|
9
|
+
// itself isn't loading or the bridge is completely broken.
|
|
10
|
+
logClientEvent("compose-module-loaded", { href: location.href, version: window.mailxVersion || "?" });
|
|
8
11
|
/** Close compose window */
|
|
9
12
|
function closeCompose() {
|
|
13
|
+
logClientEvent("compose-close");
|
|
10
14
|
window.close();
|
|
11
15
|
}
|
|
12
16
|
// ── Load editor scripts dynamically ──
|
|
@@ -576,6 +580,11 @@ window.addEventListener("blur", () => {
|
|
|
576
580
|
catch { /* */ }
|
|
577
581
|
});
|
|
578
582
|
document.getElementById("btn-send")?.addEventListener("click", () => {
|
|
583
|
+
// Loud tracing through the whole send pipeline. Every step ships a
|
|
584
|
+
// `[client] compose-send-*` event to the Node log so a "vanished message"
|
|
585
|
+
// report can be traced end-to-end without devtools. If the log stops
|
|
586
|
+
// at any step, that's where the pipeline broke.
|
|
587
|
+
logClientEvent("compose-send-click");
|
|
579
588
|
const body = {
|
|
580
589
|
from: getFromAccountId(),
|
|
581
590
|
fromAddress: getFromAddress(),
|
|
@@ -587,10 +596,12 @@ document.getElementById("btn-send")?.addEventListener("click", () => {
|
|
|
587
596
|
bodyText: editor.getText(),
|
|
588
597
|
attachments: attachments.map(a => ({ filename: a.filename, mimeType: a.mimeType, dataBase64: a.dataBase64 })),
|
|
589
598
|
};
|
|
599
|
+
logClientEvent("compose-send-body-built", { from: body.from, toCount: body.to.length, subjectLen: (body.subject || "").length, bodyHtmlLen: (body.bodyHtml || "").length, atts: body.attachments.length });
|
|
590
600
|
// Local validity (one missing-To check) — must run before close so the
|
|
591
601
|
// user gets an inline error instead of silent loss. Anything else (real
|
|
592
602
|
// address validation, MIME assembly, disk write) happens server-side.
|
|
593
603
|
if (!body.to.length) {
|
|
604
|
+
logClientEvent("compose-send-rejected-no-to");
|
|
594
605
|
alert("Please add at least one To recipient.");
|
|
595
606
|
return;
|
|
596
607
|
}
|
|
@@ -611,11 +622,14 @@ document.getElementById("btn-send")?.addEventListener("click", () => {
|
|
|
611
622
|
statusEl.textContent = "";
|
|
612
623
|
const sendStart = Date.now();
|
|
613
624
|
let ipcPromise;
|
|
625
|
+
logClientEvent("compose-send-pre-ipc");
|
|
614
626
|
try {
|
|
615
627
|
ipcPromise = sendMessage(body);
|
|
628
|
+
logClientEvent("compose-send-ipc-invoked", { promiseType: typeof ipcPromise, isThenable: !!(ipcPromise && typeof ipcPromise.then === "function") });
|
|
616
629
|
}
|
|
617
630
|
catch (e) {
|
|
618
631
|
const msg = e?.message || String(e);
|
|
632
|
+
logClientEvent("compose-send-sync-throw", { error: msg });
|
|
619
633
|
console.error(`[compose] Send threw synchronously: ${msg}`);
|
|
620
634
|
if (sendBtn) {
|
|
621
635
|
sendBtn.disabled = false;
|
|
@@ -629,6 +643,7 @@ document.getElementById("btn-send")?.addEventListener("click", () => {
|
|
|
629
643
|
}
|
|
630
644
|
Promise.resolve(ipcPromise)
|
|
631
645
|
.then(() => {
|
|
646
|
+
logClientEvent("compose-send-ipc-resolved", { ms: Date.now() - sendStart });
|
|
632
647
|
console.log(`[compose] Send IPC returned OK in ${Date.now() - sendStart}ms`);
|
|
633
648
|
// Stop autosave only after ACK — if send threw we want the draft
|
|
634
649
|
// autosave to keep the message safe.
|
|
@@ -643,6 +658,7 @@ document.getElementById("btn-send")?.addEventListener("click", () => {
|
|
|
643
658
|
})
|
|
644
659
|
.catch((e) => {
|
|
645
660
|
const msg = e?.message || String(e);
|
|
661
|
+
logClientEvent("compose-send-ipc-rejected", { error: msg, ms: Date.now() - sendStart });
|
|
646
662
|
console.error(`[compose] Send IPC failed after ${Date.now() - sendStart}ms: ${msg}`);
|
|
647
663
|
if (sendBtn) {
|
|
648
664
|
sendBtn.disabled = false;
|
package/client/lib/api-client.js
CHANGED
|
@@ -129,6 +129,20 @@ export function deleteFolder(accountId, folderId) {
|
|
|
129
129
|
export function emptyFolder(accountId, folderId) {
|
|
130
130
|
return ipc().emptyFolder?.(accountId, folderId);
|
|
131
131
|
}
|
|
132
|
+
/** Ship a named event to the Node log as `[client] <tag> <data>`. Fire and
|
|
133
|
+
* forget — never awaits, never throws, never blocks the caller. If the IPC
|
|
134
|
+
* bridge is unavailable, the call is a no-op so tracing calls scattered
|
|
135
|
+
* through the UI don't become failure points themselves. */
|
|
136
|
+
export function logClientEvent(tag, data) {
|
|
137
|
+
try {
|
|
138
|
+
const bridge = typeof globalThis.mailxapi !== "undefined" && globalThis.mailxapi?.isApp ? globalThis.mailxapi
|
|
139
|
+
: window.opener?.mailxapi?.isApp ? window.opener.mailxapi
|
|
140
|
+
: window.parent?.mailxapi?.isApp ? window.parent.mailxapi
|
|
141
|
+
: null;
|
|
142
|
+
bridge?.logClientEvent?.(tag, data);
|
|
143
|
+
}
|
|
144
|
+
catch { /* never throw from tracing */ }
|
|
145
|
+
}
|
|
132
146
|
export function sendMessage(body) {
|
|
133
147
|
return ipc().sendMessage?.(body);
|
|
134
148
|
}
|
package/client/lib/mailxapi.js
CHANGED
|
@@ -86,6 +86,12 @@
|
|
|
86
86
|
return callNode("undeleteMessage", { accountId: accountId, uid: uid, folderId: folderId });
|
|
87
87
|
},
|
|
88
88
|
|
|
89
|
+
// Diagnostic tracing — callers (compose iframe, app, components) can
|
|
90
|
+
// ship arbitrary named events to the Node log. Surfaces as
|
|
91
|
+
// `[client] <tag> <data>` so a failing pipeline can be traced end
|
|
92
|
+
// to end in a single log file.
|
|
93
|
+
logClientEvent: function(tag, data) { return callNode("logClientEvent", { tag: tag, data: data }); },
|
|
94
|
+
|
|
89
95
|
// Compose
|
|
90
96
|
sendMessage: function(msg) { return callNode("sendMessage", msg); },
|
|
91
97
|
saveDraft: function(params) { return callNode("saveDraft", params); },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.353",
|
|
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,7 +20,7 @@
|
|
|
20
20
|
"postinstall": "node bin/postinstall.js"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
23
|
+
"@bobfrankston/iflow-direct": "^0.1.26",
|
|
24
24
|
"@bobfrankston/iflow-node": "^0.1.7",
|
|
25
25
|
"@bobfrankston/miscinfo": "^1.0.9",
|
|
26
26
|
"@bobfrankston/oauthsupport": "^1.0.24",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
},
|
|
85
85
|
".transformedSnapshot": {
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
87
|
+
"@bobfrankston/iflow-direct": "^0.1.26",
|
|
88
88
|
"@bobfrankston/iflow-node": "^0.1.7",
|
|
89
89
|
"@bobfrankston/miscinfo": "^1.0.9",
|
|
90
90
|
"@bobfrankston/oauthsupport": "^1.0.24",
|
|
@@ -128,6 +128,14 @@ async function dispatchAction(svc, action, p) {
|
|
|
128
128
|
return { content: await svc.readConfigHelp(p.name) };
|
|
129
129
|
case "unsubscribeOneClick":
|
|
130
130
|
return await svc.unsubscribeOneClick(p.url);
|
|
131
|
+
// Client-side tracing — lets webview / iframe code ship events to the
|
|
132
|
+
// Node log so a "compose→send→vanished" report can be diagnosed without
|
|
133
|
+
// opening devtools. Every call shows up as `[client] <tag> <data>` in
|
|
134
|
+
// the main log. Keep it on the top of the switch so it's cheap + first
|
|
135
|
+
// to dispatch.
|
|
136
|
+
case "logClientEvent":
|
|
137
|
+
console.log(` [client] ${p.tag || "?"}${p.data ? " " + JSON.stringify(p.data).slice(0, 400) : ""}`);
|
|
138
|
+
return { ok: true };
|
|
131
139
|
// Settings
|
|
132
140
|
case "getSettings":
|
|
133
141
|
return svc.getSettings();
|