@bobfrankston/mailx 1.0.15 → 1.0.17

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
@@ -309,6 +309,19 @@ window.addEventListener("message", (e) => {
309
309
  if (e.data?.type === "openLink" && e.data.url) {
310
310
  window.open(e.data.url, "_blank", "noopener,noreferrer");
311
311
  }
312
+ if (e.data?.type === "linkHover") {
313
+ const statusEl = document.getElementById("status-sync");
314
+ if (statusEl) {
315
+ if (e.data.url) {
316
+ statusEl.textContent = e.data.url;
317
+ statusEl.style.color = "var(--color-text-muted)";
318
+ }
319
+ else {
320
+ statusEl.textContent = "";
321
+ statusEl.style.color = "";
322
+ }
323
+ }
324
+ }
312
325
  });
313
326
  // ── Splitter drag ──
314
327
  const splitter = document.getElementById("splitter-h");
@@ -505,7 +518,8 @@ fetch("/api/version").then(r => r.json()).then(d => {
505
518
  startupStatus.textContent = "Server offline — start with: node packages/mailx-server/index.js";
506
519
  }
507
520
  });
508
- // ── Sync pending indicator ──
521
+ // ── Sync pending indicator + server health check ──
522
+ let serverDown = false;
509
523
  setInterval(async () => {
510
524
  try {
511
525
  const res = await fetch("/api/sync/pending");
@@ -517,8 +531,25 @@ setInterval(async () => {
517
531
  el.textContent = data.pending > 0 ? `↻ ${data.pending} pending` : "";
518
532
  el.style.color = data.pending > 0 ? "oklch(0.75 0.15 60)" : "";
519
533
  }
534
+ // Server is back — reload if it was down
535
+ if (serverDown) {
536
+ serverDown = false;
537
+ const statusEl = document.getElementById("status-sync");
538
+ if (statusEl)
539
+ statusEl.textContent = "Server reconnected";
540
+ location.reload();
541
+ }
542
+ }
543
+ catch {
544
+ if (!serverDown) {
545
+ serverDown = true;
546
+ const statusEl = document.getElementById("status-sync");
547
+ if (statusEl) {
548
+ statusEl.textContent = "SERVER OFFLINE";
549
+ statusEl.style.color = "oklch(0.65 0.2 25)";
550
+ }
551
+ }
520
552
  }
521
- catch { /* offline */ }
522
553
  }, 5000);
523
554
  console.log("mailx client initialized, location:", location.href);
524
555
  // Diagnostic: test API connectivity (helps debug WebView2 blank screen)
@@ -6,13 +6,16 @@ import { getMessage, updateFlags } from "../lib/api-client.js";
6
6
  let currentMessage = null;
7
7
  let currentAccountId = "";
8
8
  let showMessageGeneration = 0; // Cancel stale fetches
9
+ let retryCount = 0;
9
10
  export function getCurrentMessage() {
10
11
  if (!currentMessage)
11
12
  return null;
12
13
  return { accountId: currentAccountId, message: currentMessage };
13
14
  }
14
- export async function showMessage(accountId, uid, folderId, specialUse) {
15
+ export async function showMessage(accountId, uid, folderId, specialUse, isRetry = false) {
15
16
  const gen = ++showMessageGeneration;
17
+ if (!isRetry)
18
+ retryCount = 0;
16
19
  const headerEl = document.getElementById("mv-header");
17
20
  const bodyEl = document.getElementById("mv-body");
18
21
  const attEl = document.getElementById("mv-attachments");
@@ -221,9 +224,17 @@ export async function showMessage(accountId, uid, folderId, specialUse) {
221
224
  }
222
225
  }
223
226
  catch (e) {
224
- const msg = e.message || "Unknown error";
225
- bodyEl.innerHTML = `<div class="mv-empty">Failed to load message: ${msg}<br><button onclick="location.reload()">Retry</button></div>`;
227
+ const err = e.message || "Unknown error";
226
228
  console.error("showMessage error:", e);
229
+ if (retryCount < 3) {
230
+ retryCount++;
231
+ bodyEl.innerHTML = `<div class="mv-empty">Loading failed: ${err} — retrying (${retryCount}/3)...</div>`;
232
+ setTimeout(() => { if (gen === showMessageGeneration)
233
+ showMessage(accountId, uid, folderId, specialUse, true); }, 3000);
234
+ }
235
+ else {
236
+ bodyEl.innerHTML = `<div class="mv-empty">Failed to load message: ${err}</div>`;
237
+ }
227
238
  }
228
239
  }
229
240
  function formatAddr(addr) {
@@ -254,7 +265,7 @@ function wrapHtmlBody(html, allowRemote = false) {
254
265
  // CSP blocks remote resource loading (tracking pixels, external CSS) but allows link clicks
255
266
  const csp = allowRemote
256
267
  ? ""
257
- : `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data: cid:; form-action 'none';">`;
268
+ : `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: cid:; form-action 'none';">`;
258
269
  return `<!DOCTYPE html>
259
270
  <html><head>
260
271
  <meta charset="UTF-8">
@@ -282,6 +293,12 @@ ${csp}
282
293
  }
283
294
  </style>
284
295
  <base target="_blank">
296
+ <script>
297
+ document.addEventListener("mouseover", e => {
298
+ const a = e.target.closest("a[href]");
299
+ window.parent.postMessage({ type: "linkHover", url: a ? a.href : "" }, "*");
300
+ });
301
+ </script>
285
302
  </head><body>${html}</body></html>`;
286
303
  }
287
304
  //# sourceMappingURL=message-viewer.js.map
@@ -19,10 +19,19 @@ function newMessageListSignal() {
19
19
  return messageListAbort.signal;
20
20
  }
21
21
  async function api(path, options) {
22
- const res = await fetch(`/api${path}`, {
23
- headers: { "Content-Type": "application/json" },
24
- ...options
25
- });
22
+ let res;
23
+ try {
24
+ res = await fetch(`/api${path}`, {
25
+ headers: { "Content-Type": "application/json" },
26
+ ...options
27
+ });
28
+ }
29
+ catch (e) {
30
+ // Network error — server is down
31
+ if (e.name === "AbortError")
32
+ throw e;
33
+ throw new Error("Server offline — restart with launch.ps1");
34
+ }
26
35
  if (!res.ok) {
27
36
  const err = await res.json().catch(() => ({ error: res.statusText }));
28
37
  throw new Error(err.error || res.statusText);
@@ -92,7 +92,7 @@
92
92
  background: var(--color-bg-surface);
93
93
  color: var(--color-text);
94
94
  font-size: var(--font-size-sm);
95
- width: 350px;
95
+ width: 500px;
96
96
 
97
97
  &::placeholder { color: var(--color-text-muted); }
98
98
  &:focus { outline: 1px solid var(--color-accent); border-color: var(--color-accent); }
@@ -1,21 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * mailx postinstall — delegates to @bobfrankston/rust-builder
3
+ * mailx postinstall — sets binary permissions on Linux/Mac.
4
+ * Tries shared rust-builder; falls back to inline logic if not available.
4
5
  */
5
- import { runPostinstall } from "@bobfrankston/rust-builder/postinstall";
6
- import path from "path";
7
- import { fileURLToPath } from "url";
6
+ try {
7
+ const { runPostinstall } = await import("@bobfrankston/rust-builder/postinstall");
8
+ const path = await import("path");
9
+ const { fileURLToPath } = await import("url");
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
11
 
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
-
11
- runPostinstall({
12
- binaryName: "mailx-app",
13
- binDir: path.join(__dirname, "..", "bin"),
14
- binaries: {
15
- win32: "mailx-app.exe",
16
- darwin: "mailx-app",
17
- darwinArm64: "mailx-app-arm64",
18
- linux: "mailx-app-linux",
19
- linuxArm64: "mailx-app-linux-aarch64",
20
- },
21
- });
12
+ runPostinstall({
13
+ binaryName: "mailx-app",
14
+ binDir: path.join(__dirname, "..", "bin"),
15
+ binaries: {
16
+ win32: "mailx-app.exe",
17
+ darwin: "mailx-app",
18
+ darwinArm64: "mailx-app-arm64",
19
+ linux: "mailx-app-linux",
20
+ linuxArm64: "mailx-app-linux-aarch64",
21
+ },
22
+ });
23
+ } catch {
24
+ // rust-builder not available (e.g., local dev with file: deps)
25
+ // On Windows, nothing to do. On Linux/Mac, try chmod directly.
26
+ if (process.platform !== "win32") {
27
+ const fs = await import("fs");
28
+ const path = await import("path");
29
+ const { fileURLToPath } = await import("url");
30
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
31
+ const arch = process.arch;
32
+ const name = process.platform === "darwin"
33
+ ? (arch === "arm64" ? "mailx-app-arm64" : "mailx-app")
34
+ : (arch === "arm64" ? "mailx-app-linux-aarch64" : "mailx-app-linux");
35
+ const bin = path.join(__dirname, "..", "bin", name);
36
+ try { fs.chmodSync(bin, 0o755); } catch { /* binary may not exist */ }
37
+ }
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -44,6 +44,9 @@
44
44
  ],
45
45
  "author": "Bob Frankston",
46
46
  "license": "MIT",
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
47
50
  "repository": {
48
51
  "type": "git",
49
52
  "url": "https://github.com/BobFrankston/mailx.git"
@@ -233,6 +233,14 @@ process.on("SIGTERM", shutdown);
233
233
  process.on("unhandledRejection", (err) => {
234
234
  console.error("Unhandled rejection:", err?.message || err);
235
235
  });
236
+ process.on("uncaughtException", (err) => {
237
+ console.error("FATAL uncaught exception:", err.message);
238
+ console.error(err.stack);
239
+ // Don't exit — let node --watch handle restart
240
+ });
241
+ process.on("exit", (code) => {
242
+ console.log(`Process exiting with code ${code}`);
243
+ });
236
244
  // ── Entry Point ──
237
245
  await start();
238
246
  export { app, start };