@bobfrankston/mailx 1.0.353 → 1.0.355
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 +35 -1
- package/client/compose/compose.css +8 -0
- package/client/compose/compose.js +39 -19
- package/client/lib/api-client.js +18 -4
- package/package.json +1 -1
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, logClientEvent } 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, sendMessage as apiSendMessage } 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";
|
|
@@ -1144,6 +1144,40 @@ if (ftFilterInput) {
|
|
|
1144
1144
|
}
|
|
1145
1145
|
// ── Open links from email body in system browser ──
|
|
1146
1146
|
window.addEventListener("message", (e) => {
|
|
1147
|
+
// Relay traces from iframes (compose) to Node via our working bridge.
|
|
1148
|
+
// The iframe calls logClientEvent which tries its own bridge first; if
|
|
1149
|
+
// that path is broken it also posts here as backup. Tag gets a `via-relay`
|
|
1150
|
+
// suffix when the iframe couldn't reach its own bridge — that alone
|
|
1151
|
+
// diagnoses whether the iframe bridge works.
|
|
1152
|
+
if (e.data?.type === "mailx-trace" && typeof e.data.tag === "string") {
|
|
1153
|
+
const relayTag = e.data.bridged ? e.data.tag : `${e.data.tag} (via-relay)`;
|
|
1154
|
+
logClientEvent(relayTag, e.data.data);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
// Compose-send relay: iframe posts the send request here because its own
|
|
1158
|
+
// bridge call to sendMessage was failing to reach Node. This window's
|
|
1159
|
+
// bridge is proven (getAccounts / getOutboxStatus run every few seconds
|
|
1160
|
+
// with no failures), so we do the IPC from here and post the result back
|
|
1161
|
+
// to the iframe via its source. `e.source` is the iframe's window; use it
|
|
1162
|
+
// so targeting works even if the iframe moves in the DOM.
|
|
1163
|
+
if (e.data?.type === "mailx-compose-send" && e.data.id && e.data.body) {
|
|
1164
|
+
const src = e.source;
|
|
1165
|
+
const id = e.data.id;
|
|
1166
|
+
logClientEvent("relay-compose-send-received", { id });
|
|
1167
|
+
(async () => {
|
|
1168
|
+
try {
|
|
1169
|
+
await apiSendMessage(e.data.body);
|
|
1170
|
+
logClientEvent("relay-compose-send-ok", { id });
|
|
1171
|
+
src?.postMessage({ type: "mailx-compose-send-result", id, ok: true }, "*");
|
|
1172
|
+
}
|
|
1173
|
+
catch (err) {
|
|
1174
|
+
const msg = err?.message || String(err);
|
|
1175
|
+
logClientEvent("relay-compose-send-error", { id, error: msg });
|
|
1176
|
+
src?.postMessage({ type: "mailx-compose-send-result", id, ok: false, error: msg }, "*");
|
|
1177
|
+
}
|
|
1178
|
+
})();
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1147
1181
|
if (e.data?.type === "openLink" && e.data.url) {
|
|
1148
1182
|
window.open(e.data.url, "_blank", "noopener,noreferrer");
|
|
1149
1183
|
}
|
|
@@ -75,6 +75,14 @@ body {
|
|
|
75
75
|
padding: var(--gap-xs) 0;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/* HTML `hidden` attribute is display:none by UA default, but the `display:
|
|
79
|
+
flex` above wins specificity-wise and the row stays visible. Restore the
|
|
80
|
+
expected hide behavior — the Cc/Bcc toggle buttons flip `hidden` to
|
|
81
|
+
reveal/conceal the rows, which is pointless if CSS forces them on. */
|
|
82
|
+
.compose-field[hidden] {
|
|
83
|
+
display: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
78
86
|
.compose-field label {
|
|
79
87
|
flex: 0 0 60px;
|
|
80
88
|
font-size: var(--font-size-sm);
|
|
@@ -4,7 +4,7 @@
|
|
|
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,
|
|
7
|
+
import { getSettings, getAccounts, searchContacts, saveDraft as apiSaveDraft, deleteDraft, logClientEvent } from "../lib/api-client.js";
|
|
8
8
|
// Very first line the iframe runs — if this doesn't reach Node, the iframe
|
|
9
9
|
// itself isn't loading or the bridge is completely broken.
|
|
10
10
|
logClientEvent("compose-module-loaded", { href: location.href, version: window.mailxVersion || "?" });
|
|
@@ -621,26 +621,46 @@ document.getElementById("btn-send")?.addEventListener("click", () => {
|
|
|
621
621
|
if (statusEl)
|
|
622
622
|
statusEl.textContent = "";
|
|
623
623
|
const sendStart = Date.now();
|
|
624
|
-
let ipcPromise;
|
|
625
624
|
logClientEvent("compose-send-pre-ipc");
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
625
|
+
// Parent-window relay for send. Empirical observation: Android (direct
|
|
626
|
+
// in-process SMTP) sends reliably; desktop (iframe → parent.mailxapi →
|
|
627
|
+
// msger → Node → service) has sendMessage IPCs failing to reach Node
|
|
628
|
+
// for reasons still unknown (iframe bridge behaves differently from the
|
|
629
|
+
// top frame in msger's WebView2 for this specific call). Meanwhile the
|
|
630
|
+
// parent window's bridge is proven — getAccounts / getOutboxStatus run
|
|
631
|
+
// through it every few seconds with no failures.
|
|
632
|
+
//
|
|
633
|
+
// Fix: the iframe doesn't touch the bridge at all for send. It posts
|
|
634
|
+
// a request to the parent, and the parent calls the real sendMessage
|
|
635
|
+
// from its own frame. Parent posts the result back. This bypasses
|
|
636
|
+
// whatever is wrong with iframe-scoped IPC.
|
|
637
|
+
const ipcPromise = new Promise((resolve, reject) => {
|
|
638
|
+
const reqId = `send-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
639
|
+
const timer = setTimeout(() => {
|
|
640
|
+
window.removeEventListener("message", onMsg);
|
|
641
|
+
reject(new Error("parent-relay send timeout (120s)"));
|
|
642
|
+
}, 120000);
|
|
643
|
+
const onMsg = (ev) => {
|
|
644
|
+
if (!ev.data || ev.data.type !== "mailx-compose-send-result" || ev.data.id !== reqId)
|
|
645
|
+
return;
|
|
646
|
+
clearTimeout(timer);
|
|
647
|
+
window.removeEventListener("message", onMsg);
|
|
648
|
+
if (ev.data.ok)
|
|
649
|
+
resolve();
|
|
650
|
+
else
|
|
651
|
+
reject(new Error(ev.data.error || "unknown"));
|
|
652
|
+
};
|
|
653
|
+
window.addEventListener("message", onMsg);
|
|
654
|
+
try {
|
|
655
|
+
parent.postMessage({ type: "mailx-compose-send", id: reqId, body }, "*");
|
|
656
|
+
logClientEvent("compose-send-ipc-invoked", { via: "parent-relay", reqId });
|
|
637
657
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
}
|
|
658
|
+
catch (e) {
|
|
659
|
+
clearTimeout(timer);
|
|
660
|
+
window.removeEventListener("message", onMsg);
|
|
661
|
+
reject(e);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
644
664
|
Promise.resolve(ipcPromise)
|
|
645
665
|
.then(() => {
|
|
646
666
|
logClientEvent("compose-send-ipc-resolved", { ms: Date.now() - sendStart });
|
package/client/lib/api-client.js
CHANGED
|
@@ -130,18 +130,32 @@ export function emptyFolder(accountId, folderId) {
|
|
|
130
130
|
return ipc().emptyFolder?.(accountId, folderId);
|
|
131
131
|
}
|
|
132
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.
|
|
134
|
-
*
|
|
135
|
-
*
|
|
133
|
+
* forget — never awaits, never throws, never blocks the caller. Tries two
|
|
134
|
+
* paths so a broken primary channel can't swallow the trace:
|
|
135
|
+
* 1. Direct bridge call (self / opener / parent mailxapi).
|
|
136
|
+
* 2. parent.postMessage fallback — the main window listens and relays.
|
|
137
|
+
* The fallback matters because the whole point of tracing is to diagnose
|
|
138
|
+
* a broken iframe bridge; a single-path tracer that goes through that same
|
|
139
|
+
* bridge is useless in exactly the case we need it. */
|
|
136
140
|
export function logClientEvent(tag, data) {
|
|
141
|
+
let delivered = false;
|
|
137
142
|
try {
|
|
138
143
|
const bridge = typeof globalThis.mailxapi !== "undefined" && globalThis.mailxapi?.isApp ? globalThis.mailxapi
|
|
139
144
|
: window.opener?.mailxapi?.isApp ? window.opener.mailxapi
|
|
140
145
|
: window.parent?.mailxapi?.isApp ? window.parent.mailxapi
|
|
141
146
|
: null;
|
|
142
|
-
bridge?.logClientEvent
|
|
147
|
+
if (bridge?.logClientEvent) {
|
|
148
|
+
bridge.logClientEvent(tag, data);
|
|
149
|
+
delivered = true;
|
|
150
|
+
}
|
|
143
151
|
}
|
|
144
152
|
catch { /* never throw from tracing */ }
|
|
153
|
+
try {
|
|
154
|
+
if (window.parent && window.parent !== window) {
|
|
155
|
+
window.parent.postMessage({ type: "mailx-trace", tag, data, bridged: delivered }, "*");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch { /* */ }
|
|
145
159
|
}
|
|
146
160
|
export function sendMessage(body) {
|
|
147
161
|
return ipc().sendMessage?.(body);
|