@bobfrankston/rmfmail 1.2.115 → 1.2.117

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.
@@ -22,20 +22,62 @@
22
22
  */
23
23
  import http from "node:http";
24
24
  import crypto from "node:crypto";
25
+ import fs from "node:fs/promises";
26
+ import path from "node:path";
27
+ import { dispatch } from "@bobfrankston/mailx-service/jsonrpc.js";
25
28
  /** svc must expose getMessage(accountId, uid, allowRemote, folderId) and
26
29
  * openExternal(url). `onAction` receives toolbar actions from popout windows
27
30
  * (reply/replyAll/forward/editAsNew/flag/delete) for routing to the main
28
31
  * window. Returns null if the server can't bind (popout disabled). */
29
32
  export async function startPopoutServer(svc, onAction) {
30
33
  const token = crypto.randomBytes(18).toString("hex");
34
+ // Static root for /app/<token>/... — the app package root, so pages under
35
+ // client/ can reach ../styles, ../../node_modules etc. exactly as they do
36
+ // through msger's custom protocol.
37
+ const appRoot = path.resolve(import.meta.dirname, "..");
31
38
  const server = http.createServer(async (req, res) => {
32
39
  try {
33
40
  const url = new URL(req.url || "/", "http://127.0.0.1");
41
+ // Static app files: /app/<token>/<relpath>. Token travels in the
42
+ // path so the page's RELATIVE asset/module loads (css, bundles,
43
+ // node_modules imports) stay inside the gate without rewriting.
44
+ const staticMatch = url.pathname.match(/^\/app\/([0-9a-f]+)\/(.*)$/);
45
+ if (req.method === "GET" && staticMatch) {
46
+ if (staticMatch[1] !== token) {
47
+ res.writeHead(403).end("forbidden");
48
+ return;
49
+ }
50
+ await serveStatic(appRoot, staticMatch[2], token, res);
51
+ return;
52
+ }
34
53
  // Token gate on every request — loopback TCP is locally reachable.
35
54
  if (url.searchParams.get("t") !== token) {
36
55
  res.writeHead(403).end("forbidden");
37
56
  return;
38
57
  }
58
+ // Full mailxapi surface over HTTP: the popout compose page's
59
+ // bridge (client/lib/popout-bridge.ts) posts the SAME
60
+ // { _action, _cbid, ...params } payload the WebView IPC channel
61
+ // carries, and gets the dispatcher's { _cbid, result | error }
62
+ // back. One endpoint = the whole service, no per-method routes.
63
+ if (req.method === "POST" && url.pathname === "/rpc") {
64
+ let body = "";
65
+ for await (const chunk of req)
66
+ body += chunk;
67
+ let rpcReq = null;
68
+ try {
69
+ rpcReq = JSON.parse(body);
70
+ }
71
+ catch { /* handled below */ }
72
+ if (!rpcReq || typeof rpcReq._action !== "string") {
73
+ res.writeHead(400).end("bad rpc request");
74
+ return;
75
+ }
76
+ const rpcRes = await dispatch(svc, rpcReq);
77
+ res.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
78
+ res.end(JSON.stringify(rpcRes));
79
+ return;
80
+ }
39
81
  const a = url.searchParams.get("a") || "";
40
82
  const u = Number(url.searchParams.get("u"));
41
83
  const fRaw = url.searchParams.get("f");
@@ -113,6 +155,62 @@ export async function startPopoutServer(svc, onAction) {
113
155
  });
114
156
  });
115
157
  }
158
+ const STATIC_MIME = {
159
+ html: "text/html; charset=utf-8",
160
+ js: "text/javascript; charset=utf-8",
161
+ mjs: "text/javascript; charset=utf-8",
162
+ css: "text/css; charset=utf-8",
163
+ json: "application/json",
164
+ map: "application/json",
165
+ svg: "image/svg+xml",
166
+ png: "image/png",
167
+ ico: "image/x-icon",
168
+ woff2: "font/woff2",
169
+ woff: "font/woff",
170
+ wasm: "application/wasm",
171
+ };
172
+ /** Serve one file from the app root for the popout compose window. HTML gets
173
+ * the IPC-over-HTTP bridge injected at the top of <head>: config + bridge
174
+ * (window.ipc → POST /rpc) + the REAL mailxapi.js — so the page runs the
175
+ * byte-identical API surface the main WebView gets via initScript. */
176
+ async function serveStatic(appRoot, relRaw, token, res) {
177
+ const rel = decodeURIComponent(relRaw);
178
+ const abs = path.resolve(appRoot, rel);
179
+ // Traversal guard — resolved path must stay inside the app root.
180
+ if (abs !== appRoot && !abs.startsWith(appRoot + path.sep)) {
181
+ res.writeHead(403).end("forbidden");
182
+ return;
183
+ }
184
+ const ext = (abs.split(".").pop() || "").toLowerCase();
185
+ const mime = STATIC_MIME[ext] || "application/octet-stream";
186
+ let data;
187
+ try {
188
+ data = await fs.readFile(abs);
189
+ }
190
+ catch {
191
+ res.writeHead(404).end("not found");
192
+ return;
193
+ }
194
+ // no-store everywhere: the token (and port) change per daemon run, but a
195
+ // stale cached bundle behind a same-shaped URL is exactly the class of
196
+ // bug that burned us before (see memory: webview_bundle_cache_stale).
197
+ const headers = { "content-type": mime, "cache-control": "no-store" };
198
+ if (ext === "html") {
199
+ // popout-bridge.js is type="module": tsc (NodeNext) emits `export {}`
200
+ // which is a SyntaxError in a classic script. Module scripts run
201
+ // after parse but in DOCUMENT ORDER, so the bridge still executes
202
+ // before the page's own module (compose.bundle.js) makes any RPC.
203
+ // mailxapi.js stays classic — it's the same file every host injects.
204
+ const inject = `<script>window.__rmfPopoutRpc={url:"/rpc?t=${token}"};</script>`
205
+ + `<script type="module" src="/app/${token}/client/lib/popout-bridge.js"></script>`
206
+ + `<script src="/app/${token}/client/lib/mailxapi.js"></script>`;
207
+ const html = data.toString("utf-8").replace(/<head([^>]*)>/i, (m) => m + inject);
208
+ res.writeHead(200, headers).end(html);
209
+ }
210
+ else {
211
+ res.writeHead(200, headers).end(data);
212
+ }
213
+ }
116
214
  function esc(s) {
117
215
  return (s || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
118
216
  }
@@ -1 +1 @@
1
- {"version":3,"file":"popout-server.js","sourceRoot":"","sources":["popout-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"popout-server.js","sourceRoot":"","sources":["popout-server.ts"],"names":[],"mappings":"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"}
@@ -22,6 +22,9 @@
22
22
  */
23
23
  import http from "node:http";
24
24
  import crypto from "node:crypto";
25
+ import fs from "node:fs/promises";
26
+ import path from "node:path";
27
+ import { dispatch } from "@bobfrankston/mailx-service/jsonrpc.js";
25
28
 
26
29
  export interface PopoutServerInfo {
27
30
  port: number;
@@ -41,15 +44,53 @@ export interface PopoutAction {
41
44
  * window. Returns null if the server can't bind (popout disabled). */
42
45
  export async function startPopoutServer(svc: any, onAction?: (act: PopoutAction) => void): Promise<PopoutServerInfo | null> {
43
46
  const token = crypto.randomBytes(18).toString("hex");
47
+ // Static root for /app/<token>/... — the app package root, so pages under
48
+ // client/ can reach ../styles, ../../node_modules etc. exactly as they do
49
+ // through msger's custom protocol.
50
+ const appRoot = path.resolve(import.meta.dirname, "..");
44
51
 
45
52
  const server = http.createServer(async (req, res) => {
46
53
  try {
47
54
  const url = new URL(req.url || "/", "http://127.0.0.1");
55
+
56
+ // Static app files: /app/<token>/<relpath>. Token travels in the
57
+ // path so the page's RELATIVE asset/module loads (css, bundles,
58
+ // node_modules imports) stay inside the gate without rewriting.
59
+ const staticMatch = url.pathname.match(/^\/app\/([0-9a-f]+)\/(.*)$/);
60
+ if (req.method === "GET" && staticMatch) {
61
+ if (staticMatch[1] !== token) {
62
+ res.writeHead(403).end("forbidden");
63
+ return;
64
+ }
65
+ await serveStatic(appRoot, staticMatch[2], token, res);
66
+ return;
67
+ }
68
+
48
69
  // Token gate on every request — loopback TCP is locally reachable.
49
70
  if (url.searchParams.get("t") !== token) {
50
71
  res.writeHead(403).end("forbidden");
51
72
  return;
52
73
  }
74
+
75
+ // Full mailxapi surface over HTTP: the popout compose page's
76
+ // bridge (client/lib/popout-bridge.ts) posts the SAME
77
+ // { _action, _cbid, ...params } payload the WebView IPC channel
78
+ // carries, and gets the dispatcher's { _cbid, result | error }
79
+ // back. One endpoint = the whole service, no per-method routes.
80
+ if (req.method === "POST" && url.pathname === "/rpc") {
81
+ let body = "";
82
+ for await (const chunk of req) body += chunk;
83
+ let rpcReq: any = null;
84
+ try { rpcReq = JSON.parse(body); } catch { /* handled below */ }
85
+ if (!rpcReq || typeof rpcReq._action !== "string") {
86
+ res.writeHead(400).end("bad rpc request");
87
+ return;
88
+ }
89
+ const rpcRes = await dispatch(svc, rpcReq);
90
+ res.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
91
+ res.end(JSON.stringify(rpcRes));
92
+ return;
93
+ }
53
94
  const a = url.searchParams.get("a") || "";
54
95
  const u = Number(url.searchParams.get("u"));
55
96
  const fRaw = url.searchParams.get("f");
@@ -116,6 +157,62 @@ export async function startPopoutServer(svc: any, onAction?: (act: PopoutAction)
116
157
  });
117
158
  }
118
159
 
160
+ const STATIC_MIME: Record<string, string> = {
161
+ html: "text/html; charset=utf-8",
162
+ js: "text/javascript; charset=utf-8",
163
+ mjs: "text/javascript; charset=utf-8",
164
+ css: "text/css; charset=utf-8",
165
+ json: "application/json",
166
+ map: "application/json",
167
+ svg: "image/svg+xml",
168
+ png: "image/png",
169
+ ico: "image/x-icon",
170
+ woff2: "font/woff2",
171
+ woff: "font/woff",
172
+ wasm: "application/wasm",
173
+ };
174
+
175
+ /** Serve one file from the app root for the popout compose window. HTML gets
176
+ * the IPC-over-HTTP bridge injected at the top of <head>: config + bridge
177
+ * (window.ipc → POST /rpc) + the REAL mailxapi.js — so the page runs the
178
+ * byte-identical API surface the main WebView gets via initScript. */
179
+ async function serveStatic(appRoot: string, relRaw: string, token: string, res: http.ServerResponse): Promise<void> {
180
+ const rel = decodeURIComponent(relRaw);
181
+ const abs = path.resolve(appRoot, rel);
182
+ // Traversal guard — resolved path must stay inside the app root.
183
+ if (abs !== appRoot && !abs.startsWith(appRoot + path.sep)) {
184
+ res.writeHead(403).end("forbidden");
185
+ return;
186
+ }
187
+ const ext = (abs.split(".").pop() || "").toLowerCase();
188
+ const mime = STATIC_MIME[ext] || "application/octet-stream";
189
+ let data: Buffer;
190
+ try {
191
+ data = await fs.readFile(abs);
192
+ } catch {
193
+ res.writeHead(404).end("not found");
194
+ return;
195
+ }
196
+ // no-store everywhere: the token (and port) change per daemon run, but a
197
+ // stale cached bundle behind a same-shaped URL is exactly the class of
198
+ // bug that burned us before (see memory: webview_bundle_cache_stale).
199
+ const headers = { "content-type": mime, "cache-control": "no-store" };
200
+ if (ext === "html") {
201
+ // popout-bridge.js is type="module": tsc (NodeNext) emits `export {}`
202
+ // which is a SyntaxError in a classic script. Module scripts run
203
+ // after parse but in DOCUMENT ORDER, so the bridge still executes
204
+ // before the page's own module (compose.bundle.js) makes any RPC.
205
+ // mailxapi.js stays classic — it's the same file every host injects.
206
+ const inject = `<script>window.__rmfPopoutRpc={url:"/rpc?t=${token}"};</script>`
207
+ + `<script type="module" src="/app/${token}/client/lib/popout-bridge.js"></script>`
208
+ + `<script src="/app/${token}/client/lib/mailxapi.js"></script>`;
209
+ const html = data.toString("utf-8").replace(/<head([^>]*)>/i, (m) => m + inject);
210
+ res.writeHead(200, headers).end(html);
211
+ } else {
212
+ res.writeHead(200, headers).end(data);
213
+ }
214
+ }
215
+
119
216
  function esc(s: string): string {
120
217
  return (s || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
121
218
  }
@@ -27,10 +27,12 @@ __export(api_client_exports, {
27
27
  autocomplete: () => autocomplete,
28
28
  cancelQueuedOutgoing: () => cancelQueuedOutgoing,
29
29
  cancelServerSearch: () => cancelServerSearch,
30
+ closeComposePopout: () => closeComposePopout,
30
31
  closeWordEdit: () => closeWordEdit,
31
32
  connectEvents: () => connectEvents,
32
33
  connectWebSocket: () => connectWebSocket,
33
34
  consumePendingMailto: () => consumePendingMailto,
35
+ consumePopoutComposeInit: () => consumePopoutComposeInit,
34
36
  copyMessages: () => copyMessages,
35
37
  createCalendarEvent: () => createCalendarEvent,
36
38
  createFolder: () => createFolder,
@@ -85,6 +87,7 @@ __export(api_client_exports, {
85
87
  openInTextEditor: () => openInTextEditor,
86
88
  openInWord: () => openInWord,
87
89
  openLocalPath: () => openLocalPath,
90
+ popoutCompose: () => popoutCompose,
88
91
  popoutWindow: () => popoutWindow,
89
92
  readConfigHelp: () => readConfigHelp,
90
93
  readJsoncFile: () => readJsoncFile,
@@ -628,6 +631,22 @@ async function popoutWindow(accountId, uid, folderId, subject) {
628
631
  return { ok: false, reason: "no popoutWindow bridge" };
629
632
  return fn(accountId, uid, folderId, subject);
630
633
  }
634
+ async function popoutCompose(init) {
635
+ const fn = ipc().popoutCompose;
636
+ if (!fn)
637
+ return { ok: false, reason: "no popoutCompose bridge" };
638
+ return fn(init);
639
+ }
640
+ async function consumePopoutComposeInit(id) {
641
+ const fn = ipc().consumePopoutComposeInit;
642
+ if (!fn)
643
+ return { init: null };
644
+ return fn(id);
645
+ }
646
+ function closeComposePopout(id) {
647
+ const fn = ipc().closeComposePopout;
648
+ return fn ? fn(id) : Promise.resolve({ ok: false });
649
+ }
631
650
  async function getDeviceAccounts() {
632
651
  return ipc().getDeviceAccounts?.() ?? [];
633
652
  }