@bobfrankston/rmfmail 1.1.213 → 1.1.214

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.
@@ -53,33 +53,67 @@
53
53
  // turned a slow-but-fine search into "mailxapi timeout:
54
54
  // searchMessages". Local search stays fast and well under any limit.
55
55
  searchMessages: 120000,
56
- // getVersion reads package.json — instant. The only way it "times out"
57
- // is msger silently dropping the IPC (see api-client note). A short
58
- // ceiling makes the client detect the drop fast and retry, instead of
59
- // staring at "(??)" + a scary "service error" banner for 30s (Bob
60
- // 2026-06-02). The app.ts version display retries on this rejection.
56
+ // getVersion reads package.json — instant. With the startup-race
57
+ // re-send above (it's in RESENDABLE) a dropped post is re-tried every
58
+ // 2s, so a 5s ceiling normally allows ~2 re-sends before giving up —
59
+ // enough to ride out the brief window before the Rust→daemon pipe is
60
+ // wired. Short ceiling so a genuinely wedged daemon surfaces fast
61
+ // rather than hanging the version display for 30s.
61
62
  getVersion: 5000,
62
63
  };
64
+ // Actions that are PURE READS and therefore safe to auto-re-send if the
65
+ // first post is lost. Why a post gets lost: at startup the WebView can
66
+ // call `window.ipc.postMessage` after `window.ipc` is injected but BEFORE
67
+ // the Rust host has wired its message handler to the daemon's stdin — the
68
+ // post is accepted by WebView2 and silently dropped downstream, so no
69
+ // response ever comes and the callback times out. That's the real cause of
70
+ // "getVersion got dropped" (Bob 2026-06-03) — a startup race in our own
71
+ // bridge, not a mysterious msger behavior. Re-sending a read is harmless
72
+ // (the daemon ignores a duplicate response — _msgapiServiceResolve no-ops
73
+ // when the callback is already gone). WRITES are NOT listed: re-sending a
74
+ // send/saveDraft/delete/move/flag could double-apply.
75
+ var RESENDABLE = {
76
+ getVersion: 1, getAccounts: 1, getFolders: 1, getUnifiedInbox: 1, getMessages: 1,
77
+ getSettings: 1, getAutocompleteSettings: 1, getDiagnostics: 1, getOutboxStatus: 1,
78
+ getSyncPending: 1, getCalendars: 1, getCalendarEvents: 1, getTasks: 1,
79
+ getPrimaryAccount: 1, getThreadMessages: 1, getPriorityLists: 1
80
+ };
81
+ var RESEND_INTERVAL_MS = 2000;
63
82
  function callNode(action, params) {
64
83
  var id = String(++_callbackId);
84
+ var msgStr = JSON.stringify(Object.assign({ _action: action, _cbid: id }, params || {}));
65
85
  return new Promise(function(resolve, reject) {
66
- var timer = setTimeout(function() {
86
+ function send() {
87
+ if (window.ipc && window.ipc.postMessage) {
88
+ // Trace BEFORE the call so a slow daemon-receive shows up as
89
+ // ipc-send + N s before the daemon logs `[ipc] ←`. Skip the
90
+ // trace event itself to avoid recursion.
91
+ if (action !== "logClientEvent") _traceIpcSend(action, id);
92
+ window.ipc.postMessage(msgStr);
93
+ return true;
94
+ }
95
+ return false;
96
+ }
97
+ var entry = { resolve: resolve, reject: reject, timer: null, resend: null };
98
+ _callbacks[id] = entry;
99
+ entry.timer = setTimeout(function() {
67
100
  delete _callbacks[id];
101
+ if (entry.resend) clearInterval(entry.resend);
68
102
  reject(new Error("mailxapi timeout: " + action));
69
103
  }, ACTION_TIMEOUT_MS[action] || 30000);
70
- _callbacks[id] = { resolve: resolve, reject: reject, timer: timer };
71
- var msg = Object.assign({ _action: action, _cbid: id }, params || {});
72
- if (window.ipc && window.ipc.postMessage) {
73
- // Trace BEFORE the actual call so a slow daemon-receive shows
74
- // up as ipc-send wall-clock + N seconds before the daemon
75
- // logs `[ipc] ←`. Skip tracing on the trace event itself so
76
- // we don't recurse.
77
- if (action !== "logClientEvent") _traceIpcSend(action, id);
78
- window.ipc.postMessage(JSON.stringify(msg));
79
- } else {
80
- clearTimeout(timer);
104
+ var sent = send();
105
+ if (!sent && !RESENDABLE[action]) {
106
+ // Channel object not injected yet AND not safe to wait/re-send
107
+ // (a write) fail now rather than risk it.
108
+ clearTimeout(entry.timer);
81
109
  delete _callbacks[id];
82
110
  reject(new Error("No IPC channel available"));
111
+ return;
112
+ }
113
+ // Re-send pure reads until a response lands or the ceiling fires —
114
+ // heals a startup-race drop (and a not-yet-injected window.ipc).
115
+ if (RESENDABLE[action]) {
116
+ entry.resend = setInterval(send, RESEND_INTERVAL_MS);
83
117
  }
84
118
  });
85
119
  }
@@ -90,6 +124,7 @@
90
124
  if (!cb) return;
91
125
  delete _callbacks[id];
92
126
  clearTimeout(cb.timer);
127
+ if (cb.resend) clearInterval(cb.resend);
93
128
  cb.resolve(value);
94
129
  };
95
130
 
@@ -99,6 +134,7 @@
99
134
  if (!cb) return;
100
135
  delete _callbacks[id];
101
136
  clearTimeout(cb.timer);
137
+ if (cb.resend) clearInterval(cb.resend);
102
138
  cb.reject(new Error(error));
103
139
  };
104
140
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/rmfmail",
3
- "version": "1.1.213",
3
+ "version": "1.1.214",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",