@bobfrankston/rmfmail 1.2.209 → 1.2.211

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/bin/mailx.ts CHANGED
@@ -2179,22 +2179,46 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2179
2179
  // ANOTHER machine (seen via the reminders.jsonc state sync) can retract
2180
2180
  // a popup that's already on this machine's screen. `popupKey` is
2181
2181
  // supplied by alarms.ts and stripped before the opts reach msger.
2182
- const reminderPopups = new Map<string, { close: () => void }>();
2182
+ const reminderPopups = new Map<string, { close: () => void; result: Promise<any> }>();
2183
2183
  svc.setPopupFn(async (opts: any) => {
2184
2184
  // Own WebView2 profile so a reminder popup can never share (and
2185
2185
  // crash) the main window's browser process — see S67 note on the
2186
2186
  // popout windows below. Service-supplied opts may override.
2187
2187
  const { popupKey, ...boxOpts } = opts || {};
2188
+ // ONE window per occurrence, however many app windows are open.
2189
+ // Every window runs its own alarm poller (alarms.ts is client-side),
2190
+ // so a second main window meant the same reminder spawned two
2191
+ // identical popups — and the second registration clobbered the
2192
+ // first's closer, leaving a popup that cross-machine retraction
2193
+ // could no longer close (Bob 2026-07-31: "two instances running,
2194
+ // two popups"). The late caller joins the SAME result, so both
2195
+ // pollers persist the same dismiss/snooze.
2196
+ if (popupKey) {
2197
+ const open = reminderPopups.get(popupKey);
2198
+ if (open) {
2199
+ console.log(` [reminder] popup ${popupKey} already open — joining it instead of opening a second`);
2200
+ return await open.result;
2201
+ }
2202
+ }
2188
2203
  const h = showMessageBoxEx({ profile: "popout", ...boxOpts });
2189
2204
  if (typeof h.pid === "number") addChildPid(h.pid);
2190
- if (popupKey) reminderPopups.set(popupKey, { close: () => { try { h.close(); } catch { /* already gone */ } } });
2191
- try {
2205
+ const result = (async () => {
2192
2206
  const r = await h.result;
2193
2207
  return { button: r.button, closed: r.closed, dismissed: r.dismissed, form: (r as any).form };
2194
- } finally {
2208
+ })();
2209
+ // Cleanup rides on the shared promise, not on this caller — joiners
2210
+ // must not tear down registration out from under the real window.
2211
+ void result.catch(() => { /* surfaced to the awaiting callers */ }).finally(() => {
2195
2212
  if (typeof h.pid === "number") removeChildPid(h.pid);
2196
2213
  if (popupKey) reminderPopups.delete(popupKey);
2214
+ });
2215
+ if (popupKey) {
2216
+ reminderPopups.set(popupKey, {
2217
+ close: () => { try { h.close(); } catch { /* already gone */ } },
2218
+ result,
2219
+ });
2197
2220
  }
2221
+ return await result;
2198
2222
  });
2199
2223
  svc.setPopupCloserFn((key: string) => {
2200
2224
  const p = reminderPopups.get(key);
@@ -11344,6 +11344,7 @@ function initAndroid() {
11344
11344
  }
11345
11345
  async function initAndroidOnce() {
11346
11346
  console.log("[android] Initializing mailx (main-thread mode)...");
11347
+ narrate("Initializing\u2026");
11347
11348
  await waitForNativeBridge();
11348
11349
  if (window._nativeBridge && !window.msgapi) {
11349
11350
  window.msgapi = window._nativeBridge;
@@ -11355,6 +11356,7 @@ async function initAndroidOnce() {
11355
11356
  service = new WebMailxService(db, bodyStore, syncManager);
11356
11357
  let accounts = await loadAccounts();
11357
11358
  console.log(`[android] ${accounts.length} account(s) found`);
11359
+ narrate(accounts.length ? `Opening ${accounts.map((a) => a.email || a.id).join(", ")}\u2026` : "No local accounts yet \u2014 checking Google Drive\u2026");
11358
11360
  let gmailTokenProvider = null;
11359
11361
  for (const account of accounts) {
11360
11362
  if (!account.enabled) continue;
@@ -11424,12 +11426,16 @@ async function initAndroidOnce() {
11424
11426
  console.log(`[android] GDrive returned ${gdriveAccounts.length} accounts: ${gdriveAccounts.map((a) => a.id).join(",")}`);
11425
11427
  if (gdriveAccounts.length > 0) {
11426
11428
  accounts = gdriveAccounts;
11429
+ const enabled = accounts.filter((a) => a.enabled);
11430
+ narrate(`Found ${enabled.length} account(s): ${enabled.map((a) => a.email || a.id).join(", ")}`);
11431
+ let setUp = 0;
11427
11432
  for (const account of accounts) {
11428
11433
  vlog(`init: registering ${account.id} email=${account.email} enabled=${account.enabled} imap=${JSON.stringify(account.imap)}`);
11429
11434
  if (!account.enabled) {
11430
11435
  vlog(`init: ${account.id} disabled, skipping`);
11431
11436
  continue;
11432
11437
  }
11438
+ narrate(`Setting up ${account.email || account.id}\u2026 (${++setUp}/${enabled.length})`);
11433
11439
  const domain = account.email?.split("@")[1]?.toLowerCase() || "";
11434
11440
  if (domain === "gmail.com" || domain === "googlemail.com") {
11435
11441
  syncManager.setTokenProvider(account.id, createNativeTokenProvider(account.email));
@@ -11437,7 +11443,7 @@ async function initAndroidOnce() {
11437
11443
  await syncManager.addAccount(account);
11438
11444
  }
11439
11445
  console.log(`[android] Loaded ${accounts.length} accounts from GDrive`);
11440
- narrate(`Syncing ${accounts.length} account(s)\u2026`);
11446
+ narrate(`Syncing ${enabled.map((a) => a.email || a.id).join(", ")}\u2026`);
11441
11447
  syncManager.syncAll().catch((e) => console.error(`[android] post-reconcile sync: ${e.message}`));
11442
11448
  }
11443
11449
  await registerDeviceInGDrive(tp, folderId, accounts.map((a) => a.id));
@@ -11477,6 +11483,7 @@ async function initAndroidOnce() {
11477
11483
  }
11478
11484
  syncManager.syncAll().catch((e) => console.error(`[android] Periodic sync error: ${e.message}`));
11479
11485
  service.maybeRefreshContactsConfig().catch((e) => console.error(`[android] contacts.jsonc freshness check: ${e?.message || e}`));
11486
+ primeSharedConfigs(false).catch((e) => console.error(`[android] shared config freshness check: ${e?.message || e}`));
11480
11487
  if (++contactsSyncTickCounter % 8 === 0) {
11481
11488
  for (const account of db.getAccounts()) {
11482
11489
  if (!account.email || !isGoogleAccount(account)) continue;