@bobfrankston/iflow 1.0.43 → 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,15 +141,9 @@ export class NativeImapClient {
141
141
  this.capabilities.add(c.toUpperCase());
142
142
  }
143
143
  async logout() {
144
- // Send LOGOUT but don't wait just close the transport immediately.
145
- // The server will clean up the session when the socket closes.
146
- try {
147
- if (this._connected) {
148
- const tag = proto.nextTag();
149
- this.transport.write(proto.logoutCommand(tag)).catch(() => { });
150
- }
151
- }
152
- catch { /* ignore */ }
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.
153
147
  this.pendingCommand = null;
154
148
  this.transport.close();
155
149
  this._connected = false;
@@ -471,15 +465,23 @@ export class NativeImapClient {
471
465
  }
472
466
  processBuffer() {
473
467
  while (true) {
474
- // Check for literal {size}\r\n
468
+ // Check for literal {size}\r\n — reading exact byte count of literal data
475
469
  if (this.pendingCommand?.literalBytes != null) {
476
470
  if (this.buffer.length >= this.pendingCommand.literalBytes) {
477
471
  const literal = this.buffer.substring(0, this.pendingCommand.literalBytes);
478
472
  this.buffer = this.buffer.substring(this.pendingCommand.literalBytes);
479
473
  this.pendingCommand.literalBuffer = (this.pendingCommand.literalBuffer || "") + literal;
480
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
481
480
  continue;
482
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
+ }
483
485
  break; // Wait for more data
484
486
  }
485
487
  const lineEnd = this.buffer.indexOf("\r\n");
@@ -487,24 +489,34 @@ export class NativeImapClient {
487
489
  break;
488
490
  const line = this.buffer.substring(0, lineEnd + 2);
489
491
  this.buffer = this.buffer.substring(lineEnd + 2);
490
- // Check for literal announcement {size}
492
+ // Check for literal announcement {size}\r\n at end of line
491
493
  const literalMatch = line.match(/\{(\d+)\}\r\n$/);
492
494
  if (literalMatch && this.pendingCommand) {
493
- this.pendingCommand.literalBytes = parseInt(literalMatch[1]);
494
- // Store the line prefix before the literal
495
- const linePrefix = line.substring(0, line.indexOf(`{${literalMatch[1]}}`));
496
- 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`);
497
508
  continue;
498
509
  }
499
- // 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)
500
511
  let fullLine = line;
501
512
  if (this.pendingCommand?.literalBuffer) {
502
513
  fullLine = this.pendingCommand.literalBuffer + line;
503
514
  this.pendingCommand.literalBuffer = undefined;
504
515
  }
505
516
  const resp = proto.parseResponseLine(fullLine);
506
- if (this.verbose && resp.raw.length < 200) {
507
- 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}`);
508
520
  }
509
521
  // Server greeting — resolve readGreeting() promise
510
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.43",
3
+ "version": "1.0.44",
4
4
  "description": "IMAP client wrapper library",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",