@bobfrankston/rmfmail 1.2.177 → 1.2.179

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 (61) hide show
  1. package/client/android-bootstrap.bundle.js +167 -6
  2. package/client/android-bootstrap.bundle.js.map +3 -3
  3. package/client/app.bundle.js +79 -7
  4. package/client/app.bundle.js.map +3 -3
  5. package/client/components/alarms.js +88 -11
  6. package/client/components/alarms.js.map +1 -1
  7. package/client/components/alarms.ts +83 -13
  8. package/client/compose/compose.bundle.js +4 -0
  9. package/client/compose/compose.bundle.js.map +2 -2
  10. package/client/lib/api-client.js +6 -0
  11. package/client/lib/api-client.js.map +1 -1
  12. package/client/lib/api-client.ts +9 -0
  13. package/client/lib/mailxapi.js +27 -5
  14. package/package.json +7 -7
  15. package/packages/mailx-imap/package-lock.json +2 -2
  16. package/packages/mailx-imap/package.json +1 -1
  17. package/packages/mailx-service/index.d.ts +5 -0
  18. package/packages/mailx-service/index.d.ts.map +1 -1
  19. package/packages/mailx-service/index.js +59 -12
  20. package/packages/mailx-service/index.js.map +1 -1
  21. package/packages/mailx-service/index.ts +63 -13
  22. package/packages/mailx-service/jsonrpc.js +4 -0
  23. package/packages/mailx-service/jsonrpc.js.map +1 -1
  24. package/packages/mailx-service/jsonrpc.ts +4 -0
  25. package/packages/mailx-service/package.json +1 -1
  26. package/packages/mailx-settings/index.d.ts +8 -1
  27. package/packages/mailx-settings/index.d.ts.map +1 -1
  28. package/packages/mailx-settings/index.js +71 -0
  29. package/packages/mailx-settings/index.js.map +1 -1
  30. package/packages/mailx-settings/index.ts +61 -1
  31. package/packages/mailx-settings/package.json +1 -1
  32. package/packages/mailx-store/package.json +1 -1
  33. package/packages/mailx-store-web/android-bootstrap.js +4 -0
  34. package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
  35. package/packages/mailx-store-web/android-bootstrap.ts +4 -0
  36. package/packages/mailx-store-web/package.json +1 -1
  37. package/packages/mailx-store-web/web-service.d.ts +4 -0
  38. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  39. package/packages/mailx-store-web/web-service.js +77 -8
  40. package/packages/mailx-store-web/web-service.js.map +1 -1
  41. package/packages/mailx-store-web/web-service.ts +71 -9
  42. package/packages/mailx-types/index.d.ts +4 -0
  43. package/packages/mailx-types/index.d.ts.map +1 -1
  44. package/packages/mailx-types/index.js +6 -0
  45. package/packages/mailx-types/index.js.map +1 -1
  46. package/packages/mailx-types/index.ts +12 -0
  47. package/packages/mailx-types/mailx-api.d.ts +14 -0
  48. package/packages/mailx-types/mailx-api.d.ts.map +1 -1
  49. package/packages/mailx-types/mailx-api.ts +5 -0
  50. package/packages/mailx-types/mime-inline-images.d.ts +38 -0
  51. package/packages/mailx-types/mime-inline-images.d.ts.map +1 -0
  52. package/packages/mailx-types/mime-inline-images.js +45 -0
  53. package/packages/mailx-types/mime-inline-images.js.map +1 -0
  54. package/packages/mailx-types/mime-inline-images.ts +55 -0
  55. package/packages/mailx-types/package.json +1 -1
  56. package/packages/mailx-types/reminder-state.d.ts +42 -0
  57. package/packages/mailx-types/reminder-state.d.ts.map +1 -0
  58. package/packages/mailx-types/reminder-state.js +85 -0
  59. package/packages/mailx-types/reminder-state.js.map +1 -0
  60. package/packages/mailx-types/reminder-state.ts +88 -0
  61. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-69680 → node_modules.npmglobalize-stash-48352}/.package-lock.json +0 -0
@@ -2328,10 +2328,91 @@ var init_groups = __esm({
2328
2328
  }
2329
2329
  });
2330
2330
 
2331
+ // packages/mailx-types/reminder-state.js
2332
+ function normalizeReminderState(raw, now) {
2333
+ const out = { dismissed: {}, snoozed: {} };
2334
+ if (raw && typeof raw === "object") {
2335
+ for (const [k, v] of Object.entries(raw.dismissed || {})) {
2336
+ if (v === true)
2337
+ out.dismissed[k] = now;
2338
+ else if (Number.isFinite(v) && v > 0)
2339
+ out.dismissed[k] = v;
2340
+ }
2341
+ for (const [k, v] of Object.entries(raw.snoozed || {})) {
2342
+ if (Number.isFinite(v) && v > 0)
2343
+ out.snoozed[k] = v;
2344
+ }
2345
+ }
2346
+ return out;
2347
+ }
2348
+ function mergeReminderStates(a, b, now) {
2349
+ const out = { dismissed: {}, snoozed: {} };
2350
+ const dismissedCutoff = now - DISMISSED_RETENTION_MS;
2351
+ const snoozedCutoff = now - SNOOZED_RETENTION_MS;
2352
+ for (const src of [a.dismissed, b.dismissed]) {
2353
+ for (const [k, v] of Object.entries(src)) {
2354
+ if (v < dismissedCutoff)
2355
+ continue;
2356
+ if (!(k in out.dismissed) || v > out.dismissed[k])
2357
+ out.dismissed[k] = v;
2358
+ }
2359
+ }
2360
+ for (const src of [a.snoozed, b.snoozed]) {
2361
+ for (const [k, v] of Object.entries(src)) {
2362
+ if (v < snoozedCutoff)
2363
+ continue;
2364
+ if (!(k in out.snoozed) || v > out.snoozed[k])
2365
+ out.snoozed[k] = v;
2366
+ }
2367
+ }
2368
+ return out;
2369
+ }
2370
+ function reminderStatesEqual(a, b) {
2371
+ const mapsEqual = (x, y) => {
2372
+ const xk = Object.keys(x);
2373
+ if (xk.length !== Object.keys(y).length)
2374
+ return false;
2375
+ return xk.every((k) => y[k] === x[k]);
2376
+ };
2377
+ return mapsEqual(a.dismissed, b.dismissed) && mapsEqual(a.snoozed, b.snoozed);
2378
+ }
2379
+ var REMINDER_STATE_FILE, DISMISSED_RETENTION_MS, SNOOZED_RETENTION_MS;
2380
+ var init_reminder_state = __esm({
2381
+ "packages/mailx-types/reminder-state.js"() {
2382
+ "use strict";
2383
+ REMINDER_STATE_FILE = "reminders.jsonc";
2384
+ DISMISSED_RETENTION_MS = 30 * 864e5;
2385
+ SNOOZED_RETENTION_MS = 7 * 864e5;
2386
+ }
2387
+ });
2388
+
2389
+ // packages/mailx-types/mime-inline-images.js
2390
+ function extractInlineImages(html, idDomain) {
2391
+ const images = [];
2392
+ const byDataUri = /* @__PURE__ */ new Map();
2393
+ const stamp = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
2394
+ const out = html.replace(/(<img\b[^>]*?\ssrc=)(["'])(data:(image\/[\w.+-]+);base64,([A-Za-z0-9+/=\s]+))\2/gi, (_m, prefix, quote, dataUri, mime, b64) => {
2395
+ let cid = byDataUri.get(dataUri);
2396
+ if (!cid) {
2397
+ cid = `img${images.length + 1}.${stamp}@${idDomain}`;
2398
+ byDataUri.set(dataUri, cid);
2399
+ images.push({ contentId: cid, mime, base64: b64.replace(/\s+/g, "") });
2400
+ }
2401
+ return `${prefix}${quote}cid:${cid}${quote}`;
2402
+ });
2403
+ return { html: out, images };
2404
+ }
2405
+ var init_mime_inline_images = __esm({
2406
+ "packages/mailx-types/mime-inline-images.js"() {
2407
+ "use strict";
2408
+ }
2409
+ });
2410
+
2331
2411
  // packages/mailx-types/index.js
2332
2412
  var mailx_types_exports = {};
2333
2413
  __export(mailx_types_exports, {
2334
2414
  CONTACT_RULES: () => CONTACT_RULES,
2415
+ REMINDER_STATE_FILE: () => REMINDER_STATE_FILE,
2335
2416
  addContactsDenylistEntry: () => addContactsDenylistEntry,
2336
2417
  addContactsPreferredEntry: () => addContactsPreferredEntry,
2337
2418
  answeredOf: () => answeredOf,
@@ -2340,10 +2421,14 @@ __export(mailx_types_exports, {
2340
2421
  encodeQuotedPrintable: () => encodeQuotedPrintable,
2341
2422
  expandRecipients: () => expandRecipients,
2342
2423
  extractAddress: () => extractAddress,
2424
+ extractInlineImages: () => extractInlineImages,
2343
2425
  flaggedOf: () => flaggedOf,
2344
2426
  htmlToPlainText: () => htmlToPlainText,
2345
2427
  isAddressToken: () => isAddressToken,
2428
+ mergeReminderStates: () => mergeReminderStates,
2429
+ normalizeReminderState: () => normalizeReminderState,
2346
2430
  parseSearchQuery: () => parseSearchQuery,
2431
+ reminderStatesEqual: () => reminderStatesEqual,
2347
2432
  sanitizeHtml: () => sanitizeHtml,
2348
2433
  seenOf: () => seenOf,
2349
2434
  setAnswered: () => setAnswered,
@@ -2544,6 +2629,8 @@ var init_mailx_types = __esm({
2544
2629
  init_contact_rules();
2545
2630
  init_contacts_config();
2546
2631
  init_groups();
2632
+ init_reminder_state();
2633
+ init_mime_inline_images();
2547
2634
  _SEEN = "\\Seen";
2548
2635
  _FLAGGED = "\\Flagged";
2549
2636
  _ANSWERED = "\\Answered";
@@ -6107,12 +6194,14 @@ var WebMailxService = class _WebMailxService {
6107
6194
  const cc = msg.cc?.map((a) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
6108
6195
  const bcc = msg.bcc?.map((a) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
6109
6196
  const hasHtml = !!msg.bodyHtml;
6110
- const htmlBody = msg.bodyHtml || "";
6197
+ const domain = account.email.split("@")[1] || "mailx.local";
6198
+ const messageId = `<${Date.now()}.${Math.random().toString(36).slice(2)}@${domain}>`;
6199
+ const extracted = hasHtml ? extractInlineImages(msg.bodyHtml, domain) : { html: "", images: [] };
6200
+ const htmlBody = extracted.html;
6201
+ const inlineImages = extracted.images;
6111
6202
  const textBody = msg.bodyText || (hasHtml ? htmlToPlainText(htmlBody) : "");
6112
6203
  const htmlEncoded = hasHtml ? encodeQuotedPrintable(htmlBody) : "";
6113
6204
  const textEncoded = encodeQuotedPrintable(textBody);
6114
- const domain = account.email.split("@")[1] || "mailx.local";
6115
- const messageId = `<${Date.now()}.${Math.random().toString(36).slice(2)}@${domain}>`;
6116
6205
  const envelope = [
6117
6206
  `From: ${fromHeader}`,
6118
6207
  `To: ${to}`,
@@ -6125,9 +6214,11 @@ var WebMailxService = class _WebMailxService {
6125
6214
  msg.references?.length ? `References: ${msg.references.join(" ")}` : null,
6126
6215
  `MIME-Version: 1.0`
6127
6216
  ].filter((h) => h !== null);
6217
+ const newBoundary = () => `mailx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
6218
+ const wrap76 = (s) => s.match(/.{1,76}/g)?.join("\r\n") || s;
6128
6219
  const makeInner = () => {
6129
6220
  if (hasHtml) {
6130
- const altBoundary = `mailx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
6221
+ const altBoundary = newBoundary();
6131
6222
  const body = `--${altBoundary}\r
6132
6223
  Content-Type: text/plain; charset=UTF-8\r
6133
6224
  Content-Transfer-Encoding: quoted-printable\r
@@ -6140,7 +6231,29 @@ Content-Transfer-Encoding: quoted-printable\r
6140
6231
  ${htmlEncoded}\r
6141
6232
  --${altBoundary}--\r
6142
6233
  `;
6143
- return { headers: [`Content-Type: multipart/alternative; boundary="${altBoundary}"`], body };
6234
+ const alt = { headers: [`Content-Type: multipart/alternative; boundary="${altBoundary}"`], body };
6235
+ if (inlineImages.length === 0)
6236
+ return alt;
6237
+ const relBoundary = newBoundary();
6238
+ const relParts = [
6239
+ `--${relBoundary}\r
6240
+ ` + alt.headers.join("\r\n") + `\r
6241
+ \r
6242
+ ` + alt.body,
6243
+ ...inlineImages.map((im) => `--${relBoundary}\r
6244
+ Content-Type: ${im.mime}\r
6245
+ Content-ID: <${im.contentId}>\r
6246
+ Content-Disposition: inline\r
6247
+ Content-Transfer-Encoding: base64\r
6248
+ \r
6249
+ ${wrap76(im.base64)}\r
6250
+ `)
6251
+ ];
6252
+ return {
6253
+ headers: [`Content-Type: multipart/related; boundary="${relBoundary}"; type="multipart/alternative"`],
6254
+ body: relParts.join("") + `--${relBoundary}--\r
6255
+ `
6256
+ };
6144
6257
  }
6145
6258
  return {
6146
6259
  headers: [`Content-Type: text/plain; charset=UTF-8`, `Content-Transfer-Encoding: quoted-printable`],
@@ -6150,7 +6263,6 @@ ${htmlEncoded}\r
6150
6263
  const atts = Array.isArray(msg.attachments) ? msg.attachments : [];
6151
6264
  let rawMessage;
6152
6265
  if (atts.length > 0) {
6153
- const wrap76 = (s) => s.match(/.{1,76}/g)?.join("\r\n") || s;
6154
6266
  const mixedBoundary = `mailxmix_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
6155
6267
  const inner = makeInner();
6156
6268
  const parts = [];
@@ -6640,6 +6752,51 @@ ${bodyEncoded}`;
6640
6752
  async deleteTask(_uuid) {
6641
6753
  return this.notImpl("deleteTask");
6642
6754
  }
6755
+ // Reminder dismissed/snoozed state — same reminders.jsonc on the shared
6756
+ // Drive folder the desktop uses, same mailx-types merge. Alarms don't
6757
+ // fire on Android today (getCalendarEvents/getTasks return []), but the
6758
+ // API is live so this device still contributes its state and adopts the
6759
+ // desktops' — the moment calendar lands here, sync just works.
6760
+ reminderStateCache = null;
6761
+ async getReminderState() {
6762
+ if (this.reminderStateCache && Date.now() - this.reminderStateCache.at < 6e4) {
6763
+ return this.reminderStateCache.state;
6764
+ }
6765
+ const now = Date.now();
6766
+ let state = { dismissed: {}, snoozed: {} };
6767
+ try {
6768
+ const json = await cloudRead(REMINDER_STATE_FILE);
6769
+ if (json != null)
6770
+ state = normalizeReminderState(JSON.parse(json), now);
6771
+ } catch {
6772
+ }
6773
+ this.reminderStateCache = { state, at: Date.now() };
6774
+ return state;
6775
+ }
6776
+ async mergeReminderState(patch) {
6777
+ const now = Date.now();
6778
+ const local = normalizeReminderState(patch, now);
6779
+ let cloud = { dismissed: {}, snoozed: {} };
6780
+ let hadCloud = false;
6781
+ try {
6782
+ const json = await cloudRead(REMINDER_STATE_FILE);
6783
+ if (json != null) {
6784
+ cloud = normalizeReminderState(JSON.parse(json), now);
6785
+ hadCloud = true;
6786
+ }
6787
+ } catch {
6788
+ }
6789
+ const merged = mergeReminderStates(cloud, local, now);
6790
+ if (!(hadCloud && reminderStatesEqual(merged, cloud))) {
6791
+ try {
6792
+ await cloudWrite(REMINDER_STATE_FILE, JSON.stringify(merged, null, 2) + "\n");
6793
+ } catch (e) {
6794
+ console.error(`[reminders] cloud write failed: ${e?.message || e}`);
6795
+ }
6796
+ }
6797
+ this.reminderStateCache = { state: merged, at: Date.now() };
6798
+ return merged;
6799
+ }
6643
6800
  // User dictionary (cloud-mirrored) — Android can implement these via
6644
6801
  // the same userdict.csv cloud round-trip the desktop uses; until that
6645
6802
  // wire-up lands, return safe defaults so spellcheck.ts catch() paths
@@ -11346,6 +11503,10 @@ function installBridge() {
11346
11503
  return { ok: true };
11347
11504
  },
11348
11505
  loadContactsConfig: () => service.loadContactsConfig(),
11506
+ // Cross-device reminder dismissed/snoozed state (reminders.jsonc on
11507
+ // Drive) — alarm poller pushes/pulls through these.
11508
+ getReminderState: () => service.getReminderState(),
11509
+ mergeReminderState: (patch) => service.mergeReminderState(patch),
11349
11510
  syncAll: async () => {
11350
11511
  await service.syncAll();
11351
11512
  return { ok: true };