@bobfrankston/iflow 1.0.39 → 1.0.41

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.
@@ -57,6 +57,7 @@ export declare class NativeImapClient {
57
57
  private verbose;
58
58
  private selectedMailbox;
59
59
  private mailboxInfo;
60
+ private greetingResolve;
60
61
  constructor(config: ImapClientConfig, transportFactory: TransportFactory);
61
62
  get connected(): boolean;
62
63
  connect(): Promise<void>;
@@ -112,6 +113,8 @@ export declare class NativeImapClient {
112
113
  appendMessage(mailbox: string, message: string | Uint8Array, flags?: string[]): Promise<number | null>;
113
114
  startIdle(onNewMail: (count: number) => void): Promise<() => Promise<void>>;
114
115
  getMessageCount(mailbox: string): Promise<number>;
116
+ /** Default timeout for IMAP commands (30s). Prevents hanging connections. */
117
+ private commandTimeout;
115
118
  private sendCommand;
116
119
  private waitForContinuation;
117
120
  private waitForTagged;
@@ -20,6 +20,7 @@ export class NativeImapClient {
20
20
  verbose;
21
21
  selectedMailbox = null;
22
22
  mailboxInfo = { exists: 0, recent: 0, uidNext: 0, uidValidity: 0, flags: [], permanentFlags: [] };
23
+ greetingResolve = null;
23
24
  constructor(config, transportFactory) {
24
25
  this.config = config;
25
26
  this.transportFactory = transportFactory;
@@ -31,10 +32,24 @@ export class NativeImapClient {
31
32
  async connect() {
32
33
  const useTls = this.config.port === 993;
33
34
  this.transport.onData((data) => this.handleData(data));
34
- this.transport.onClose(() => { this._connected = false; });
35
+ this.transport.onClose(() => {
36
+ this._connected = false;
37
+ // Reject any pending command so it doesn't hang forever
38
+ if (this.pendingCommand) {
39
+ const { reject } = this.pendingCommand;
40
+ this.pendingCommand = null;
41
+ reject(new Error("Connection closed"));
42
+ }
43
+ });
35
44
  this.transport.onError((err) => {
36
45
  if (this.verbose)
37
46
  console.error(` [imap] Transport error: ${err.message}`);
47
+ // Reject any pending command on transport error
48
+ if (this.pendingCommand) {
49
+ const { reject } = this.pendingCommand;
50
+ this.pendingCommand = null;
51
+ reject(err);
52
+ }
38
53
  });
39
54
  await this.transport.connect(this.config.server, this.config.port, useTls, this.config.server);
40
55
  // Read server greeting
@@ -57,19 +72,15 @@ export class NativeImapClient {
57
72
  await this.authenticate();
58
73
  }
59
74
  async readGreeting() {
60
- return new Promise((resolve) => {
61
- const check = () => {
62
- const lineEnd = this.buffer.indexOf("\r\n");
63
- if (lineEnd >= 0) {
64
- const line = this.buffer.substring(0, lineEnd + 2);
65
- this.buffer = this.buffer.substring(lineEnd + 2);
66
- resolve(proto.parseResponseLine(line));
67
- }
68
- else {
69
- setTimeout(check, 10);
70
- }
75
+ return new Promise((resolve, reject) => {
76
+ const timeout = setTimeout(() => {
77
+ this.greetingResolve = null;
78
+ reject(new Error("Greeting timeout (10s)"));
79
+ }, 10000);
80
+ this.greetingResolve = (resp) => {
81
+ clearTimeout(timeout);
82
+ resolve(resp);
71
83
  };
72
- check();
73
84
  });
74
85
  }
75
86
  async authenticate() {
@@ -133,10 +144,15 @@ export class NativeImapClient {
133
144
  try {
134
145
  if (this._connected) {
135
146
  const tag = proto.nextTag();
136
- await this.sendCommand(tag, proto.logoutCommand(tag));
147
+ // Timeout the LOGOUT command — don't let it hang forever
148
+ await Promise.race([
149
+ this.sendCommand(tag, proto.logoutCommand(tag)),
150
+ new Promise((_, reject) => setTimeout(() => reject(new Error("LOGOUT timeout")), 5000))
151
+ ]);
137
152
  }
138
153
  }
139
- catch { /* ignore */ }
154
+ catch { /* ignore — always close transport below */ }
155
+ this.pendingCommand = null; // clear any hanging promise
140
156
  this.transport.close();
141
157
  this._connected = false;
142
158
  }
@@ -399,13 +415,23 @@ export class NativeImapClient {
399
415
  return status.messages || 0;
400
416
  }
401
417
  // ── Low-level command handling ──
418
+ /** Default timeout for IMAP commands (30s). Prevents hanging connections. */
419
+ commandTimeout = 30000;
402
420
  sendCommand(tag, command) {
403
421
  return new Promise((resolve, reject) => {
404
422
  if (this.verbose && !command.includes("LOGIN") && !command.includes("AUTHENTICATE")) {
405
423
  console.log(` [imap] > ${command.trimEnd()}`);
406
424
  }
407
- this.pendingCommand = { tag, resolve, reject, responses: [] };
408
- this.transport.write(command).catch(reject);
425
+ const timer = setTimeout(() => {
426
+ this.pendingCommand = null;
427
+ reject(new Error(`IMAP command timeout (${this.commandTimeout / 1000}s): ${command.split("\r")[0].substring(0, 50)}`));
428
+ }, this.commandTimeout);
429
+ this.pendingCommand = {
430
+ tag, responses: [],
431
+ resolve: (responses) => { clearTimeout(timer); resolve(responses); },
432
+ reject: (err) => { clearTimeout(timer); reject(err); },
433
+ };
434
+ this.transport.write(command).catch((err) => { clearTimeout(timer); reject(err); });
409
435
  });
410
436
  }
411
437
  waitForContinuation(tag) {
@@ -482,6 +508,13 @@ export class NativeImapClient {
482
508
  if (this.verbose && resp.raw.length < 200) {
483
509
  console.log(` [imap] < ${resp.raw}`);
484
510
  }
511
+ // Server greeting — resolve readGreeting() promise
512
+ if (this.greetingResolve && resp.tag === "*" && (resp.type === "OK" || resp.type === "PREAUTH")) {
513
+ const resolve = this.greetingResolve;
514
+ this.greetingResolve = null;
515
+ resolve(resp);
516
+ continue;
517
+ }
485
518
  // During IDLE, handle EXISTS notifications
486
519
  if (this.idleTag && resp.tag === "*" && resp.type === "EXISTS") {
487
520
  const count = parseInt(resp.text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/iflow",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "description": "IMAP client wrapper library",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",