@bobfrankston/iflow 1.0.42 → 1.0.44

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.
@@ -141,18 +141,10 @@ export class NativeImapClient {
141
141
  this.capabilities.add(c.toUpperCase());
142
142
  }
143
143
  async logout() {
144
- try {
145
- if (this._connected) {
146
- const tag = proto.nextTag();
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
- ]);
152
- }
153
- }
154
- catch { /* ignore — always close transport below */ }
155
- this.pendingCommand = null; // clear any hanging promise
144
+ // Force-close the transport immediately. The LOGOUT command is a courtesy —
145
+ // the server cleans up the session when the socket closes.
146
+ // Sending LOGOUT and waiting caused hangs and connection leaks.
147
+ this.pendingCommand = null;
156
148
  this.transport.close();
157
149
  this._connected = false;
158
150
  }
@@ -473,15 +465,23 @@ export class NativeImapClient {
473
465
  }
474
466
  processBuffer() {
475
467
  while (true) {
476
- // Check for literal {size}\r\n
468
+ // Check for literal {size}\r\n — reading exact byte count of literal data
477
469
  if (this.pendingCommand?.literalBytes != null) {
478
470
  if (this.buffer.length >= this.pendingCommand.literalBytes) {
479
471
  const literal = this.buffer.substring(0, this.pendingCommand.literalBytes);
480
472
  this.buffer = this.buffer.substring(this.pendingCommand.literalBytes);
481
473
  this.pendingCommand.literalBuffer = (this.pendingCommand.literalBuffer || "") + literal;
482
474
  this.pendingCommand.literalBytes = undefined;
475
+ if (this.verbose)
476
+ console.log(` [imap] literal consumed, ${literal.length} bytes, buffer remaining: ${this.buffer.length}`);
477
+ // After consuming literal, check if the NEXT part has another literal
478
+ // (e.g., BODY[HEADER] {500}\r\n<data>BODY[] {2000}\r\n<data>)\r\n)
479
+ // Continue to process the next line/literal from the buffer
483
480
  continue;
484
481
  }
482
+ if (this.verbose && this.pendingCommand.literalBytes > 0) {
483
+ console.log(` [imap] waiting for literal: need ${this.pendingCommand.literalBytes}, have ${this.buffer.length}`);
484
+ }
485
485
  break; // Wait for more data
486
486
  }
487
487
  const lineEnd = this.buffer.indexOf("\r\n");
@@ -489,24 +489,34 @@ export class NativeImapClient {
489
489
  break;
490
490
  const line = this.buffer.substring(0, lineEnd + 2);
491
491
  this.buffer = this.buffer.substring(lineEnd + 2);
492
- // Check for literal announcement {size}
492
+ // Check for literal announcement {size}\r\n at end of line
493
493
  const literalMatch = line.match(/\{(\d+)\}\r\n$/);
494
494
  if (literalMatch && this.pendingCommand) {
495
- this.pendingCommand.literalBytes = parseInt(literalMatch[1]);
496
- // Store the line prefix before the literal
497
- const linePrefix = line.substring(0, line.indexOf(`{${literalMatch[1]}}`));
498
- this.pendingCommand.literalBuffer = linePrefix;
495
+ const size = parseInt(literalMatch[1]);
496
+ // Store the line prefix before {size} — will be prepended after literal is consumed
497
+ const linePrefix = line.substring(0, literalMatch.index);
498
+ if (this.pendingCommand.literalBuffer) {
499
+ // Already have buffered data from a previous literal — append this prefix
500
+ this.pendingCommand.literalBuffer += linePrefix;
501
+ }
502
+ else {
503
+ this.pendingCommand.literalBuffer = linePrefix;
504
+ }
505
+ this.pendingCommand.literalBytes = size;
506
+ if (this.verbose)
507
+ console.log(` [imap] literal announced: ${size} bytes, prefix: ${linePrefix.length} chars`);
499
508
  continue;
500
509
  }
501
- // If we have buffered literal data, prepend it
510
+ // If we have buffered literal data, prepend it to this line (the closing part after the literal)
502
511
  let fullLine = line;
503
512
  if (this.pendingCommand?.literalBuffer) {
504
513
  fullLine = this.pendingCommand.literalBuffer + line;
505
514
  this.pendingCommand.literalBuffer = undefined;
506
515
  }
507
516
  const resp = proto.parseResponseLine(fullLine);
508
- if (this.verbose && resp.raw.length < 200) {
509
- console.log(` [imap] < ${resp.raw}`);
517
+ if (this.verbose) {
518
+ const display = resp.raw.length > 200 ? resp.raw.substring(0, 200) + `... (${resp.raw.length} bytes)` : resp.raw;
519
+ console.log(` [imap] < [tag=${resp.tag} type=${resp.type}] ${display}`);
510
520
  }
511
521
  // Server greeting — resolve readGreeting() promise
512
522
  if (this.greetingResolve && resp.tag === "*" && (resp.type === "OK" || resp.type === "PREAUTH")) {
@@ -116,8 +116,8 @@ export function parseResponseLine(line) {
116
116
  // Untagged response
117
117
  if (trimmed.startsWith("* ")) {
118
118
  const rest = trimmed.substring(2);
119
- // Check for numeric response (e.g., "* 5 EXISTS")
120
- const numMatch = rest.match(/^(\d+)\s+(\S+)(.*)$/);
119
+ // Check for numeric response (e.g., "* 5 EXISTS", "* 131441 FETCH (...)")
120
+ const numMatch = rest.match(/^(\d+)\s+(\S+)/);
121
121
  if (numMatch) {
122
122
  return { tag: "*", type: numMatch[2].toUpperCase(), text: rest, raw: trimmed };
123
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/iflow",
3
- "version": "1.0.42",
3
+ "version": "1.0.44",
4
4
  "description": "IMAP client wrapper library",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",