@bobfrankston/iflow-direct 0.1.49 → 0.1.50

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/imap-compat.d.ts CHANGED
@@ -160,6 +160,7 @@ export declare class CompatImapClient {
160
160
  watchMailbox(mailbox: string, onNew: (count: number) => void, opts?: {
161
161
  notifySpec?: string;
162
162
  onMailboxStatus?: (mailbox: string, data: proto.StatusData) => void;
163
+ onExpunge?: () => void;
163
164
  }): Promise<() => Promise<void>>;
164
165
  /** Copy a message to another server (cross-account) */
165
166
  moveMessageToServer(msg: any, fromMailbox: string, targetClient: CompatImapClient, toMailbox: string): Promise<void>;
package/imap-compat.js CHANGED
@@ -349,7 +349,7 @@ export class CompatImapClient {
349
349
  this.native.onMailboxStatus = opts.onMailboxStatus;
350
350
  if (opts?.notifySpec)
351
351
  await this.native.notify(opts.notifySpec);
352
- return this.native.startIdle(onNew);
352
+ return this.native.startIdle(onNew, opts?.onExpunge);
353
353
  }
354
354
  /** Copy a message to another server (cross-account) */
355
355
  async moveMessageToServer(msg, fromMailbox, targetClient, toMailbox) {
package/imap-native.d.ts CHANGED
@@ -113,6 +113,12 @@ export declare class NativeImapClient {
113
113
  private _connected;
114
114
  private idleTag;
115
115
  private idleCallback;
116
+ /** Fires when the SELECTED mailbox loses messages while parked in IDLE —
117
+ * an EXISTS decrease, or an unsolicited `* EXPUNGE` / `* VANISHED`
118
+ * (RFC 5465 NOTIFY SELECTED MessageExpunge). Lets the caller reconcile
119
+ * a server-side deletion in real time instead of waiting for the next
120
+ * periodic poll. Distinct from `idleCallback` (new mail only). */
121
+ private idleExpungeCallback;
116
122
  private idleRefreshTimer;
117
123
  /** RFC 5465 NOTIFY: fires on unsolicited STATUS responses for non-selected
118
124
  * mailboxes (the server pushes these when the client has issued NOTIFY
@@ -257,7 +263,7 @@ export declare class NativeImapClient {
257
263
  * SELECT and BEFORE startIdle — the server holds the spec for the
258
264
  * lifetime of the connection. */
259
265
  notify(spec: string): Promise<void>;
260
- startIdle(onNewMail: (count: number) => void): Promise<() => Promise<void>>;
266
+ startIdle(onNewMail: (count: number) => void, onExpunge?: () => void): Promise<() => Promise<void>>;
261
267
  /**
262
268
  * If IDLE is currently active, send DONE and wait for its tagged OK so the
263
269
  * connection is free to accept a new command. Saves the active callback so
package/imap-native.js CHANGED
@@ -39,6 +39,12 @@ export class NativeImapClient {
39
39
  _connected = false;
40
40
  idleTag = null;
41
41
  idleCallback = null;
42
+ /** Fires when the SELECTED mailbox loses messages while parked in IDLE —
43
+ * an EXISTS decrease, or an unsolicited `* EXPUNGE` / `* VANISHED`
44
+ * (RFC 5465 NOTIFY SELECTED MessageExpunge). Lets the caller reconcile
45
+ * a server-side deletion in real time instead of waiting for the next
46
+ * periodic poll. Distinct from `idleCallback` (new mail only). */
47
+ idleExpungeCallback = null;
42
48
  idleRefreshTimer = null;
43
49
  /** RFC 5465 NOTIFY: fires on unsolicited STATUS responses for non-selected
44
50
  * mailboxes (the server pushes these when the client has issued NOTIFY
@@ -731,8 +737,9 @@ export class NativeImapClient {
731
737
  }
732
738
  }
733
739
  // ── IDLE ──
734
- async startIdle(onNewMail) {
740
+ async startIdle(onNewMail, onExpunge) {
735
741
  this.idleCallback = onNewMail;
742
+ this.idleExpungeCallback = onExpunge ?? null;
736
743
  this.idleStopped = false;
737
744
  const beginIdleCycle = async () => {
738
745
  const tag = proto.nextTag();
@@ -764,6 +771,7 @@ export class NativeImapClient {
764
771
  const tag = this.idleTag;
765
772
  this.idleTag = null;
766
773
  this.idleCallback = null;
774
+ this.idleExpungeCallback = null;
767
775
  if (tag) {
768
776
  // Arm tagged-wait BEFORE DONE — avoid missing the OK.
769
777
  const waitTag = this.waitForTagged(tag);
@@ -1291,6 +1299,24 @@ export class NativeImapClient {
1291
1299
  this.mailboxInfo.exists = count;
1292
1300
  this.idleCallback(newCount);
1293
1301
  }
1302
+ else if (count < this.mailboxInfo.exists) {
1303
+ // Count dropped — a message was expunged on the selected
1304
+ // mailbox (another client / Thunderbird deleted it).
1305
+ // Signal the caller to reconcile the deletion now.
1306
+ this.mailboxInfo.exists = count;
1307
+ if (this.idleExpungeCallback)
1308
+ this.idleExpungeCallback();
1309
+ }
1310
+ continue;
1311
+ }
1312
+ // During IDLE, an unsolicited `* n EXPUNGE` / `* VANISHED` for the
1313
+ // selected mailbox (RFC 5465 NOTIFY SELECTED MessageExpunge, or a
1314
+ // plain post-EXPUNGE notification). Push a deletion reconcile.
1315
+ if (this.idleTag && resp.tag === "*" && (resp.type === "EXPUNGE" || resp.type === "VANISHED")) {
1316
+ if (this.mailboxInfo.exists > 0)
1317
+ this.mailboxInfo.exists--;
1318
+ if (this.idleExpungeCallback)
1319
+ this.idleExpungeCallback();
1294
1320
  continue;
1295
1321
  }
1296
1322
  // RFC 5465 NOTIFY: unsolicited STATUS responses for non-selected
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/iflow-direct",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "description": "Direct IMAP client — transport-agnostic, no Node.js dependencies, browser-ready",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",