@bobfrankston/rmfmail 1.0.546 → 1.0.548

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 (40) hide show
  1. package/bin/mailx.js +13 -0
  2. package/bin/mailx.js.map +1 -1
  3. package/bin/mailx.ts +13 -0
  4. package/client/app.js +51 -1
  5. package/client/app.js.map +1 -1
  6. package/client/app.ts +50 -1
  7. package/client/components/message-list.js +1 -3
  8. package/client/components/message-list.js.map +1 -1
  9. package/client/components/message-list.ts +1 -3
  10. package/client/components/message-viewer.js +65 -97
  11. package/client/components/message-viewer.js.map +1 -1
  12. package/client/components/message-viewer.ts +51 -90
  13. package/client/lib/api-client.js +5 -0
  14. package/client/lib/api-client.js.map +1 -1
  15. package/client/lib/api-client.ts +5 -1
  16. package/client/styles/components.css +0 -12
  17. package/package.json +1 -1
  18. package/packages/mailx-server/index.d.ts.map +1 -1
  19. package/packages/mailx-server/index.js +13 -0
  20. package/packages/mailx-server/index.js.map +1 -1
  21. package/packages/mailx-server/index.ts +14 -0
  22. package/packages/mailx-service/index.d.ts +26 -0
  23. package/packages/mailx-service/index.d.ts.map +1 -1
  24. package/packages/mailx-service/index.js +82 -235
  25. package/packages/mailx-service/index.js.map +1 -1
  26. package/packages/mailx-service/index.ts +45 -54
  27. package/packages/mailx-service/local-store.d.ts +5 -0
  28. package/packages/mailx-service/local-store.d.ts.map +1 -1
  29. package/packages/mailx-service/local-store.js +112 -12
  30. package/packages/mailx-service/local-store.js.map +1 -1
  31. package/packages/mailx-service/reconciler.d.ts +62 -0
  32. package/packages/mailx-service/reconciler.d.ts.map +1 -0
  33. package/packages/mailx-service/reconciler.js +141 -0
  34. package/packages/mailx-service/reconciler.js.map +1 -0
  35. package/packages/mailx-service/reconciler.ts +151 -0
  36. package/packages/mailx-service/sync-queue.d.ts +79 -0
  37. package/packages/mailx-service/sync-queue.d.ts.map +1 -0
  38. package/packages/mailx-service/sync-queue.js +118 -0
  39. package/packages/mailx-service/sync-queue.js.map +1 -0
  40. package/packages/mailx-service/sync-queue.ts +134 -0
@@ -14,6 +14,8 @@ import { ImapManager } from "@bobfrankston/mailx-imap";
14
14
  import * as gsync from "./google-sync.js";
15
15
  import { sniffAndFixCharset } from "./charset.js";
16
16
  import { LocalStore } from "./local-store.js";
17
+ import { SyncQueue } from "./sync-queue.js";
18
+ import { Reconciler } from "./reconciler.js";
17
19
  import { loadSettings, saveSettings, loadAccounts, loadAccountsAsync, saveAccounts, initCloudConfig, loadAllowlist, saveAllowlist, loadAutocomplete, saveAutocomplete, loadKeys, saveKeys, ensureKeysSectionExists, getStorePath, getStorageInfo, getConfigDir } from "@bobfrankston/mailx-settings";
18
20
  import type { AccountConfig, Folder, AutocompleteRequest, AutocompleteResponse, AutocompleteSettings, AiTransformRequest, AiTransformResponse, ExtractedEvent } from "@bobfrankston/mailx-types";
19
21
  import { sanitizeHtml, encodeQuotedPrintable, htmlToPlainText } from "@bobfrankston/mailx-types";
@@ -121,22 +123,25 @@ export class MailxService {
121
123
 
122
124
  /** Local-first read/write facade. Every UI IPC handler that touches the
123
125
  * local DB or body store goes through this — no awaiting IMAP, no
124
- * awaiting Gmail API, no awaiting SMTP. The reconciler (step 4) owns
125
- * all server I/O. See docs/local-first-plan.md. */
126
+ * awaiting Gmail API, no awaiting SMTP. See docs/local-first-plan.md. */
126
127
  private localStore: LocalStore;
127
128
 
128
- /** Background body-fetch dedupe set: `${accountId}:${folderId}:${uid}`.
129
- * When the UI requests a not-yet-cached message we kick off a single
130
- * fire-and-forget IMAP fetch and emit `bodyAvailable` when it lands.
131
- * Subsequent clicks on the same row before the fetch finishes are
132
- * no-ops they'll see `cached: true` once the event arrives. */
133
- private bodyFetchInFlight = new Set<string>();
129
+ /** Persistent (and in-memory body-fetch) queue. UI handlers commit
130
+ * locally, then enqueue a server-mirror task here. */
131
+ private syncQueue: SyncQueue;
132
+
133
+ /** Background loop: drains body-fetch tasks, retries failed message
134
+ * actions, emits sync-state events for the status pill. */
135
+ private reconciler: Reconciler;
134
136
 
135
137
  constructor(
136
138
  private db: MailxDB,
137
139
  private imapManager: ImapManager,
138
140
  ) {
139
141
  this.localStore = new LocalStore(db, imapManager.getBodyStore());
142
+ this.syncQueue = new SyncQueue(db, imapManager);
143
+ this.reconciler = new Reconciler(db, imapManager, this.syncQueue);
144
+ this.reconciler.start();
140
145
 
141
146
  // Invalidate account cache when accounts.jsonc changes on disk or GDrive.
142
147
  this.imapManager.on?.("configChanged", (filename: string) => {
@@ -390,10 +395,12 @@ export class MailxService {
390
395
  if (!local) throw new Error("Message not found");
391
396
 
392
397
  if (!local.cached) {
393
- // Body not on disk. Trigger a single in-flight fetch and return
394
- // envelope-only the UI will show its placeholder and re-request
395
- // when it sees `bodyAvailable`.
396
- this.kickOffBodyFetch(accountId, local.uid as any, local.folderId as any);
398
+ // Body not on disk. Enqueue an interactive-lane fetch the
399
+ // reconciler picks it up, emits `bodyAvailable` on success or
400
+ // `bodyFetchError` on failure. Return envelope-only; UI shows
401
+ // a placeholder and re-renders on the event.
402
+ this.syncQueue.enqueueBodyFetch(accountId, local.folderId as any, local.uid as any, "interactive");
403
+ this.reconciler.kick();
397
404
  return local;
398
405
  }
399
406
 
@@ -410,37 +417,11 @@ export class MailxService {
410
417
  return { ...local, reputation };
411
418
  }
412
419
 
413
- /** Fire-and-forget body fetch for a not-yet-cached message. Dedupes per
414
- * (account, folder, uid) so rapid re-clicks don't stack IMAP fetches.
415
- * Emits `bodyAvailable` on success (UI re-requests via getMessage) and
416
- * `bodyFetchError` on failure (UI surfaces a banner without blocking). */
417
- private kickOffBodyFetch(accountId: string, uid: number, folderId: number): void {
418
- const key = `${accountId}:${folderId}:${uid}`;
419
- if (this.bodyFetchInFlight.has(key)) return;
420
- this.bodyFetchInFlight.add(key);
421
-
422
- (async () => {
423
- try {
424
- const raw = await this.imapManager.fetchMessageBody(accountId, folderId, uid);
425
- if (raw) {
426
- this.imapManager.emit("bodyAvailable", { accountId, folderId, uid });
427
- }
428
- } catch (err: any) {
429
- if (err?.isNotFound) {
430
- try {
431
- this.db.deleteMessage(accountId, uid);
432
- this.db.recalcFolderCounts(folderId);
433
- this.imapManager.emit("messageRemoved", { accountId, folderId, uid });
434
- } catch { /* ignore */ }
435
- return;
436
- }
437
- const msg = err?.message || "body fetch failed";
438
- const transient = /connection|Too many|UNAVAILABLE|rate|429|5\d\d|timeout|ENOTFOUND|ECONNRESET|ETIMEDOUT/i.test(msg);
439
- this.imapManager.emit("bodyFetchError", { accountId, folderId, uid, error: msg, transient });
440
- } finally {
441
- this.bodyFetchInFlight.delete(key);
442
- }
443
- })();
420
+ /** Diagnostic accessor exposed for the sync-status pill / debug UI.
421
+ * Returns the current queue counts so the UI can render
422
+ * "Sync OK / Syncing N items" without polling per-account state. */
423
+ getSyncStatus(): { messageActions: number; bodyFetches: number } {
424
+ return this.syncQueue.pendingCount();
444
425
  }
445
426
 
446
427
  /** RFC 8058 one-click unsubscribe: POST `List-Unsubscribe=One-Click` to the
@@ -1079,7 +1060,7 @@ export class MailxService {
1079
1060
  .catch(e => this.handleGoogleRefreshError("calendar", e));
1080
1061
  }
1081
1062
  }
1082
- return this.db.getCalendarEvents(acctId, fromMs, toMs);
1063
+ return this.localStore.getCalendarEvents(acctId, fromMs, toMs);
1083
1064
  }
1084
1065
 
1085
1066
  /** Returns true if the feature is currently in a quota-exceeded cooldown. */
@@ -1236,7 +1217,7 @@ export class MailxService {
1236
1217
  .catch(e => this.handleGoogleRefreshError("tasks", e));
1237
1218
  }
1238
1219
  }
1239
- return this.db.getTasks(acctId, includeCompleted);
1220
+ return this.localStore.getTasks(acctId, includeCompleted);
1240
1221
  }
1241
1222
 
1242
1223
  private async refreshTasks(accountId: string, includeCompleted: boolean): Promise<boolean> {
@@ -1887,8 +1868,22 @@ export class MailxService {
1887
1868
  async getAttachment(accountId: string, uid: number, attachmentId: number, folderId?: number): Promise<{ content: Buffer; contentType: string; filename: string }> {
1888
1869
  const envelope = this.db.getMessageByUid(accountId, uid, folderId);
1889
1870
  if (!envelope) throw new Error("Message not found");
1890
- const raw = await this.imapManager.fetchMessageBody(accountId, envelope.folderId, envelope.uid);
1871
+
1872
+ // Prefer the on-disk body — when the user just opened the message,
1873
+ // LocalStore.getMessage either returned cached:true (body already on
1874
+ // disk) or kicked a background fetch that wrote it. Either way a
1875
+ // re-fetch here would be wasted IMAP work and risks racing.
1876
+ const bodyStore = this.imapManager.getBodyStore();
1877
+ let raw: Buffer | null = null;
1878
+ const storedPath = envelope.bodyPath || this.db.getMessageBodyPath(accountId, uid) || "";
1879
+ if (storedPath && await bodyStore.hasByPath(storedPath)) {
1880
+ try { raw = await bodyStore.readByPath(storedPath); } catch { raw = null; }
1881
+ }
1882
+ if (!raw) {
1883
+ raw = await this.imapManager.fetchMessageBody(accountId, envelope.folderId, envelope.uid);
1884
+ }
1891
1885
  if (!raw) throw new Error("Message body not available");
1886
+
1892
1887
  const parsed = await simpleParser(raw);
1893
1888
  const att = parsed.attachments?.[attachmentId];
1894
1889
  if (!att) throw new Error("Attachment not found");
@@ -1954,14 +1949,10 @@ export class MailxService {
1954
1949
  }
1955
1950
  } catch { /* non-fatal — draft stays in memory at least */ }
1956
1951
 
1957
- // Background reconcile to server Drafts folder. Fire-and-forget
1958
- // the ACK to the client is already on its way.
1959
- this.imapManager.saveDraft(accountId, raw, previousDraftUid, id).catch((e: any) => {
1960
- console.error(` [draft] background IMAP save failed for ${id}: ${e?.message || e}`);
1961
- // Surface as an event so the UI can show a status-bar hint without
1962
- // blocking the caller. Draft is preserved on disk regardless.
1963
- (this as any).emit?.("draftSaveDeferred", { accountId, draftId: id, error: String(e?.message || e) });
1964
- });
1952
+ // Background reconcile to server Drafts folder via the SyncQueue
1953
+ // seam. Fire-and-forget; deferred failures surface as a `draftSaveDeferred`
1954
+ // event the UI can render in the status bar.
1955
+ this.syncQueue.enqueueDraftPush(accountId, raw, previousDraftUid, id);
1965
1956
 
1966
1957
  return { draftUid: null, draftId: id };
1967
1958
  }
@@ -38,6 +38,11 @@ export interface LocalMessage extends MessageEnvelope {
38
38
  deliveredTo: string;
39
39
  returnPath: string;
40
40
  listUnsubscribe: string;
41
+ listUnsubscribeMail: string;
42
+ listUnsubscribeHttp: string;
43
+ listUnsubscribeOneClick: boolean;
44
+ emlPath: string;
45
+ isFlagged: boolean;
41
46
  }
42
47
  export declare class LocalStore {
43
48
  private db;
@@ -1 +1 @@
1
- {"version":3,"file":"local-store.d.ts","sourceRoot":"","sources":["local-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,KAAK,EACR,eAAe,EAAW,YAAY,EAAE,WAAW,EACtD,MAAM,2BAA2B,CAAC;AAInC;;;;mEAImE;AACnE,MAAM,WAAW,YAAa,SAAQ,eAAe;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxG,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,UAAU;IAEf,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,SAAS;gBADT,EAAE,EAAE,OAAO,EACX,SAAS,EAAE,gBAAgB;IAMvC;;;6DAGyD;IACzD,WAAW,IAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE;IAM9E,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE;IAMpC;;4CAEwC;IACxC,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAK7F,iEAAiE;IACjE,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,WAAW,CAAC,eAAe,CAAC;IAI9D,mEAAmE;IACnE,eAAe,CAAC,IAAI,SAAI,EAAE,QAAQ,SAAK,GAAG,WAAW,CAAC,eAAe,CAAC;IAItE,sEAAsE;IACtE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAI,EAAE,QAAQ,SAAK,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,eAAe,CAAC;IAM3H;;;;;;;sEAOkE;IAC5D,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IA+EvH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE;IAIzE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,UAAQ,GAAG,GAAG,EAAE;IAI5D,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,GAAG,EAAE;IAIhD,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAI,EAAE,QAAQ,SAAM,GAAG,GAAG;CAK7D"}
1
+ {"version":3,"file":"local-store.d.ts","sourceRoot":"","sources":["local-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,KAAK,EACR,eAAe,EAAW,YAAY,EAAE,WAAW,EACtD,MAAM,2BAA2B,CAAC;AAuCnC;;;;mEAImE;AACnE,MAAM,WAAW,YAAa,SAAQ,eAAe;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxG,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,uBAAuB,EAAE,OAAO,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,UAAU;IAEf,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,SAAS;gBADT,EAAE,EAAE,OAAO,EACX,SAAS,EAAE,gBAAgB;IAMvC;;;6DAGyD;IACzD,WAAW,IAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE;IAM9E,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE;IAMpC;;4CAEwC;IACxC,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAK7F,iEAAiE;IACjE,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,WAAW,CAAC,eAAe,CAAC;IAI9D,mEAAmE;IACnE,eAAe,CAAC,IAAI,SAAI,EAAE,QAAQ,SAAK,GAAG,WAAW,CAAC,eAAe,CAAC;IAItE,sEAAsE;IACtE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAI,EAAE,QAAQ,SAAK,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,eAAe,CAAC;IAM3H;;;;;;;sEAOkE;IAC5D,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAmJvH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE;IAIzE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,UAAQ,GAAG,GAAG,EAAE;IAI5D,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,GAAG,EAAE;IAIhD,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAI,EAAE,QAAQ,SAAM,GAAG,GAAG;CAK7D"}
@@ -17,7 +17,43 @@
17
17
  */
18
18
  import { simpleParser } from "mailparser";
19
19
  import { sanitizeHtml } from "@bobfrankston/mailx-types";
20
+ import { loadSettings, loadAllowlist } from "@bobfrankston/mailx-settings";
20
21
  import { sniffAndFixCharset } from "./charset.js";
22
+ /** Parse `List-Unsubscribe` (RFC 2369) and `List-Unsubscribe-Post` (RFC 8058).
23
+ * mailparser only exposes ONE of mail/url even when both are present, so we
24
+ * also scan the raw header text for the full set of angle-bracketed URIs. */
25
+ function parseListUnsubscribe(headers) {
26
+ let mail = "";
27
+ let http = "";
28
+ let oneClick = false;
29
+ const raw = headers.get("list-unsubscribe");
30
+ const rawStr = typeof raw === "string" ? raw : (raw && typeof raw.text === "string" ? raw.text : "");
31
+ if (rawStr) {
32
+ const matches = rawStr.match(/<([^>]+)>/g) || [];
33
+ for (const m of matches) {
34
+ const url = m.slice(1, -1).trim();
35
+ if (!mail && /^mailto:/i.test(url))
36
+ mail = url;
37
+ else if (!http && /^https?:/i.test(url))
38
+ http = url;
39
+ }
40
+ }
41
+ if (!mail && !http) {
42
+ const listHeaders = headers.get("list");
43
+ if (listHeaders?.unsubscribe) {
44
+ const unsub = listHeaders.unsubscribe;
45
+ if (unsub.url)
46
+ http = Array.isArray(unsub.url) ? unsub.url[0] : unsub.url;
47
+ if (unsub.mail)
48
+ mail = `mailto:${Array.isArray(unsub.mail) ? unsub.mail[0] : unsub.mail}`;
49
+ }
50
+ }
51
+ const post = headers.get("list-unsubscribe-post");
52
+ const postStr = typeof post === "string" ? post : (post && typeof post.text === "string" ? post.text : "");
53
+ if (postStr && /one-?click/i.test(postStr))
54
+ oneClick = true;
55
+ return { listUnsubscribeMail: mail, listUnsubscribeHttp: http, listUnsubscribeOneClick: oneClick };
56
+ }
21
57
  export class LocalStore {
22
58
  db;
23
59
  bodyStore;
@@ -71,6 +107,24 @@ export class LocalStore {
71
107
  const envelope = this.db.getMessageByUid(accountId, uid, folderId);
72
108
  if (!envelope)
73
109
  return null;
110
+ const allowList = loadAllowlist();
111
+ const senderAddr = (envelope.from?.address || "").toLowerCase();
112
+ const senderDomain = senderAddr.split("@")[1] || "";
113
+ const toAddrs = (envelope.to || []).map((a) => (a.address || "").toLowerCase());
114
+ // Allowlist auto-allow: trusted sender / domain / recipient skips
115
+ // sanitization. Same rule as the legacy service implementation.
116
+ if (!allowRemote) {
117
+ const senders = (allowList.senders || []).map((s) => (s || "").toLowerCase());
118
+ const domains = (allowList.domains || []).map((d) => (d || "").toLowerCase());
119
+ const recipients = (allowList.recipients || []).map((r) => (r || "").toLowerCase());
120
+ if (senders.includes(senderAddr) ||
121
+ domains.includes(senderDomain) ||
122
+ toAddrs.some((a) => recipients.includes(a))) {
123
+ allowRemote = true;
124
+ }
125
+ }
126
+ const isFlagged = !!((allowList.flaggedSenders || []).some((s) => (s || "").toLowerCase() === senderAddr) ||
127
+ (allowList.flaggedDomains || []).some((d) => (d || "").toLowerCase() === senderDomain));
74
128
  // Resolve body path: prefer the row's own bodyPath, fall back to
75
129
  // the historical lookup. Either may be empty (body never fetched).
76
130
  let storedPath = envelope.bodyPath || "";
@@ -79,10 +133,13 @@ export class LocalStore {
79
133
  const empty = {
80
134
  ...envelope,
81
135
  bodyHtml: "", bodyText: "",
82
- hasRemoteContent: false, remoteAllowed: false,
136
+ hasRemoteContent: false, remoteAllowed: allowRemote,
83
137
  attachments: [],
84
138
  cached: false,
85
- deliveredTo: "", returnPath: "", listUnsubscribe: "",
139
+ deliveredTo: "", returnPath: "",
140
+ listUnsubscribe: "", listUnsubscribeMail: "", listUnsubscribeHttp: "", listUnsubscribeOneClick: false,
141
+ emlPath: "",
142
+ isFlagged,
86
143
  };
87
144
  if (!storedPath)
88
145
  return empty;
@@ -107,7 +164,6 @@ export class LocalStore {
107
164
  let bodyHtml = parsed.html || "";
108
165
  const bodyText = parsed.text || "";
109
166
  let hasRemoteContent = false;
110
- let remoteAllowed = allowRemote;
111
167
  const attachments = (parsed.attachments || []).map((a, i) => ({
112
168
  id: i,
113
169
  filename: a.filename || `attachment-${i}`,
@@ -120,26 +176,70 @@ export class LocalStore {
120
176
  bodyHtml = result.html;
121
177
  hasRemoteContent = result.hasRemoteContent;
122
178
  }
123
- // Header extraction — Delivered-To, Return-Path, List-Unsubscribe
124
- // and friends. simpleParser exposes them via `headers`.
179
+ // Header extraction — Delivered-To with relay-domain unwrapping,
180
+ // Return-Path, List-Unsubscribe variants. The relay/prefix rules
181
+ // come from accounts.jsonc settings; cheap local read.
182
+ const settings = loadSettings() || {};
183
+ const acctConfig = (settings.accounts || []).find((a) => a.id === accountId) || {};
184
+ const relayDomains = acctConfig.relayDomains || [];
185
+ const prefixes = acctConfig.deliveredToPrefix || [];
186
+ let deliveredTo = "";
187
+ const rawDelivered = parsed.headers.get("delivered-to");
188
+ if (rawDelivered) {
189
+ const deliveredList = Array.isArray(rawDelivered) ? rawDelivered : [rawDelivered];
190
+ for (let i = deliveredList.length - 1; i >= 0; i--) {
191
+ const d = deliveredList[i];
192
+ const addr = typeof d === "string" ? d : d?.text || d?.address || String(d);
193
+ if (!relayDomains.some(rd => addr.includes(`@${rd}`))) {
194
+ deliveredTo = addr;
195
+ break;
196
+ }
197
+ }
198
+ if (!deliveredTo && deliveredList.length > 0) {
199
+ const d = deliveredList[deliveredList.length - 1];
200
+ deliveredTo = typeof d === "string" ? d : d?.text || d?.address || String(d);
201
+ }
202
+ if (deliveredTo && prefixes.length > 0) {
203
+ const [local, domain] = deliveredTo.split("@");
204
+ for (const prefix of prefixes) {
205
+ if (local.startsWith(prefix)) {
206
+ deliveredTo = `${local.slice(prefix.length)}@${domain}`;
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ }
125
212
  const hdr = (key) => {
126
- const v = parsed.headers.get(key);
213
+ let v = parsed.headers.get(key);
127
214
  if (!v)
128
215
  return "";
216
+ if (Array.isArray(v))
217
+ v = v[0];
129
218
  if (typeof v === "string")
130
219
  return v;
131
- return String(v?.value || "");
220
+ if (typeof v === "object" && v !== null) {
221
+ if ("text" in v)
222
+ return v.text || "";
223
+ if ("value" in v)
224
+ return String(v.value);
225
+ if ("address" in v)
226
+ return v.address || "";
227
+ }
228
+ return String(v);
132
229
  };
133
- const deliveredTo = hdr("delivered-to");
134
- const returnPath = hdr("return-path");
135
- const listUnsubscribe = hdr("list-unsubscribe");
230
+ const returnPath = hdr("return-path").replace(/[<>]/g, "");
231
+ const { listUnsubscribeMail, listUnsubscribeHttp, listUnsubscribeOneClick } = parseListUnsubscribe(parsed.headers);
232
+ const listUnsubscribe = listUnsubscribeHttp || listUnsubscribeMail;
136
233
  return {
137
234
  ...envelope,
138
235
  bodyHtml, bodyText,
139
- hasRemoteContent, remoteAllowed,
236
+ hasRemoteContent, remoteAllowed: allowRemote,
140
237
  attachments,
141
238
  cached: true,
142
- deliveredTo, returnPath, listUnsubscribe,
239
+ deliveredTo, returnPath,
240
+ listUnsubscribe, listUnsubscribeMail, listUnsubscribeHttp, listUnsubscribeOneClick,
241
+ emlPath: storedPath,
242
+ isFlagged,
143
243
  };
144
244
  }
145
245
  // ── Calendar / tasks / contacts (read paths) ──
@@ -1 +1 @@
1
- {"version":3,"file":"local-store.js","sourceRoot":"","sources":["local-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local-store.js","sourceRoot":"","sources":["local-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Reconciler — single background loop that drains the SyncQueue, polls
3
+ * for server changes, and emits state events the UI listens for.
4
+ *
5
+ * Local-first contract: the Reconciler is the ONLY code path that touches
6
+ * IMAP / Gmail API on behalf of UI actions. UI handlers commit locally
7
+ * and enqueue; the Reconciler picks up and mirrors. Reads from the UI
8
+ * never await the Reconciler.
9
+ *
10
+ * What this owns today:
11
+ * - body-fetch lane: drains `SyncQueue.nextBodyFetch()`, calls
12
+ * `imapManager.fetchMessageBody`, emits `bodyAvailable` / `bodyFetchError`.
13
+ * This is the new piece; previously body fetches happened inline on
14
+ * every getMessage call.
15
+ * - sync-state pill events: emits `syncStateChanged` so the UI can show
16
+ * "Sync OK / Syncing N items".
17
+ *
18
+ * What it delegates (intentional — no duplication):
19
+ * - message-action drain: ImapManager already runs a 30s actionsInterval
20
+ * that processes both outbox (processSendActions) and sync_actions
21
+ * (processSyncActions) per account. Per plan decision #5, outbox stays
22
+ * under ImapManager; we don't fork that work.
23
+ * - poll loops (per-account quick check, full sync, prefetch, tombstone
24
+ * prune): all owned by ImapManager's startPeriodicSync. The Reconciler
25
+ * does NOT shadow them — back-pressure on those timers can be added
26
+ * later via a single `setBackPressure(level)` hook.
27
+ *
28
+ * Part of docs/local-first-plan.md (step 4).
29
+ */
30
+ import type { ImapManager } from "@bobfrankston/mailx-imap";
31
+ import type { MailxDB } from "@bobfrankston/mailx-store";
32
+ import type { SyncQueue } from "./sync-queue.js";
33
+ export interface ReconcilerOptions {
34
+ /** Max concurrent body fetches across all accounts (in addition to
35
+ * per-account semaphore in ImapManager). Keeps the interactive lane
36
+ * responsive even when prefetch is hammering. */
37
+ bodyFetchConcurrency?: number;
38
+ /** Periodic emit of syncStateChanged — drives the UI status pill. */
39
+ statusEmitIntervalMs?: number;
40
+ }
41
+ export declare class Reconciler {
42
+ private db;
43
+ private imapManager;
44
+ private queue;
45
+ private opts;
46
+ private running;
47
+ private statusTimer;
48
+ private inFlightFetches;
49
+ constructor(db: MailxDB, imapManager: ImapManager, queue: SyncQueue, opts?: ReconcilerOptions);
50
+ start(): void;
51
+ stop(): void;
52
+ /** User-visible "sync now" — runs an immediate poll on all accounts
53
+ * (or just one) without waiting for the next periodic tick. */
54
+ syncNow(accountId?: string): void;
55
+ /** Drain the body-fetch lane up to `bodyFetchConcurrency`. Each
56
+ * fetch is fire-and-forget; on completion it kicks the pump again
57
+ * so a queue that grows during work stays drained. */
58
+ private pumpBodyFetches;
59
+ /** External nudge — call after enqueueing a high-priority body fetch. */
60
+ kick(): void;
61
+ }
62
+ //# sourceMappingURL=reconciler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconciler.d.ts","sourceRoot":"","sources":["reconciler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAC9B;;sDAEkD;IAClD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qEAAqE;IACrE,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACjC;AAOD,qBAAa,UAAU;IAOf,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,KAAK;IARjB,OAAO,CAAC,IAAI,CAA8B;IAC1C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAA+C;IAClE,OAAO,CAAC,eAAe,CAAK;gBAGhB,EAAE,EAAE,OAAO,EACX,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,SAAS,EACxB,IAAI,GAAE,iBAAsB;IAKhC,KAAK,IAAI,IAAI;IAeb,IAAI,IAAI,IAAI;IAKZ;oEACgE;IAChE,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAOjC;;2DAEuD;IACvD,OAAO,CAAC,eAAe;IAmDvB,yEAAyE;IACzE,IAAI,IAAI,IAAI;CAGf"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Reconciler — single background loop that drains the SyncQueue, polls
3
+ * for server changes, and emits state events the UI listens for.
4
+ *
5
+ * Local-first contract: the Reconciler is the ONLY code path that touches
6
+ * IMAP / Gmail API on behalf of UI actions. UI handlers commit locally
7
+ * and enqueue; the Reconciler picks up and mirrors. Reads from the UI
8
+ * never await the Reconciler.
9
+ *
10
+ * What this owns today:
11
+ * - body-fetch lane: drains `SyncQueue.nextBodyFetch()`, calls
12
+ * `imapManager.fetchMessageBody`, emits `bodyAvailable` / `bodyFetchError`.
13
+ * This is the new piece; previously body fetches happened inline on
14
+ * every getMessage call.
15
+ * - sync-state pill events: emits `syncStateChanged` so the UI can show
16
+ * "Sync OK / Syncing N items".
17
+ *
18
+ * What it delegates (intentional — no duplication):
19
+ * - message-action drain: ImapManager already runs a 30s actionsInterval
20
+ * that processes both outbox (processSendActions) and sync_actions
21
+ * (processSyncActions) per account. Per plan decision #5, outbox stays
22
+ * under ImapManager; we don't fork that work.
23
+ * - poll loops (per-account quick check, full sync, prefetch, tombstone
24
+ * prune): all owned by ImapManager's startPeriodicSync. The Reconciler
25
+ * does NOT shadow them — back-pressure on those timers can be added
26
+ * later via a single `setBackPressure(level)` hook.
27
+ *
28
+ * Part of docs/local-first-plan.md (step 4).
29
+ */
30
+ const DEFAULTS = {
31
+ bodyFetchConcurrency: 2,
32
+ statusEmitIntervalMs: 5_000,
33
+ };
34
+ export class Reconciler {
35
+ db;
36
+ imapManager;
37
+ queue;
38
+ opts;
39
+ running = false;
40
+ statusTimer = null;
41
+ inFlightFetches = 0;
42
+ constructor(db, imapManager, queue, opts = {}) {
43
+ this.db = db;
44
+ this.imapManager = imapManager;
45
+ this.queue = queue;
46
+ this.opts = { ...DEFAULTS, ...opts };
47
+ }
48
+ start() {
49
+ if (this.running)
50
+ return;
51
+ this.running = true;
52
+ // Status pill: emit current totals every few seconds so the UI
53
+ // can show "Syncing N items" without polling.
54
+ this.statusTimer = setInterval(() => {
55
+ const counts = this.queue.pendingCount();
56
+ this.imapManager.emit("syncStateChanged", { ...counts, inFlightFetches: this.inFlightFetches });
57
+ }, this.opts.statusEmitIntervalMs);
58
+ // Kick the body-fetch loop.
59
+ this.pumpBodyFetches();
60
+ }
61
+ stop() {
62
+ this.running = false;
63
+ if (this.statusTimer) {
64
+ clearInterval(this.statusTimer);
65
+ this.statusTimer = null;
66
+ }
67
+ }
68
+ /** User-visible "sync now" — runs an immediate poll on all accounts
69
+ * (or just one) without waiting for the next periodic tick. */
70
+ syncNow(accountId) {
71
+ const accts = accountId ? [{ id: accountId }] : this.db.getAccounts();
72
+ for (const a of accts) {
73
+ this.imapManager.processSyncActions(a.id).catch(() => { });
74
+ }
75
+ }
76
+ /** Drain the body-fetch lane up to `bodyFetchConcurrency`. Each
77
+ * fetch is fire-and-forget; on completion it kicks the pump again
78
+ * so a queue that grows during work stays drained. */
79
+ pumpBodyFetches() {
80
+ if (!this.running)
81
+ return;
82
+ while (this.inFlightFetches < this.opts.bodyFetchConcurrency) {
83
+ const next = this.queue.nextBodyFetch();
84
+ if (!next)
85
+ return;
86
+ this.inFlightFetches++;
87
+ (async () => {
88
+ try {
89
+ const raw = await this.imapManager.fetchMessageBody(next.accountId, next.folderId, next.uid);
90
+ if (raw) {
91
+ this.imapManager.emit("bodyAvailable", {
92
+ accountId: next.accountId, folderId: next.folderId, uid: next.uid,
93
+ });
94
+ }
95
+ }
96
+ catch (err) {
97
+ if (err?.isNotFound) {
98
+ try {
99
+ this.db.deleteMessage(next.accountId, next.uid);
100
+ this.db.recalcFolderCounts(next.folderId);
101
+ this.imapManager.emit("messageRemoved", {
102
+ accountId: next.accountId, folderId: next.folderId, uid: next.uid,
103
+ });
104
+ }
105
+ catch { /* */ }
106
+ }
107
+ else {
108
+ const msg = err?.message || "body fetch failed";
109
+ const transient = /connection|Too many|UNAVAILABLE|rate|429|5\d\d|timeout|ENOTFOUND|ECONNRESET|ETIMEDOUT/i.test(msg);
110
+ // Transient: re-enqueue (budget=3) so the next pump
111
+ // tick retries. Surface the error only when the
112
+ // budget is exhausted — otherwise we'd flash banners
113
+ // for every blip on a slow server.
114
+ if (transient && this.queue.requeueBodyFetch(next)) {
115
+ // Backoff so we don't immediately re-hit the same
116
+ // wedge. Body-fetch lane is in-memory; setTimeout
117
+ // is fine.
118
+ const backoffMs = 500 * Math.pow(2, next.attempts);
119
+ setTimeout(() => this.pumpBodyFetches(), backoffMs);
120
+ }
121
+ else {
122
+ this.imapManager.emit("bodyFetchError", {
123
+ accountId: next.accountId, folderId: next.folderId, uid: next.uid,
124
+ error: msg, transient,
125
+ });
126
+ }
127
+ }
128
+ }
129
+ finally {
130
+ this.inFlightFetches--;
131
+ this.pumpBodyFetches();
132
+ }
133
+ })();
134
+ }
135
+ }
136
+ /** External nudge — call after enqueueing a high-priority body fetch. */
137
+ kick() {
138
+ this.pumpBodyFetches();
139
+ }
140
+ }
141
+ //# sourceMappingURL=reconciler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconciler.js","sourceRoot":"","sources":["reconciler.ts"],"names":[],"mappings":"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"}