@bobfrankston/mailx 1.0.145 → 1.0.147

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 CHANGED
@@ -529,21 +529,34 @@ async function main() {
529
529
  const imapManager = new ImapManager(db);
530
530
  imapManager.useNativeClient = true;
531
531
  const svc = new MailxService(db, imapManager);
532
- // Read mailxapi.js for injection into WebView
533
- const mailxapiPath = path.join(import.meta.dirname, "..", "client", "lib", "mailxapi.js");
534
- const mailxapiScript = fs.readFileSync(mailxapiPath, "utf-8");
535
- // Open msger in service mode — file:// URL, no HTTP
532
+ // Open msger in service mode — custom protocol serves files from client dir
536
533
  const clientDir = path.join(import.meta.dirname, "..", "client");
537
- const indexPath = path.join(clientDir, "index.html");
534
+ const mailxapiPath = path.join(clientDir, "lib", "mailxapi.js");
535
+ const mailxapiScript = fs.readFileSync(mailxapiPath, "utf-8");
538
536
  const handle = showService({
539
- url: indexPath,
537
+ url: "index.html",
538
+ contentDir: clientDir,
540
539
  initScript: mailxapiScript,
541
540
  size: { width: 1400, height: 900 },
541
+ escapeCloses: false,
542
542
  });
543
543
  // Handle requests from WebView → dispatch to MailxService
544
+ // Pass server version to dispatch so getVersion returns it
545
+ const rootPkg = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, "..", "package.json"), "utf-8"));
544
546
  handle.onRequest(async (req) => {
545
- const response = await dispatch(svc, req);
546
- handle.send(response);
547
+ if (verbose)
548
+ console.error(`[ipc] ← ${req._action} (${req._cbid})`);
549
+ req._version = rootPkg.version;
550
+ try {
551
+ const response = await dispatch(svc, req);
552
+ if (verbose)
553
+ console.error(`[ipc] → ${req._action} (${req._cbid}) ok`);
554
+ handle.send(response);
555
+ }
556
+ catch (e) {
557
+ console.error(`[ipc] → ${req._action} (${req._cbid}) error: ${e.message}`);
558
+ handle.send({ _cbid: req._cbid, error: e.message });
559
+ }
547
560
  });
548
561
  // Wire IMAP events → push to WebView
549
562
  imapManager.on("syncProgress", (accountId, phase, progress) => {
@@ -558,6 +571,8 @@ async function main() {
558
571
  imapManager.on("accountError", (accountId, error, hint, isOAuth) => {
559
572
  handle.send({ _event: "accountError", type: "accountError", accountId, error, hint, isOAuth });
560
573
  });
574
+ // Wait for WebView2 initialization before starting IMAP (stdin writes during init crash wry)
575
+ await new Promise(r => setTimeout(r, 2000));
561
576
  // Add accounts and start sync
562
577
  for (const account of settings.accounts) {
563
578
  if (!account.enabled)
package/client/app.js CHANGED
@@ -900,7 +900,8 @@ optAutocomplete?.addEventListener("change", () => {
900
900
  }).catch(() => { });
901
901
  });
902
902
  const isApp = typeof mailxapi !== "undefined" && mailxapi?.isApp;
903
- fetch("/api/version").then(r => r.json()).then(d => {
903
+ const versionPromise = isApp ? mailxapi.getVersion() : fetch("/api/version").then(r => r.json());
904
+ versionPromise.then((d) => {
904
905
  const el = document.getElementById("app-version");
905
906
  const storage = d.storage || {};
906
907
  const storageLabel = storage.provider && storage.provider !== "local"
@@ -947,56 +948,54 @@ fetch("/api/version").then(r => r.json()).then(d => {
947
948
  else if (storage.cloudError) {
948
949
  showAlert(`Cloud storage error: ${storage.cloudError}`, "cloud-error");
949
950
  }
950
- }).catch(async () => {
951
- // Server not runningtry to start it if we're in the app
952
- const startupStatus = document.getElementById("startup-status");
953
- if (isApp) {
954
- if (startupStatus)
955
- startupStatus.textContent = "Starting server...";
956
- await mailxapi.ensureServer();
957
- location.reload();
958
- }
959
- else {
951
+ }).catch(() => {
952
+ // Version fetch failedin IPC mode this means service isn't responding yet
953
+ if (!isApp) {
960
954
  const el = document.getElementById("app-version");
961
955
  if (el)
962
956
  el.textContent = "mailx [server offline]";
957
+ const startupStatus = document.getElementById("startup-status");
963
958
  if (startupStatus)
964
959
  startupStatus.textContent = "Server offline — start with: node packages/mailx-server/index.js";
965
960
  }
966
961
  });
967
- // ── Sync pending indicator + server health check ──
962
+ // ── Sync pending indicator + server health check (HTTP mode only) ──
968
963
  let serverDown = false;
969
- setInterval(async () => {
970
- try {
971
- const res = await fetch("/api/sync/pending");
972
- if (!res.ok)
973
- return;
974
- const data = await res.json();
975
- const el = document.getElementById("status-pending");
976
- if (el) {
977
- el.textContent = data.pending > 0 ? `↻ ${data.pending} pending` : "";
978
- el.style.color = data.pending > 0 ? "oklch(0.75 0.15 60)" : "";
979
- }
980
- // Server is back — reload if it was down
981
- if (serverDown) {
982
- serverDown = false;
983
- const statusEl = document.getElementById("status-sync");
984
- if (statusEl)
985
- statusEl.textContent = "Server reconnected";
986
- location.reload();
964
+ if (isApp) {
965
+ // IPC mode: events come via push, no polling needed
966
+ }
967
+ else
968
+ setInterval(async () => {
969
+ try {
970
+ const res = await fetch("/api/sync/pending");
971
+ if (!res.ok)
972
+ return;
973
+ const data = await res.json();
974
+ const el = document.getElementById("status-pending");
975
+ if (el) {
976
+ el.textContent = data.pending > 0 ? `↻ ${data.pending} pending` : "";
977
+ el.style.color = data.pending > 0 ? "oklch(0.75 0.15 60)" : "";
978
+ }
979
+ // Server is back — reload if it was down
980
+ if (serverDown) {
981
+ serverDown = false;
982
+ const statusEl = document.getElementById("status-sync");
983
+ if (statusEl)
984
+ statusEl.textContent = "Server reconnected";
985
+ location.reload();
986
+ }
987
987
  }
988
- }
989
- catch {
990
- if (!serverDown) {
991
- serverDown = true;
992
- const statusEl = document.getElementById("status-sync");
993
- if (statusEl) {
994
- statusEl.textContent = "SERVER OFFLINE";
995
- statusEl.style.color = "oklch(0.65 0.2 25)";
988
+ catch {
989
+ if (!serverDown) {
990
+ serverDown = true;
991
+ const statusEl = document.getElementById("status-sync");
992
+ if (statusEl) {
993
+ statusEl.textContent = "SERVER OFFLINE";
994
+ statusEl.style.color = "oklch(0.65 0.2 25)";
995
+ }
996
996
  }
997
997
  }
998
- }
999
- }, 5000);
998
+ }, 5000);
1000
999
  console.log("mailx client initialized, location:", location.href);
1001
1000
  updateNewMessageCount();
1002
1001
  // ── Midnight refresh — update date display when day changes ──
@@ -1011,7 +1010,7 @@ function scheduleMiddnightRefresh() {
1011
1010
  }
1012
1011
  scheduleMiddnightRefresh();
1013
1012
  // ── Apply theme from settings ──
1014
- fetch("/api/version").then(r => r.json()).then(d => {
1013
+ (isApp ? mailxapi.getVersion() : fetch("/api/version").then(r => r.json())).then((d) => {
1015
1014
  if (d.theme === "dark")
1016
1015
  document.documentElement.classList.add("theme-dark");
1017
1016
  else if (d.theme === "light")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.145",
3
+ "version": "1.0.147",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -23,7 +23,7 @@
23
23
  "@bobfrankston/iflow": "^1.0.52",
24
24
  "@bobfrankston/miscinfo": "^1.0.7",
25
25
  "@bobfrankston/oauthsupport": "^1.0.20",
26
- "@bobfrankston/msger": "^0.1.197",
26
+ "@bobfrankston/msger": "^0.1.199",
27
27
  "@capacitor/android": "^8.3.0",
28
28
  "@capacitor/cli": "^8.3.0",
29
29
  "@capacitor/core": "^8.3.0",
@@ -164,6 +164,7 @@ export declare class ImapManager extends EventEmitter {
164
164
  processOutbox(accountId: string): Promise<void>;
165
165
  /** Start background Outbox worker — runs immediately then every 10 seconds */
166
166
  private outboxBackoff;
167
+ private outboxBackoffDelay;
167
168
  startOutboxWorker(): void;
168
169
  /** Stop Outbox worker */
169
170
  stopOutboxWorker(): void;
@@ -1592,7 +1592,8 @@ export class ImapManager extends EventEmitter {
1592
1592
  }
1593
1593
  }
1594
1594
  /** Start background Outbox worker — runs immediately then every 10 seconds */
1595
- outboxBackoff = new Map(); // accountId → next retry time
1595
+ outboxBackoff = new Map(); // accountId → next retry timestamp
1596
+ outboxBackoffDelay = new Map(); // accountId → current delay ms
1596
1597
  startOutboxWorker() {
1597
1598
  if (this.outboxInterval)
1598
1599
  return;
@@ -1606,12 +1607,15 @@ export class ImapManager extends EventEmitter {
1606
1607
  try {
1607
1608
  await this.processLocalQueue(accountId);
1608
1609
  await this.processOutbox(accountId);
1609
- this.outboxBackoff.delete(accountId); // success — clear backoff
1610
+ // Success — clear backoff
1611
+ this.outboxBackoff.delete(accountId);
1612
+ this.outboxBackoffDelay.delete(accountId);
1610
1613
  }
1611
1614
  catch (e) {
1612
- // Exponential backoff: 30s, 60s, 120s, max 5min
1613
- const prev = this.outboxBackoff.get(accountId);
1614
- const delay = prev ? Math.min((now - prev + 30000) * 2, 300000) : 30000;
1615
+ // Exponential backoff: 30s 60s 120s → 300s (max 5min)
1616
+ const prevDelay = this.outboxBackoffDelay.get(accountId) || 0;
1617
+ const delay = prevDelay ? Math.min(prevDelay * 2, 300000) : 30000;
1618
+ this.outboxBackoffDelay.set(accountId, delay);
1615
1619
  this.outboxBackoff.set(accountId, now + delay);
1616
1620
  console.error(` [outbox] Error for ${accountId}: ${imapError(e)} (retry in ${Math.round(delay / 1000)}s)`);
1617
1621
  }
@@ -93,8 +93,11 @@ async function dispatchAction(svc, action, p) {
93
93
  case "allowRemoteContent":
94
94
  svc.allowRemoteContent(p.type, p.value);
95
95
  return { ok: true };
96
- case "getVersion":
97
- return svc.getStorageInfo();
96
+ case "getVersion": {
97
+ const settings = svc.getSettings();
98
+ const storage = svc.getStorageInfo();
99
+ return { version: p._version || "dev", theme: settings.ui?.theme || "system", storage };
100
+ }
98
101
  // Autocomplete
99
102
  case "autocomplete":
100
103
  return svc.autocomplete(p);
package/rebuild.cmd CHANGED
@@ -1,6 +1,6 @@
1
1
  setlocal
2
2
  : set MAILX_NATIVE_IMAP=1
3
3
  call npmglobalize
4
- call killmail.cmd
5
- launch.ps1
4
+ : call killmail.cmd
5
+ : launch.ps1
6
6
  endlocal