@bobfrankston/rmfmail 1.2.272 → 1.2.276

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.
Files changed (63) hide show
  1. package/.commitmsg +26 -0
  2. package/client/android-bootstrap.bundle.js +184 -0
  3. package/client/android-bootstrap.bundle.js.map +2 -2
  4. package/client/app.bundle.js +235 -31
  5. package/client/app.bundle.js.map +4 -4
  6. package/client/app.js +35 -1
  7. package/client/app.js.map +1 -1
  8. package/client/app.ts +36 -1
  9. package/client/components/invite-card.js +107 -0
  10. package/client/components/invite-card.js.map +1 -0
  11. package/client/components/invite-card.ts +119 -0
  12. package/client/components/message-list.js +9 -1
  13. package/client/components/message-list.js.map +1 -1
  14. package/client/components/message-list.ts +9 -1
  15. package/client/components/message-viewer.js +82 -19
  16. package/client/components/message-viewer.js.map +1 -1
  17. package/client/components/message-viewer.ts +79 -18
  18. package/client/compose/compose.bundle.js +4 -0
  19. package/client/compose/compose.bundle.js.map +2 -2
  20. package/client/lib/api-client.js +6 -0
  21. package/client/lib/api-client.js.map +1 -1
  22. package/client/lib/api-client.ts +6 -0
  23. package/client/lib/approved-senders.js +66 -0
  24. package/client/lib/approved-senders.js.map +1 -0
  25. package/client/lib/approved-senders.ts +63 -0
  26. package/client/lib/mailxapi.js +3 -0
  27. package/client/package.json +1 -1
  28. package/client/styles/components.css +48 -0
  29. package/package.json +10 -10
  30. package/packages/mailx-imap/package-lock.json +2 -2
  31. package/packages/mailx-imap/package.json +2 -2
  32. package/packages/mailx-service/index.d.ts +15 -0
  33. package/packages/mailx-service/index.d.ts.map +1 -1
  34. package/packages/mailx-service/index.js +18 -0
  35. package/packages/mailx-service/index.js.map +1 -1
  36. package/packages/mailx-service/index.ts +19 -0
  37. package/packages/mailx-service/jsonrpc.js +2 -0
  38. package/packages/mailx-service/jsonrpc.js.map +1 -1
  39. package/packages/mailx-service/jsonrpc.ts +2 -0
  40. package/packages/mailx-service/package.json +1 -1
  41. package/packages/mailx-settings/package.json +1 -1
  42. package/packages/mailx-store/package.json +1 -1
  43. package/packages/mailx-store/store.d.ts +5 -0
  44. package/packages/mailx-store/store.d.ts.map +1 -1
  45. package/packages/mailx-store/store.js +21 -0
  46. package/packages/mailx-store/store.js.map +1 -1
  47. package/packages/mailx-store/store.ts +27 -0
  48. package/packages/mailx-store-web/android-bootstrap.js +1 -0
  49. package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
  50. package/packages/mailx-store-web/android-bootstrap.ts +1 -0
  51. package/packages/mailx-store-web/package.json +1 -1
  52. package/packages/mailx-store-web/web-service.d.ts +11 -0
  53. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  54. package/packages/mailx-store-web/web-service.js +14 -0
  55. package/packages/mailx-store-web/web-service.js.map +1 -1
  56. package/packages/mailx-store-web/web-service.ts +15 -0
  57. package/packages/mailx-types/index.d.ts +56 -0
  58. package/packages/mailx-types/index.d.ts.map +1 -1
  59. package/packages/mailx-types/index.js +187 -0
  60. package/packages/mailx-types/index.js.map +1 -1
  61. package/packages/mailx-types/index.ts +192 -0
  62. package/packages/mailx-types/package.json +1 -1
  63. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-72180 → node_modules.npmglobalize-stash-59216}/.package-lock.json +0 -0
package/.commitmsg ADDED
@@ -0,0 +1,26 @@
1
+ Preview hotkeys fired twice — Ctrl+F opened two compose windows
2
+
3
+ A keydown in the message preview reached app.ts by TWO routes on desktop
4
+ WebView2: the iframe's own inline script posts every keydown to the parent as
5
+ `previewKey` (app.ts re-dispatches it), and installPreviewControls ALSO attached
6
+ a contentDocument listener that re-dispatched a synthetic keydown of its own.
7
+ The srcdoc document is same-origin, so both are live. One Ctrl+F therefore ran
8
+ openCompose("forward") twice and opened two compose popouts — the second never
9
+ finished loading and sat blank and "(Not Responding)" until it was killed (Bob
10
+ 2026-08-23, "Compose: Fwd: FirstCare Slide Deck (Not Responding)"; the log shows
11
+ openCompose-entry mode=forward twice in the same millisecond, two popoutCompose
12
+ windows, and the loaded one exiting while the blank duplicate survived). Ctrl+D
13
+ and Ctrl+P doubled the same way.
14
+
15
+ The inline forwarder is the mechanism that survives — it is the only one that
16
+ works on Android WebView, where parent-side contentDocument listeners never
17
+ fire. installPreviewControls keeps only what is genuinely iframe-local: zoom and
18
+ Ctrl+A.
19
+
20
+ Also: push the Windows taskbar overlay only when the unread count CHANGES.
21
+ Windows holds the overlay icon until the next SetOverlayIcon, so the host cannot
22
+ free the icon it just handed over; re-sending an identical badge every few
23
+ seconds cost GDI objects for nothing and helped drive rmfmail.exe to the
24
+ 10,000-object per-process cap (measured at GDI=9995, with 3423
25
+ "CreateIconIndirect failed: Not enough memory resources" in one day's log).
26
+ msger 0.1.429 fixes the leak itself by freeing the retired icon.
@@ -2583,10 +2583,12 @@ __export(mailx_types_exports, {
2583
2583
  flaggedOf: () => flaggedOf,
2584
2584
  formatSender: () => formatSender,
2585
2585
  htmlToPlainText: () => htmlToPlainText,
2586
+ inviteHasGoogleRsvp: () => inviteHasGoogleRsvp,
2586
2587
  isAddressToken: () => isAddressToken,
2587
2588
  isJunkPreviewText: () => isJunkPreviewText,
2588
2589
  mergeReminderStates: () => mergeReminderStates,
2589
2590
  normalizeReminderState: () => normalizeReminderState,
2591
+ parseInvite: () => parseInvite,
2590
2592
  parseSearchQuery: () => parseSearchQuery,
2591
2593
  reminderStatesEqual: () => reminderStatesEqual,
2592
2594
  sanitizeHtml: () => sanitizeHtml,
@@ -2887,6 +2889,173 @@ function formatSender(addr) {
2887
2889
  return { text: address, claimed: null };
2888
2890
  return { text: `${name} <${address}>`, claimed: displayNameClaimsOtherAddress(name, address) };
2889
2891
  }
2892
+ function unfoldIcs(text) {
2893
+ const raw = (text || "").replace(/\r\n/g, "\n").split("\n");
2894
+ const out = [];
2895
+ for (const line of raw) {
2896
+ if (/^[ \t]/.test(line) && out.length)
2897
+ out[out.length - 1] += line.slice(1);
2898
+ else
2899
+ out.push(line);
2900
+ }
2901
+ return out;
2902
+ }
2903
+ function unescapeIcs(v) {
2904
+ return (v || "").replace(/\\n/gi, "\n").replace(/\\([;,\\])/g, "$1");
2905
+ }
2906
+ function splitIcsLine(line) {
2907
+ let inQuote = false, colon = -1;
2908
+ for (let i = 0; i < line.length; i++) {
2909
+ const c = line[i];
2910
+ if (c === '"')
2911
+ inQuote = !inQuote;
2912
+ else if (c === ":" && !inQuote) {
2913
+ colon = i;
2914
+ break;
2915
+ }
2916
+ }
2917
+ if (colon < 0)
2918
+ return null;
2919
+ const namePart = line.slice(0, colon);
2920
+ const value = line.slice(colon + 1);
2921
+ const bits = namePart.split(";");
2922
+ const key = (bits.shift() || "").toUpperCase();
2923
+ const params = {};
2924
+ for (const b of bits) {
2925
+ const eq = b.indexOf("=");
2926
+ if (eq < 0)
2927
+ continue;
2928
+ params[b.slice(0, eq).toUpperCase()] = b.slice(eq + 1).replace(/^"|"$/g, "");
2929
+ }
2930
+ return { key, params, value };
2931
+ }
2932
+ function icsDateToIso(v, isUtc) {
2933
+ const m = (v || "").match(/^(\d{4})(\d{2})(\d{2})(?:T(\d{2})(\d{2})(\d{2})(Z)?)?$/);
2934
+ if (!m)
2935
+ return "";
2936
+ const [, y, mo, d, hh, mm, ss, z] = m;
2937
+ if (!hh)
2938
+ return `${y}-${mo}-${d}`;
2939
+ const base = `${y}-${mo}-${d}T${hh}:${mm}:${ss}`;
2940
+ return z || isUtc ? base + "Z" : base;
2941
+ }
2942
+ function parseCalAddress(value, params) {
2943
+ const email = (value || "").replace(/^mailto:/i, "").trim();
2944
+ return { name: (params.CN || "").trim() || email, email };
2945
+ }
2946
+ function parseInvite(ics) {
2947
+ if (!ics || !/BEGIN:VEVENT/i.test(ics))
2948
+ return null;
2949
+ const lines = unfoldIcs(ics);
2950
+ const out = {
2951
+ method: "",
2952
+ uid: "",
2953
+ sequence: 0,
2954
+ summary: "",
2955
+ description: "",
2956
+ location: "",
2957
+ start: "",
2958
+ end: "",
2959
+ tzid: "",
2960
+ allDay: false,
2961
+ organizer: { name: "", email: "" },
2962
+ attendees: [],
2963
+ rrule: "",
2964
+ status: "",
2965
+ fromGoogle: false
2966
+ };
2967
+ let inEvent = false;
2968
+ let depthNonEvent = 0;
2969
+ for (const line of lines) {
2970
+ const up = line.toUpperCase();
2971
+ if (up.startsWith("BEGIN:VEVENT")) {
2972
+ inEvent = true;
2973
+ continue;
2974
+ }
2975
+ if (up.startsWith("END:VEVENT")) {
2976
+ inEvent = false;
2977
+ continue;
2978
+ }
2979
+ if (!inEvent && (up.startsWith("BEGIN:VTIMEZONE") || up.startsWith("BEGIN:VALARM"))) {
2980
+ depthNonEvent++;
2981
+ continue;
2982
+ }
2983
+ if (up.startsWith("END:VTIMEZONE") || up.startsWith("END:VALARM")) {
2984
+ if (depthNonEvent)
2985
+ depthNonEvent--;
2986
+ continue;
2987
+ }
2988
+ if (inEvent && up.startsWith("BEGIN:VALARM")) {
2989
+ depthNonEvent++;
2990
+ continue;
2991
+ }
2992
+ const p = splitIcsLine(line);
2993
+ if (!p)
2994
+ continue;
2995
+ if (!inEvent) {
2996
+ if (p.key === "METHOD")
2997
+ out.method = p.value.trim().toUpperCase();
2998
+ else if (p.key === "PRODID" && /google/i.test(p.value))
2999
+ out.fromGoogle = true;
3000
+ continue;
3001
+ }
3002
+ if (depthNonEvent)
3003
+ continue;
3004
+ switch (p.key) {
3005
+ case "SUMMARY":
3006
+ out.summary = unescapeIcs(p.value);
3007
+ break;
3008
+ case "DESCRIPTION":
3009
+ out.description = unescapeIcs(p.value);
3010
+ break;
3011
+ case "LOCATION":
3012
+ out.location = unescapeIcs(p.value);
3013
+ break;
3014
+ case "UID":
3015
+ out.uid = p.value.trim();
3016
+ break;
3017
+ case "STATUS":
3018
+ out.status = p.value.trim().toUpperCase();
3019
+ break;
3020
+ case "RRULE":
3021
+ out.rrule = p.value.trim();
3022
+ break;
3023
+ case "SEQUENCE":
3024
+ out.sequence = parseInt(p.value, 10) || 0;
3025
+ break;
3026
+ case "DTSTART":
3027
+ out.start = icsDateToIso(p.value.trim(), false);
3028
+ out.tzid = p.params.TZID || out.tzid;
3029
+ out.allDay = (p.params.VALUE || "").toUpperCase() === "DATE" || !/T/.test(p.value);
3030
+ break;
3031
+ case "DTEND":
3032
+ out.end = icsDateToIso(p.value.trim(), false);
3033
+ out.tzid = out.tzid || p.params.TZID || "";
3034
+ break;
3035
+ case "ORGANIZER":
3036
+ out.organizer = parseCalAddress(p.value, p.params);
3037
+ break;
3038
+ case "ATTENDEE": {
3039
+ const a = parseCalAddress(p.value, p.params);
3040
+ out.attendees.push({
3041
+ name: a.name,
3042
+ email: a.email,
3043
+ status: (p.params.PARTSTAT || "NEEDS-ACTION").toUpperCase(),
3044
+ rsvp: (p.params.RSVP || "").toUpperCase() === "TRUE"
3045
+ });
3046
+ break;
3047
+ }
3048
+ }
3049
+ }
3050
+ if (!out.summary && !out.start)
3051
+ return null;
3052
+ return out;
3053
+ }
3054
+ function inviteHasGoogleRsvp(bodyHtml) {
3055
+ if (!bodyHtml)
3056
+ return false;
3057
+ return /calendar\.google\.com\/calendar\/event\?action=RESPOND/i.test(bodyHtml);
3058
+ }
2890
3059
  var _SEEN, _FLAGGED, _ANSWERED, _DRAFT, _DELETED, seenOf, flaggedOf, answeredOf, draftOf, deletedOf, setSeen, setFlagged, setAnswered, setDraft, toggleSeen, toggleFlagged;
2891
3060
  var init_mailx_types = __esm({
2892
3061
  "packages/mailx-types/index.js"() {
@@ -6403,6 +6572,20 @@ var WebMailxService = class _WebMailxService {
6403
6572
  await this.syncManager.updateFlagsLocal(accountId, uid, envelope?.folderId || 0, flags);
6404
6573
  }
6405
6574
  // ── Remote content allow-list ──
6575
+ /** Read side of the shared allowlist, so the UI can ask whether a sender
6576
+ * has been vouched for. Mirrors MailxService.getAllowlist — the desktop
6577
+ * and Android halves must answer the same question the same way, or the
6578
+ * display-name warning would differ between them. */
6579
+ async getAllowlist() {
6580
+ const list = await loadAllowlist() || {};
6581
+ return {
6582
+ senders: list.senders || [],
6583
+ domains: list.domains || [],
6584
+ recipients: list.recipients || [],
6585
+ flaggedSenders: list.flaggedSenders || [],
6586
+ flaggedDomains: list.flaggedDomains || []
6587
+ };
6588
+ }
6406
6589
  async allowRemoteContent(type, value) {
6407
6590
  await updateAllowlist((list) => {
6408
6591
  if (type === "sender" && !list.senders.includes(value))
@@ -11868,6 +12051,7 @@ function installBridge() {
11868
12051
  await service.allowRemoteContent(type, value);
11869
12052
  return { ok: true };
11870
12053
  },
12054
+ getAllowlist: () => service.getAllowlist(),
11871
12055
  flagSenderOrDomain: async (type, value) => {
11872
12056
  return await service.flagSenderOrDomain(type, value);
11873
12057
  },