@bobfrankston/rmfmail 1.2.77 → 1.2.78

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.
@@ -9796,6 +9796,11 @@ var AndroidSyncManager = class {
9796
9796
  // advanced to fetching new mail, so the list stayed stale despite "Synced"
9797
9797
  // (Bob 2026-06-27). Coalesce concurrent calls to one in-flight run.
9798
9798
  syncAllInflight = null;
9799
+ // Separate guard for the Phase-2/3 background pass (other folders + body
9800
+ // prefetch). It runs DETACHED from the inbox guard so a long folder sweep
9801
+ // can't make the 60s poll skip the inbox refresh (new mail must keep
9802
+ // flowing). One background pass at a time — overlapping triggers no-op.
9803
+ bgSyncInflight = null;
9799
9804
  on(_event, _handler) {
9800
9805
  }
9801
9806
  emit(event, ...args) {
@@ -9850,41 +9855,53 @@ var AndroidSyncManager = class {
9850
9855
  }
9851
9856
  async syncAll() {
9852
9857
  if (this.syncAllInflight) {
9853
- console.log("[sync] syncAll already in flight \u2014 coalescing this trigger");
9858
+ console.log("[sync] inbox sync already in flight \u2014 coalescing this trigger");
9854
9859
  return this.syncAllInflight;
9855
9860
  }
9856
9861
  const t0 = Date.now();
9857
- console.log("[sync] syncAll START");
9858
- this.syncAllInflight = this._syncAllImpl().catch((e) => {
9859
- console.error(`[sync] syncAll error: ${e?.message || e}`);
9862
+ console.log("[sync] syncAll START (inbox phase)");
9863
+ this.syncAllInflight = this._syncInboxes().catch((e) => {
9864
+ console.error(`[sync] inbox sync error: ${e?.message || e}`);
9860
9865
  }).finally(() => {
9861
- console.log(`[sync] syncAll DONE in ${Date.now() - t0}ms`);
9866
+ console.log(`[sync] inbox phase DONE in ${Date.now() - t0}ms`);
9862
9867
  this.syncAllInflight = null;
9863
9868
  });
9869
+ this._kickBackgroundSync();
9864
9870
  return this.syncAllInflight;
9865
9871
  }
9866
- async _syncAllImpl() {
9867
- const accounts = this.db.getAccounts();
9868
- vlog(`syncAll: ${accounts.length} accounts in DB: ${accounts.map((a) => a.id).join(",")}`);
9869
- for (const account of accounts) {
9870
- if (!this.providers.has(account.id)) continue;
9871
- try {
9872
- await withTimeout(`${account.id} inbox`, 6e4, async () => {
9873
- const folders = await this.syncFolders(account.id);
9874
- const inbox = folders.find((f) => f.specialUse === "inbox");
9875
- if (inbox) {
9876
- console.log(`[sync] ${account.id}: fetching INBOX (folderId=${inbox.id})`);
9877
- await this.syncFolder(account.id, inbox.id);
9878
- console.log(`[sync] ${account.id}: INBOX fetch done`);
9879
- emitEvent({ type: "syncComplete", accountId: account.id });
9880
- }
9881
- });
9882
- } catch (e) {
9883
- console.error(`[sync] ${account.id} inbox: ${e.message}`);
9884
- }
9885
- }
9872
+ /** Phase 1: sync every account's INBOX IN PARALLEL so a slow account (e.g.
9873
+ * Gmail's metadata fetch, or an auth-failing IMAP account) can't delay new
9874
+ * mail for the others. Each account is time-boxed; network fetches overlap
9875
+ * even though JS is single-threaded (the awaits yield). */
9876
+ async _syncInboxes() {
9877
+ const accounts = this.db.getAccounts().filter((a) => this.providers.has(a.id));
9878
+ vlog(`syncInboxes: ${accounts.length} accounts: ${accounts.map((a) => a.id).join(",")}`);
9879
+ await Promise.all(accounts.map(
9880
+ (account) => withTimeout(`${account.id} inbox`, 6e4, async () => {
9881
+ const folders = await this.syncFolders(account.id);
9882
+ const inbox = folders.find((f) => f.specialUse === "inbox");
9883
+ if (inbox) {
9884
+ console.log(`[sync] ${account.id}: fetching INBOX (folderId=${inbox.id})`);
9885
+ await this.syncFolder(account.id, inbox.id);
9886
+ console.log(`[sync] ${account.id}: INBOX fetch done`);
9887
+ emitEvent({ type: "syncComplete", accountId: account.id });
9888
+ }
9889
+ }).catch((e) => console.error(`[sync] ${account.id} inbox: ${e?.message || e}`))
9890
+ ));
9891
+ }
9892
+ /** Phase 2 + 3: remaining folders, then body prefetch. Runs in the
9893
+ * background under its own single-flight guard. */
9894
+ _kickBackgroundSync() {
9895
+ if (this.bgSyncInflight) return;
9896
+ const t0 = Date.now();
9897
+ this.bgSyncInflight = this._syncRemainingAndPrefetch().catch((e) => console.error(`[sync] background sweep error: ${e?.message || e}`)).finally(() => {
9898
+ console.log(`[sync] background sweep DONE in ${Date.now() - t0}ms`);
9899
+ this.bgSyncInflight = null;
9900
+ });
9901
+ }
9902
+ async _syncRemainingAndPrefetch() {
9903
+ const accounts = this.db.getAccounts().filter((a) => this.providers.has(a.id));
9886
9904
  for (const account of accounts) {
9887
- if (!this.providers.has(account.id)) continue;
9888
9905
  try {
9889
9906
  const folders = this.db.getFolders(account.id);
9890
9907
  const remaining = folders.filter((f) => f.specialUse !== "inbox");
@@ -9899,12 +9916,10 @@ var AndroidSyncManager = class {
9899
9916
  emitEvent({ type: "syncComplete", accountId: account.id });
9900
9917
  } catch (e) {
9901
9918
  console.error(`[sync] ${account.id}: ${e.message}`);
9902
- vlog(`syncAll: ${account.id} ERROR: ${e.message}`);
9903
9919
  emitEvent({ type: "syncError", accountId: account.id, error: e.message });
9904
9920
  }
9905
9921
  }
9906
9922
  for (const account of accounts) {
9907
- if (!this.providers.has(account.id)) continue;
9908
9923
  this.prefetchBodies(account.id).catch((e) => console.error(`[prefetch] ${account.id}: ${e.message}`));
9909
9924
  }
9910
9925
  }