@bobfrankston/rmfmail 1.1.141 → 1.1.143

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.ts CHANGED
@@ -949,12 +949,32 @@ async function openCompose(mode: ComposeMode, overrideMsg?: any, overrideAccount
949
949
  const initJson = JSON.stringify(init);
950
950
  try { sessionStorage.setItem("composeInit", initJson); }
951
951
  catch (e: any) { console.error("[compose] sessionStorage.setItem failed:", e?.message || e); }
952
+ // Stash on the parent window so a same-origin iframe can pull it via
953
+ // window.parent (most reliable handoff under WebView2 custom-protocol —
954
+ // sessionStorage and postMessage both fail intermittently). Keyed by
955
+ // a unique token in the iframe URL so concurrent composes don't crosswire.
956
+ const composeKey = "init-" + Math.random().toString(36).slice(2, 10);
957
+ (window as any).__mailxComposeInits = (window as any).__mailxComposeInits || {};
958
+ (window as any).__mailxComposeInits[composeKey] = init;
959
+ // Expose the key on the iframe so its IIFE can fetch the right blob.
960
+ try { (frame as any).dataset.composeKey = composeKey; } catch { /* */ }
952
961
  const post = (): void => {
953
- try { frame?.contentWindow?.postMessage({ type: "compose-init", init }, "*"); } catch { /* */ }
962
+ try { frame?.contentWindow?.postMessage({ type: "compose-init", init, composeKey }, "*"); }
963
+ catch (e: any) { logClientEvent("compose-post-failed", { err: e?.message || String(e) }); }
954
964
  };
965
+ logClientEvent("compose-handoff", { hasFrame: !!frame, hasContentWindow: !!frame?.contentWindow, composeKey, bodyBytes: initJson.length });
955
966
  post();
956
967
  // Iframe may not be loaded yet — re-post on load so the listener exists.
957
- try { frame?.addEventListener("load", post, { once: true }); } catch { /* */ }
968
+ try { frame?.addEventListener("load", () => { logClientEvent("compose-iframe-loaded", { composeKey }); post(); }); } catch { /* */ }
969
+ // And keep posting every 100ms for up to 3s as a defense-in-depth — if
970
+ // both load and the initial post race the wrong way, the iframe's IIFE
971
+ // will see the payload on its next tick.
972
+ let attempts = 0;
973
+ const heartbeat = setInterval(() => {
974
+ attempts++;
975
+ post();
976
+ if (attempts >= 30) clearInterval(heartbeat);
977
+ }, 100);
958
978
  }
959
979
 
960
980
  function showComposeOverlay(title = "Compose"): HTMLIFrameElement {
@@ -701,13 +701,16 @@ async function createTinyMceEditor(container2, opts = {}) {
701
701
  // everything else so genuine HTML formatting (bold / italic /
702
702
  // tables / inline color) still comes through verbatim.
703
703
  content_style: [
704
- // High-contrast caret: a 1-2 px slate-coloured I-beam vanishes
705
- // against white especially on hi-DPI displays. caret-color
706
- // recolours the native insertion bar; caret-shape:block (CSS
707
- // UI L4, Chromium 125+) widens it into a solid block. The
708
- // block falls back to the default bar where unsupported, so
709
- // the color alone still buys most of the visibility win.
710
- "body { font-family: system-ui, sans-serif; font-size: 14px; caret-color: #d32f2f; caret-shape: block; }",
704
+ // High-contrast caret. Default native bar shape (thin vertical
705
+ // line, browser-drawn caret-shape:block was too aggressive).
706
+ // Color shifts by ancestor element so the caret doubles as a
707
+ // formatting-mode indicator: blue plain red bold green
708
+ // italic orange underline. Innermost wrapper wins on
709
+ // combinations (em inside strong shows green).
710
+ "body { font-family: system-ui, sans-serif; font-size: 14px; caret-color: #1e88e5; }",
711
+ "b, strong { caret-color: #d32f2f; }",
712
+ "i, em { caret-color: #2e7d32; }",
713
+ "u { caret-color: #ef6c00; }",
711
714
  "blockquote { border-left: 3px solid #c0c8d0; margin: 0 0 0 4px; padding: 2px 0 2px 10px; color: #555; }",
712
715
  "div.reply { margin-top: 0.5em; }",
713
716
  "div.reply > p:first-child { color: #666; font-size: 0.95em; margin: 0 0 4px 0; }",
@@ -4167,16 +4170,46 @@ function scheduleDraftSave() {
4167
4170
  var _postedInit = null;
4168
4171
  var _parentInitReady = !!sessionStorage.getItem("composeInit");
4169
4172
  var _parentInitListeners = [];
4173
+ var _msgEventCount = 0;
4170
4174
  window.addEventListener("message", (e) => {
4175
+ _msgEventCount++;
4171
4176
  if (e.data?.type === "compose-init-ready") {
4172
4177
  _parentInitReady = true;
4173
4178
  for (const fn of _parentInitListeners.splice(0)) fn();
4174
4179
  } else if (e.data?.type === "compose-init" && e.data.init) {
4180
+ if (!_postedInit) {
4181
+ try {
4182
+ logClientEvent("compose-init-received", { src: "postMessage" });
4183
+ } catch {
4184
+ }
4185
+ }
4175
4186
  _postedInit = e.data.init;
4176
4187
  _parentInitReady = true;
4177
4188
  for (const fn of _parentInitListeners.splice(0)) fn();
4178
4189
  }
4179
4190
  });
4191
+ function pullInitFromParent() {
4192
+ try {
4193
+ const myFrame = window.frameElement;
4194
+ const key = myFrame?.dataset?.composeKey;
4195
+ if (!key) return null;
4196
+ const stash = window.parent?.__mailxComposeInits;
4197
+ const init = stash?.[key];
4198
+ if (init) {
4199
+ try {
4200
+ logClientEvent("compose-init-received", { src: "parent-stash", key });
4201
+ } catch {
4202
+ }
4203
+ return init;
4204
+ }
4205
+ } catch (e) {
4206
+ try {
4207
+ logClientEvent("compose-init-pull-failed", { err: e?.message || String(e) });
4208
+ } catch {
4209
+ }
4210
+ }
4211
+ return null;
4212
+ }
4180
4213
  function waitForParentInit(maxMs) {
4181
4214
  if (_parentInitReady) return Promise.resolve();
4182
4215
  return new Promise((resolve) => {
@@ -4192,17 +4225,20 @@ function waitForParentInit(maxMs) {
4192
4225
  }
4193
4226
  (async () => {
4194
4227
  _ctick("init IIFE start");
4195
- if (!sessionStorage.getItem("composeInit") && !_postedInit) {
4228
+ let parentInit = pullInitFromParent();
4229
+ if (!parentInit && !sessionStorage.getItem("composeInit") && !_postedInit) {
4196
4230
  _ctick("waiting for parent init");
4197
4231
  await waitForParentInit(1500);
4198
- _ctick("parent init received");
4232
+ _ctick(`parent init received (msgEvents=${_msgEventCount})`);
4233
+ if (!_postedInit) parentInit = pullInitFromParent();
4199
4234
  }
4200
4235
  const stored = sessionStorage.getItem("composeInit");
4201
- const initRaw = _postedInit || (stored ? JSON.parse(stored) : null);
4236
+ const initRaw = parentInit || _postedInit || (stored ? JSON.parse(stored) : null);
4202
4237
  if (initRaw) {
4203
4238
  sessionStorage.removeItem("composeInit");
4204
4239
  const init = initRaw;
4205
- _ctick(`init parsed (mode=${init.mode}, bodyHtml=${init.bodyHtml?.length || 0} bytes, src=${_postedInit ? "postMessage" : "sessionStorage"})`);
4240
+ const src = parentInit ? "parent-stash" : _postedInit ? "postMessage" : "sessionStorage";
4241
+ _ctick(`init parsed (mode=${init.mode}, bodyHtml=${init.bodyHtml?.length || 0} bytes, src=${src})`);
4206
4242
  if (init.accounts && init.accounts.length > 0) {
4207
4243
  applyInit(init);
4208
4244
  _ctick("applyInit done \u2014 compose visible");