@bobfrankston/rmfmail 1.2.173 → 1.2.175
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/TODO.md +3 -3
- package/bin/build-rmfshare-exe.js +79 -0
- package/bin/mailx.js +52 -1
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +51 -1
- package/bin/rmfshare-src/Program.cs +225 -0
- package/bin/rmfshare-src/rmfshare.csproj +36 -0
- package/bin/rmfshare.exe +0 -0
- package/bin/share-target.js +314 -0
- package/bin/share-target.js.map +1 -0
- package/bin/share-target.ts +331 -0
- package/client/app.bundle.js +62 -0
- package/client/app.bundle.js.map +3 -3
- package/client/app.js +69 -0
- package/client/app.js.map +1 -1
- package/client/app.ts +72 -0
- package/client/compose/compose.bundle.js +4 -0
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/api-client.js +7 -0
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +12 -0
- package/client/package.json +1 -1
- package/package.json +6 -6
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts +22 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +82 -0
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +76 -0
- package/packages/mailx-service/jsonrpc.js +2 -0
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +2 -0
- package/packages/mailx-service/package.json +1 -1
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-types/mailx-api.d.ts +3 -0
- package/packages/mailx-types/mailx-api.d.ts.map +1 -1
- package/packages/mailx-types/mailx-api.ts +3 -0
- package/packages/mailx-types/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-7284 → node_modules.npmglobalize-stash-33656}/.package-lock.json +0 -0
package/TODO.md
CHANGED
|
@@ -219,7 +219,7 @@ Items blocked on a design decision. Short-form question here; full context in th
|
|
|
219
219
|
| **C41** | PARTIAL | Reply quoting — already wraps in `<blockquote>`; read-only + non-editable-above-the-line still owed (needs Quill module work). |
|
|
220
220
|
| **C43** | OPEN | Message list column controls + threading flags. ✓ (sort-by-column-click is small) |
|
|
221
221
|
| **C44** | DONE (v1.2.105) | Real OS popup windows / pop-out — floating-overlay popout landed (Q64 v1.0.344); v1.2.105 unblocked the real OS window: 🗗 asks the daemon, which spawns a native msger window (`showMessageBoxEx({url})`) pointed at the loopback popout server. Toolbar actions POST back → `popoutAction` event → main window opens editable compose / flags / deletes (delete also closes the window). Browser-window option deferred (same URL, just `openExternal` it). |
|
|
222
|
-
| [**C46**](#ext46) | PARTIAL | Mailto handler split out as [P115](#ext115) (registry-only, not blocked).
|
|
222
|
+
| [**C46**](#ext46) | PARTIAL | Mailto handler split out as [P115](#ext115) (registry-only, not blocked). Share target SHIPPED v1.2.174 (`-register-share`, loose AppX — no MSIX needed). Remaining (tray, notifications) still blocked on MSIX packaging. |
|
|
223
223
|
| **C127** | OPTIONAL | **Push relay for calendar / mail / tasks** — design at [`docs/push-relay.md`](push-relay.md). New `MailApps/relayer/` package, alerter-shape (single Node Express+ws process, deploy alongside other small services). Generic API: client POSTs `/subscribe` with upstream + params, relay registers the upstream `events.watch` and returns an SSE/WS endpoint the client holds open. Per-upstream adapter files (`google-calendar.ts`, `google-gmail.ts`, `ms-graph-events.ts`); fan-out + replay buffer + subscription store generic. Client side: `pushRelayUrl` + credentials in settings, falls back to syncToken polling when relay unreachable. **Build only when polling latency demonstrably matters** — for typical use, 5–10 s syncToken cadence is functionally instant. Push is a freshness boost, not a correctness fix. |
|
|
224
224
|
| **C128** | OPEN | **Body pre-parse on prefetch** — first click on any unviewed message pays `simpleParser` + `sanitizeHtml` cost (~100–500 ms on rich HTML). The parsedCache (added 2026-05-09) eliminates 2nd-and-later view costs but not the first. Fix: extend prefetch to ALSO parse + sanitize the body during background fetch and store the result alongside the .eml (e.g., `<uuid>.parsed.json` next to `<uuid>.eml`, or a `body_html_cached` column). Click-time path reads pre-parsed JSON, no parser invocation. Touches `mailx-imap/index.ts` prefetch path + `mailx-store/file-store.ts` for the parsed-blob storage. |
|
|
225
225
|
| **C129** | OPEN | **Android prod/dev split via filename/URL channel** — User 2026-05-09 (revised): no separate MAUI project. Same source tree builds; output filenames distinguish channels: Phase 1 (start now) writes `rmfmaildev.apk` alongside today's `mailx-maui.apk`. Phase 2 (when a release is declared) switches to `rmfmail-<version>.apk` versioned + `rmfmaildev.apk` for latest, with promotion = re-pointing `rmfmail.apk` at a chosen version. Single APK contents per build; channel is runtime setting in AppUpdater (Settings toggle for which URL to poll). Side-by-side install on one device NOT supported by this design (single Android package id) — that's a separate need. See `docs/prod-android.md` for full plan. Phase 1 ~30 min; full flow ~2.5 hours when activated. |
|
|
@@ -488,8 +488,8 @@ Most likely cause based on the bobma folder layout: Dovecot designates "Deleted
|
|
|
488
488
|
### C46 — Windows OS integration (PARTIAL) [↑ summary](#sum)
|
|
489
489
|
|
|
490
490
|
- [ ] **mailto: handler** → see [P115](#ext115) — split out as standalone priority item, NOT blocked on MSIX (registry-only).
|
|
491
|
-
- [
|
|
492
|
-
- [ ] Native right-click context menus, system tray, file associations, notifications
|
|
491
|
+
- [x] **Windows Share target** (v1.2.174) — no MSIX signing needed after all: loose AppX deployment (`Add-AppxPackage -Register`, unsigned, needs Developer Mode). `rmfmail -register-share` stages `%LOCALAPPDATA%\rmfmail\share-target\` (rmfshare.exe + AppxManifest with `windows.shareTarget` + logos scaled from icon.png). `bin/rmfshare-src/` is a C# NativeAOT helper (3.8 MB, checked-in like rmfmailto.exe): ShareTarget activation → WinRT DataPackageView → files copied to `~/.rmfmail/share-staging/` → `pending-share.json` → daemon fs.watch base64s files and pushes `openShare` → compose opens with attachments/link/text. Startup-race twin `consumePendingShare` IPC, same shape as P115 mailto. Machines without Developer Mode get a clear message; Settings-UI toggle still owed (lands with P116 Settings rework).
|
|
492
|
+
- [ ] Native right-click context menus, system tray, file associations, notifications (tray + notifications still MSIX-blocked)
|
|
493
493
|
|
|
494
494
|
<a id="ext115"></a>
|
|
495
495
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build the rmfshare.exe Windows Share-target activation helper (C46).
|
|
4
|
+
*
|
|
5
|
+
* Mirrors build-rmfmailto-exe.js: hooked into `npm run build`, skips when
|
|
6
|
+
* - not on Windows,
|
|
7
|
+
* - the .NET SDK isn't installed (uses the checked-in bin/rmfshare.exe),
|
|
8
|
+
* - the shipped exe is newer than every file in bin/rmfshare-src/.
|
|
9
|
+
*
|
|
10
|
+
* On a real rebuild runs `dotnet publish -c Release -r win-x64` (NativeAOT —
|
|
11
|
+
* needs the VS C++ toolchain; vswhere's dir is appended to PATH because the
|
|
12
|
+
* ILCompiler targets shell out to it) and copies the published exe to
|
|
13
|
+
* bin/rmfshare.exe. The exe is checked into git so `npm install -g` ships
|
|
14
|
+
* it without requiring .NET on install machines.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const SRC_DIR = path.join(__dirname, "rmfshare-src");
|
|
24
|
+
const BUILT_EXE = path.join(SRC_DIR, "bin", "Release", "net10.0-windows10.0.19041.0", "win-x64", "publish", "rmfshare.exe");
|
|
25
|
+
const SHIPPED_EXE = path.join(__dirname, "rmfshare.exe");
|
|
26
|
+
|
|
27
|
+
if (process.platform !== "win32") {
|
|
28
|
+
console.log("[rmfshare] non-Windows host — skipping (binary is Win-only)");
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
execSync("dotnet --version", { stdio: "pipe" });
|
|
34
|
+
} catch {
|
|
35
|
+
console.log("[rmfshare] dotnet not found — skipping build (using checked-in bin/rmfshare.exe).");
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// mtime gate — same shape as build-rmfmailto-exe.js.
|
|
40
|
+
function newestMtime(dir) {
|
|
41
|
+
let newest = 0;
|
|
42
|
+
const stack = [dir];
|
|
43
|
+
while (stack.length) {
|
|
44
|
+
const d = stack.pop();
|
|
45
|
+
for (const name of fs.readdirSync(d)) {
|
|
46
|
+
if (name === "bin" || name === "obj") continue; // dotnet output dirs
|
|
47
|
+
const full = path.join(d, name);
|
|
48
|
+
const st = fs.statSync(full);
|
|
49
|
+
if (st.isDirectory()) stack.push(full);
|
|
50
|
+
else if (st.mtimeMs > newest) newest = st.mtimeMs;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return newest;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const srcMtime = newestMtime(SRC_DIR);
|
|
57
|
+
let shippedMtime = 0;
|
|
58
|
+
try { shippedMtime = fs.statSync(SHIPPED_EXE).mtimeMs; } catch { /* missing */ }
|
|
59
|
+
if (shippedMtime > srcMtime) {
|
|
60
|
+
console.log("[rmfshare] up-to-date — skipping dotnet publish");
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log("[rmfshare] building (dotnet publish NativeAOT)...");
|
|
65
|
+
const t0 = Date.now();
|
|
66
|
+
const vswhereDir = "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer";
|
|
67
|
+
const r = spawnSync("dotnet", ["publish", "-c", "Release", "-r", "win-x64"], {
|
|
68
|
+
cwd: SRC_DIR,
|
|
69
|
+
stdio: "inherit",
|
|
70
|
+
shell: true,
|
|
71
|
+
env: { ...process.env, PATH: `${process.env.PATH};${vswhereDir}` },
|
|
72
|
+
});
|
|
73
|
+
if (r.status !== 0) {
|
|
74
|
+
console.error(`[rmfshare] dotnet publish failed (exit ${r.status})`);
|
|
75
|
+
process.exit(r.status || 1);
|
|
76
|
+
}
|
|
77
|
+
fs.copyFileSync(BUILT_EXE, SHIPPED_EXE);
|
|
78
|
+
const sec = ((Date.now() - t0) / 1000).toFixed(1);
|
|
79
|
+
console.log(`[rmfshare] built and copied to bin/rmfshare.exe (${sec}s)`);
|
package/bin/mailx.js
CHANGED
|
@@ -389,6 +389,20 @@ if (hasFlag("register-mailto") || hasFlag("unregister-mailto")) {
|
|
|
389
389
|
console.log(r.message);
|
|
390
390
|
process.exit(r.ok ? 0 : 1);
|
|
391
391
|
}
|
|
392
|
+
// C46: Windows Share-target registration. Loose AppX deployment of
|
|
393
|
+
// bin/rmfshare.exe (see bin/share-target.ts for the full flow). Android
|
|
394
|
+
// gets the same feature from the MAUI manifest; macOS/Linux have no
|
|
395
|
+
// equivalent OS share sheet to register into.
|
|
396
|
+
if (hasFlag("register-share") || hasFlag("unregister-share")) {
|
|
397
|
+
if (process.platform !== "win32") {
|
|
398
|
+
console.error("-register-share / -unregister-share: Windows only (the Share sheet is a Windows shell feature).");
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
const { registerShareWindows } = await import("./share-target.js");
|
|
402
|
+
const r = registerShareWindows(hasFlag("unregister-share"));
|
|
403
|
+
console.log(r.message);
|
|
404
|
+
process.exit(r.ok ? 0 : 1);
|
|
405
|
+
}
|
|
392
406
|
// Read our own version once — used for the instance file + upgrade check below.
|
|
393
407
|
const __selfRoot = path.join(import.meta.dirname, "..");
|
|
394
408
|
const __selfVersion = (() => {
|
|
@@ -558,7 +572,7 @@ const repairMode = hasFlag("repair");
|
|
|
558
572
|
const recoverMode = hasFlag("recover");
|
|
559
573
|
const importMode = hasFlag("import");
|
|
560
574
|
// Validate arguments
|
|
561
|
-
const knownFlags = ["verbose", "kill", "v", "version", "setup", "add", "test", "rebuild", "repair", "recover", "fix-flags", "relink-bodies", "log", "import", "email", "mail", "daemon", "reauth", "mailto", "register-mailto", "unregister-mailto", "allow-elevated", "another", "no-browser", "debug-server", "send", "account"];
|
|
575
|
+
const knownFlags = ["verbose", "kill", "v", "version", "setup", "add", "test", "rebuild", "repair", "recover", "fix-flags", "relink-bodies", "log", "import", "email", "mail", "daemon", "reauth", "mailto", "register-mailto", "unregister-mailto", "register-share", "unregister-share", "allow-elevated", "another", "no-browser", "debug-server", "send", "account"];
|
|
562
576
|
for (const arg of args) {
|
|
563
577
|
// Strip a leading -/-- and any `=value` suffix before checking.
|
|
564
578
|
const flag = arg.replace(/^--?/, "").split("=")[0];
|
|
@@ -597,6 +611,10 @@ for (const arg of args) {
|
|
|
597
611
|
" -register-mailto Register as the OS-level mailto: handler\n" +
|
|
598
612
|
" -unregister-mailto Remove the mailto: handler registration\n" +
|
|
599
613
|
" -mailto <url> (internal) Decode a mailto URL and open compose\n" +
|
|
614
|
+
" Share target (Windows):\n" +
|
|
615
|
+
" -register-share Add rmfmail to the Windows Share menu\n" +
|
|
616
|
+
" (needs Developer Mode for the unsigned manifest)\n" +
|
|
617
|
+
" -unregister-share Remove rmfmail from the Windows Share menu\n" +
|
|
600
618
|
" Internal:\n" +
|
|
601
619
|
" -daemon (internal) marker for the detached daemon respawn\n" +
|
|
602
620
|
" -allow-elevated Bypass the admin/root refusal (debug only)");
|
|
@@ -2764,6 +2782,39 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2764
2782
|
console.error(` [mailto] watch setup failed: ${e.message}`);
|
|
2765
2783
|
}
|
|
2766
2784
|
}
|
|
2785
|
+
// Pending Windows-share handler (C46). Same two-path pickup as mailto
|
|
2786
|
+
// above: the client's startup consumePendingShare poll covers the
|
|
2787
|
+
// "rmfshare.exe spawned us" race; this fs.watch covers shares that land
|
|
2788
|
+
// while we're already running. The daemon (not the client) base64s the
|
|
2789
|
+
// staged files — the WebView can't read disk.
|
|
2790
|
+
{
|
|
2791
|
+
const { consumePendingShare, loadShareAttachments, cleanupShareStaging, getPendingShareFile } = await import("./share-target.js");
|
|
2792
|
+
try {
|
|
2793
|
+
cleanupShareStaging();
|
|
2794
|
+
const pendingPath = getPendingShareFile();
|
|
2795
|
+
const dir = path.dirname(pendingPath);
|
|
2796
|
+
const baseName = path.basename(pendingPath);
|
|
2797
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2798
|
+
fs.watch(dir, (_event, filename) => {
|
|
2799
|
+
if (filename !== baseName)
|
|
2800
|
+
return;
|
|
2801
|
+
if (!fs.existsSync(pendingPath))
|
|
2802
|
+
return;
|
|
2803
|
+
setTimeout(() => {
|
|
2804
|
+
const data = consumePendingShare();
|
|
2805
|
+
if (!data)
|
|
2806
|
+
return;
|
|
2807
|
+
const { attachments, skipped } = loadShareAttachments(data.files);
|
|
2808
|
+
handle.send({ _event: "openShare", type: "openShare",
|
|
2809
|
+
title: data.title, text: data.text, url: data.url,
|
|
2810
|
+
attachments, skipped });
|
|
2811
|
+
}, 50);
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
catch (e) {
|
|
2815
|
+
console.error(` [share] watch setup failed: ${e.message}`);
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2767
2818
|
// No artificial pause here. msger's stdin is queued — writes from
|
|
2768
2819
|
// imapManager events arriving before the WebView is ready get buffered
|
|
2769
2820
|
// and delivered when the WebView attaches. The earlier 500 ms wait
|