@bobfrankston/mailx 1.0.168 → 1.0.169

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 } from "./components/folder-tree.js";
6
6
  import { initMessageList, loadMessages, loadUnifiedInbox, loadSearchResults, reloadCurrentFolder, getSelectedMessages } from "./components/message-list.js";
7
7
  import { showMessage, getCurrentMessage } from "./components/message-viewer.js";
8
- import { connectWebSocket, onWsEvent, triggerSync, getAccounts, getFolders, deleteMessages, undeleteMessage, restartServer, rebuildServer, getSyncPending, getVersion, getSettings, saveSettings, getAutocompleteSettings, saveAutocompleteSettings, repairAccounts } from "./lib/api-client.js";
8
+ import { connectWebSocket, onWsEvent, triggerSync, syncAccount, reauthenticate, getAccounts, getFolders, deleteMessages, undeleteMessage, restartServer, rebuildServer, getSyncPending, getVersion, getSettings, saveSettings, getAutocompleteSettings, saveAutocompleteSettings, repairAccounts } from "./lib/api-client.js";
9
9
  // ── New message badge (favicon + title) ──
10
10
  let baseTitle = "mailx";
11
11
  let lastSeenCount = 0;
@@ -641,8 +641,7 @@ onWsEvent((event) => {
641
641
  btn.disabled = true;
642
642
  btn.textContent = "Authenticating...";
643
643
  try {
644
- const res = await fetch(`/api/reauth/${event.accountId}`, { method: "POST" });
645
- const data = await res.json();
644
+ const data = await reauthenticate(event.accountId);
646
645
  if (data.ok) {
647
646
  hideAlert();
648
647
  const acctEl = document.getElementById("status-accounts");
@@ -668,8 +667,7 @@ onWsEvent((event) => {
668
667
  btn.disabled = true;
669
668
  btn.textContent = "Syncing...";
670
669
  try {
671
- const res = await fetch(`/api/sync/${event.accountId}`, { method: "POST" });
672
- const data = await res.json();
670
+ const data = await syncAccount(event.accountId);
673
671
  if (data.ok) {
674
672
  hideAlert();
675
673
  const acctEl = document.getElementById("status-accounts");
@@ -6,7 +6,9 @@
6
6
  * All server operations MUST go through these centralized methods.
7
7
  * Never use fetch("/api/...") directly in components.
8
8
  */
9
- const hasIPC = typeof mailxapi !== "undefined" && mailxapi?.isApp;
9
+ // In popup windows (compose), mailxapi isn't injected fall back to opener's bridge
10
+ const _ipc = typeof mailxapi !== "undefined" ? mailxapi : window.opener?.mailxapi;
11
+ const hasIPC = _ipc?.isApp === true;
10
12
  // ── HTTP fallback ──
11
13
  // Abort controller for message-list requests — cancel stale fetches when folder changes
12
14
  let messageListAbort = null;
@@ -44,29 +46,29 @@ async function api(path, options) {
44
46
  // ── API Methods (IPC or HTTP) ──
45
47
  export function getAccounts() {
46
48
  if (hasIPC)
47
- return mailxapi.getAccounts();
49
+ return _ipc.getAccounts();
48
50
  return api("/accounts");
49
51
  }
50
52
  export function getFolders(accountId) {
51
53
  if (hasIPC)
52
- return mailxapi.getFolders(accountId);
54
+ return _ipc.getFolders(accountId);
53
55
  return api(`/folders/${accountId}`);
54
56
  }
55
57
  export function getMessages(accountId, folderId, page = 1, pageSize = 50) {
56
58
  if (hasIPC)
57
- return mailxapi.getMessages(accountId, folderId, page, pageSize);
59
+ return _ipc.getMessages(accountId, folderId, page, pageSize);
58
60
  const signal = newMessageListSignal();
59
61
  return api(`/messages/${accountId}/${folderId}?page=${page}&pageSize=${pageSize}`, { signal });
60
62
  }
61
63
  export function getUnifiedInbox(page = 1, pageSize = 50) {
62
64
  if (hasIPC)
63
- return mailxapi.getUnifiedInbox(page, pageSize);
65
+ return _ipc.getUnifiedInbox(page, pageSize);
64
66
  const signal = newMessageListSignal();
65
67
  return api(`/messages/unified/inbox?page=${page}&pageSize=${pageSize}`, { signal });
66
68
  }
67
69
  export function searchMessages(query, page = 1, pageSize = 50, scope = "all", accountId = "", folderId = 0) {
68
70
  if (hasIPC)
69
- return mailxapi.searchMessages(query, page, pageSize);
71
+ return _ipc.searchMessages(query, page, pageSize);
70
72
  const params = new URLSearchParams({ q: query, page: String(page), pageSize: String(pageSize), scope });
71
73
  if (scope === "current" && accountId) {
72
74
  params.set("accountId", accountId);
@@ -80,7 +82,7 @@ export function searchMessages(query, page = 1, pageSize = 50, scope = "all", ac
80
82
  }
81
83
  export function getMessage(accountId, uid, allowRemote = false, folderId) {
82
84
  if (hasIPC)
83
- return mailxapi.getMessage(accountId, uid, allowRemote, folderId);
85
+ return _ipc.getMessage(accountId, uid, allowRemote, folderId);
84
86
  const params = new URLSearchParams();
85
87
  if (allowRemote)
86
88
  params.set("allowRemote", "true");
@@ -91,7 +93,7 @@ export function getMessage(accountId, uid, allowRemote = false, folderId) {
91
93
  }
92
94
  export function updateFlags(accountId, uid, flags) {
93
95
  if (hasIPC)
94
- return mailxapi.updateFlags(accountId, uid, flags);
96
+ return _ipc.updateFlags(accountId, uid, flags);
95
97
  return api(`/message/${accountId}/${uid}/flags`, {
96
98
  method: "PATCH",
97
99
  body: JSON.stringify({ flags })
@@ -99,22 +101,32 @@ export function updateFlags(accountId, uid, flags) {
99
101
  }
100
102
  export function triggerSync() {
101
103
  if (hasIPC)
102
- return mailxapi.syncAll();
104
+ return _ipc.syncAll();
103
105
  return api("/sync", { method: "POST" });
104
106
  }
107
+ export function syncAccount(accountId) {
108
+ if (hasIPC)
109
+ return _ipc.syncAccount(accountId);
110
+ return api(`/sync/${accountId}`, { method: "POST" });
111
+ }
112
+ export function reauthenticate(accountId) {
113
+ if (hasIPC)
114
+ return _ipc.reauthenticate(accountId);
115
+ return api(`/reauth/${accountId}`, { method: "POST" });
116
+ }
105
117
  export function getSyncPending() {
106
118
  if (hasIPC)
107
- return mailxapi.getSyncPending();
119
+ return _ipc.getSyncPending();
108
120
  return api("/sync/pending");
109
121
  }
110
122
  export function searchContacts(query) {
111
123
  if (hasIPC)
112
- return mailxapi.searchContacts(query);
124
+ return _ipc.searchContacts(query);
113
125
  return api(`/contacts?q=${encodeURIComponent(query)}`);
114
126
  }
115
127
  export function allowRemoteContent(type, value) {
116
128
  if (hasIPC)
117
- return mailxapi.allowRemoteContent(type, value);
129
+ return _ipc.allowRemoteContent(type, value);
118
130
  return api("/settings/allow-remote", {
119
131
  method: "POST",
120
132
  body: JSON.stringify({ type, value })
@@ -122,14 +134,14 @@ export function allowRemoteContent(type, value) {
122
134
  }
123
135
  export function deleteMessage(accountId, uid) {
124
136
  if (hasIPC)
125
- return mailxapi.deleteMessage?.(accountId, uid);
137
+ return _ipc.deleteMessage?.(accountId, uid);
126
138
  return api(`/message/${accountId}/${uid}`, { method: "DELETE" });
127
139
  }
128
140
  export function deleteMessages(accountId, uids) {
129
141
  if (uids.length === 1)
130
142
  return deleteMessage(accountId, uids[0]);
131
143
  if (hasIPC)
132
- return mailxapi.deleteMessages?.(accountId, uids);
144
+ return _ipc.deleteMessages?.(accountId, uids);
133
145
  return api("/messages/delete", {
134
146
  method: "POST", body: JSON.stringify({ accountId, uids })
135
147
  });
@@ -138,7 +150,7 @@ export function moveMessages(accountId, uids, targetFolderId, targetAccountId) {
138
150
  if (uids.length === 1)
139
151
  return moveMessage(accountId, uids[0], targetFolderId, targetAccountId);
140
152
  if (hasIPC)
141
- return mailxapi.moveMessages?.(accountId, uids, targetFolderId, targetAccountId);
153
+ return _ipc.moveMessages?.(accountId, uids, targetFolderId, targetAccountId);
142
154
  const body = { accountId, uids, targetFolderId };
143
155
  if (targetAccountId)
144
156
  body.targetAccountId = targetAccountId;
@@ -148,7 +160,7 @@ export function moveMessages(accountId, uids, targetFolderId, targetAccountId) {
148
160
  }
149
161
  export function undeleteMessage(accountId, uid, folderId) {
150
162
  if (hasIPC)
151
- return mailxapi.undeleteMessage?.(accountId, uid, folderId);
163
+ return _ipc.undeleteMessage?.(accountId, uid, folderId);
152
164
  return api(`/message/${accountId}/${uid}/undelete`, {
153
165
  method: "POST",
154
166
  body: JSON.stringify({ folderId })
@@ -156,7 +168,7 @@ export function undeleteMessage(accountId, uid, folderId) {
156
168
  }
157
169
  export function moveMessage(accountId, uid, targetFolderId, targetAccountId) {
158
170
  if (hasIPC)
159
- return mailxapi.moveMessage?.(accountId, uid, targetFolderId, targetAccountId);
171
+ return _ipc.moveMessage?.(accountId, uid, targetFolderId, targetAccountId);
160
172
  const body = { targetFolderId };
161
173
  if (targetAccountId)
162
174
  body.targetAccountId = targetAccountId;
@@ -167,7 +179,7 @@ export function moveMessage(accountId, uid, targetFolderId, targetAccountId) {
167
179
  }
168
180
  export function restartServer() {
169
181
  if (hasIPC)
170
- return mailxapi.restart?.();
182
+ return _ipc.restart?.();
171
183
  return api("/restart", { method: "POST" }).catch(() => { });
172
184
  }
173
185
  export function rebuildServer() {
@@ -175,12 +187,12 @@ export function rebuildServer() {
175
187
  }
176
188
  export function markFolderRead(accountId, folderId) {
177
189
  if (hasIPC)
178
- return mailxapi.markFolderRead?.(accountId, folderId);
190
+ return _ipc.markFolderRead?.(accountId, folderId);
179
191
  return api(`/folder/${accountId}/${folderId}/mark-read`, { method: "POST" });
180
192
  }
181
193
  export function createFolder(accountId, parentPath, name) {
182
194
  if (hasIPC)
183
- return mailxapi.createFolder?.(accountId, parentPath, name);
195
+ return _ipc.createFolder?.(accountId, parentPath, name);
184
196
  return api(`/folder/${accountId}`, {
185
197
  method: "POST",
186
198
  body: JSON.stringify({ parentPath, name })
@@ -188,7 +200,7 @@ export function createFolder(accountId, parentPath, name) {
188
200
  }
189
201
  export function renameFolder(accountId, folderId, newName) {
190
202
  if (hasIPC)
191
- return mailxapi.renameFolder?.(accountId, folderId, newName);
203
+ return _ipc.renameFolder?.(accountId, folderId, newName);
192
204
  return api(`/folder/${accountId}/${folderId}/rename`, {
193
205
  method: "POST",
194
206
  body: JSON.stringify({ newName })
@@ -196,22 +208,22 @@ export function renameFolder(accountId, folderId, newName) {
196
208
  }
197
209
  export function deleteFolder(accountId, folderId) {
198
210
  if (hasIPC)
199
- return mailxapi.deleteFolder?.(accountId, folderId);
211
+ return _ipc.deleteFolder?.(accountId, folderId);
200
212
  return api(`/folder/${accountId}/${folderId}`, { method: "DELETE" });
201
213
  }
202
214
  export function emptyFolder(accountId, folderId) {
203
215
  if (hasIPC)
204
- return mailxapi.emptyFolder?.(accountId, folderId);
216
+ return _ipc.emptyFolder?.(accountId, folderId);
205
217
  return api(`/folder/${accountId}/${folderId}/empty`, { method: "POST" });
206
218
  }
207
219
  export function sendMessage(body) {
208
220
  if (hasIPC)
209
- return mailxapi.sendMessage?.(body);
221
+ return _ipc.sendMessage?.(body);
210
222
  return api("/send", { method: "POST", body: JSON.stringify(body) });
211
223
  }
212
224
  export function saveDraft(body) {
213
225
  if (hasIPC)
214
- return mailxapi.saveDraft?.(body);
226
+ return _ipc.saveDraft?.(body);
215
227
  return api("/draft", { method: "POST", body: JSON.stringify(body) });
216
228
  }
217
229
  const eventHandlers = [];
@@ -221,7 +233,7 @@ export function onEvent(handler) {
221
233
  export function connectEvents() {
222
234
  if (hasIPC) {
223
235
  // IPC events come via mailxapi.onEvent
224
- mailxapi.onEvent((event) => {
236
+ _ipc.onEvent((event) => {
225
237
  for (const h of eventHandlers)
226
238
  h(event);
227
239
  });
@@ -246,47 +258,47 @@ export function connectEvents() {
246
258
  // ── Autocomplete ──
247
259
  export function autocomplete(body, signal) {
248
260
  if (hasIPC)
249
- return mailxapi.autocomplete?.(body);
261
+ return _ipc.autocomplete?.(body);
250
262
  return api("/autocomplete", { method: "POST", body: JSON.stringify(body), signal });
251
263
  }
252
264
  export function getAutocompleteSettings() {
253
265
  if (hasIPC)
254
- return mailxapi.getAutocompleteSettings?.();
266
+ return _ipc.getAutocompleteSettings?.();
255
267
  return api("/autocomplete/settings");
256
268
  }
257
269
  export function saveAutocompleteSettings(settings) {
258
270
  if (hasIPC)
259
- return mailxapi.saveAutocompleteSettings?.(settings);
271
+ return _ipc.saveAutocompleteSettings?.(settings);
260
272
  return api("/autocomplete/settings", { method: "POST", body: JSON.stringify(settings) });
261
273
  }
262
274
  export function getVersion() {
263
275
  if (hasIPC)
264
- return mailxapi.getVersion();
276
+ return _ipc.getVersion();
265
277
  return api("/version");
266
278
  }
267
279
  export function getSettings() {
268
280
  if (hasIPC)
269
- return mailxapi.getSettings();
281
+ return _ipc.getSettings();
270
282
  return api("/settings");
271
283
  }
272
284
  export function saveSettings(settings) {
273
285
  if (hasIPC)
274
- return mailxapi.saveSettingsData?.(settings);
286
+ return _ipc.saveSettingsData?.(settings);
275
287
  return api("/settings", { method: "PUT", body: JSON.stringify(settings) });
276
288
  }
277
289
  export function repairAccounts() {
278
290
  if (hasIPC)
279
- return mailxapi.repairAccounts?.();
291
+ return _ipc.repairAccounts?.();
280
292
  return api("/repair-accounts", { method: "POST" });
281
293
  }
282
294
  export function deleteDraft(accountId, draftUid) {
283
295
  if (hasIPC)
284
- return mailxapi.deleteDraft?.(accountId, draftUid);
296
+ return _ipc.deleteDraft?.(accountId, draftUid);
285
297
  return api("/draft", { method: "DELETE", body: JSON.stringify({ accountId, draftUid }) });
286
298
  }
287
299
  export function setupAccount(name, email, password) {
288
300
  if (hasIPC)
289
- return mailxapi.setupAccount?.(name, email, password);
301
+ return _ipc.setupAccount?.(name, email, password);
290
302
  return api("/setup", { method: "POST", body: JSON.stringify({ name, email, password }) });
291
303
  }
292
304
  // Legacy exports for backward compatibility
@@ -103,7 +103,9 @@
103
103
 
104
104
  // Sync
105
105
  syncAll: function() { return callNode("syncAll"); },
106
+ syncAccount: function(accountId) { return callNode("syncAccount", { accountId: accountId }); },
106
107
  getSyncPending: function() { return callNode("getSyncPending"); },
108
+ reauthenticate: function(accountId) { return callNode("reauthenticate", { accountId: accountId }); },
107
109
 
108
110
  // Bulk operations
109
111
  deleteMessages: function(accountId, uids) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.168",
3
+ "version": "1.0.169",
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,11 +20,11 @@
20
20
  "postinstall": "node bin/postinstall.js"
21
21
  },
22
22
  "dependencies": {
23
- "@bobfrankston/iflow-direct": "^0.1.2",
23
+ "@bobfrankston/iflow-direct": "^0.1.3",
24
24
  "@bobfrankston/iflow-node": "^0.1.1",
25
25
  "@bobfrankston/miscinfo": "^1.0.7",
26
26
  "@bobfrankston/oauthsupport": "^1.0.20",
27
- "@bobfrankston/msger": "^0.1.218",
27
+ "@bobfrankston/msger": "^0.1.219",
28
28
  "@capacitor/android": "^8.3.0",
29
29
  "@capacitor/cli": "^8.3.0",
30
30
  "@capacitor/core": "^8.3.0",
@@ -82,8 +82,13 @@ async function dispatchAction(svc, action, p) {
82
82
  case "syncAll":
83
83
  await svc.syncAll();
84
84
  return { ok: true };
85
+ case "syncAccount":
86
+ await svc.syncAccount(p.accountId);
87
+ return { ok: true };
85
88
  case "getSyncPending":
86
89
  return svc.getSyncPending();
90
+ case "reauthenticate":
91
+ return { ok: await svc.reauthenticate(p.accountId) };
87
92
  // Search & contacts
88
93
  case "searchMessages":
89
94
  return svc.search(p.query, p.page, p.pageSize, p.scope, p.accountId, p.folderId);