imapflow 1.0.153 → 1.0.155

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.155](https://github.com/postalsys/imapflow/compare/v1.0.154...v1.0.155) (2024-03-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **exchange:** handle special error response from exchange ([3ed4198](https://github.com/postalsys/imapflow/commit/3ed4198c1b5504ab0b46926fec179c682988b2e3))
9
+
10
+ ## [1.0.154](https://github.com/postalsys/imapflow/compare/v1.0.153...v1.0.154) (2024-03-05)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **FETCH:** throw on failing FETCH ([12f9a45](https://github.com/postalsys/imapflow/commit/12f9a45530c6f3a2f35ba2968819011eea9c32b2))
16
+
3
17
  ## [1.0.153](https://github.com/postalsys/imapflow/compare/v1.0.152...v1.0.153) (2024-02-29)
4
18
 
5
19
 
@@ -203,7 +203,7 @@ module.exports = async (connection, range, query, options) => {
203
203
  }
204
204
 
205
205
  connection.log.warn({ err, cid: connection.id });
206
- return false;
206
+ throw err;
207
207
  }
208
208
  }
209
209
  };
@@ -24,29 +24,37 @@ module.exports = async (command, options) => {
24
24
  const parser = new ParserInstance(command, options);
25
25
  const response = {};
26
26
 
27
- response.tag = await parser.getTag();
28
- await parser.getSpace();
29
- response.command = await parser.getCommand();
27
+ try {
28
+ response.tag = await parser.getTag();
30
29
 
31
- if (nullBytesRemoved) {
32
- response.nullBytesRemoved = nullBytesRemoved;
33
- }
34
-
35
- if (['UID', 'AUTHENTICATE'].indexOf((response.command || '').toUpperCase()) >= 0) {
36
30
  await parser.getSpace();
37
- response.command += ' ' + (await parser.getElement(imapFormalSyntax.command()));
38
- }
31
+ response.command = await parser.getCommand();
39
32
 
40
- if (parser.remainder.trim().length) {
41
- await parser.getSpace();
42
- response.attributes = await parser.getAttributes();
43
- }
33
+ if (nullBytesRemoved) {
34
+ response.nullBytesRemoved = nullBytesRemoved;
35
+ }
44
36
 
45
- if (parser.humanReadable) {
46
- response.attributes = (response.attributes || []).concat({
47
- type: 'TEXT',
48
- value: parser.humanReadable
49
- });
37
+ if (['UID', 'AUTHENTICATE'].indexOf((response.command || '').toUpperCase()) >= 0) {
38
+ await parser.getSpace();
39
+ response.command += ' ' + (await parser.getElement(imapFormalSyntax.command()));
40
+ }
41
+
42
+ if (parser.remainder.trim().length) {
43
+ await parser.getSpace();
44
+ response.attributes = await parser.getAttributes();
45
+ }
46
+
47
+ if (parser.humanReadable) {
48
+ response.attributes = (response.attributes || []).concat({
49
+ type: 'TEXT',
50
+ value: parser.humanReadable
51
+ });
52
+ }
53
+ } catch (err) {
54
+ if (err.code === 'ParserErrorExchange' && err.parserContext && err.parserContext.value) {
55
+ return err.parserContext.value;
56
+ }
57
+ throw err;
50
58
  }
51
59
 
52
60
  return response;
@@ -73,16 +73,42 @@ class ParserInstance {
73
73
  let match, element, errPos;
74
74
 
75
75
  if (this.remainder.match(/^\s/)) {
76
- throw new Error(`Unexpected whitespace at position ${this.pos} [E1]`);
76
+ let error = new Error(`Unexpected whitespace at position ${this.pos} [E1]`);
77
+ error.code = 'ParserError1';
78
+ error.parserContext = { input: this.input, pos: this.pos };
79
+ throw error;
77
80
  }
78
81
 
79
82
  if ((match = this.remainder.match(/^\s*[^\s]+(?=\s|$)/))) {
80
83
  element = match[0];
81
84
  if ((errPos = imapFormalSyntax.verify(element, syntax)) >= 0) {
82
- throw new Error(`Unexpected char at position ${this.pos + errPos} [E2: ${JSON.stringify(element.charAt(errPos))}]`);
85
+ if (this.tag === 'Server' && element === 'Unavailable.') {
86
+ // Exchange error
87
+ let error = new Error(`Server returned an error: ${this.input}`);
88
+ error.code = 'ParserErrorExchange';
89
+ error.parserContext = {
90
+ input: this.input,
91
+ element,
92
+ pos: this.pos,
93
+ value: {
94
+ tag: '*',
95
+ command: 'BAD',
96
+ attributes: [{ type: 'TEXT', value: this.input }]
97
+ }
98
+ };
99
+ throw error;
100
+ }
101
+
102
+ let error = new Error(`Unexpected char at position ${this.pos + errPos} [E2: ${JSON.stringify(element.charAt(errPos))}]`);
103
+ error.code = 'ParserError2';
104
+ error.parserContext = { input: this.input, element, pos: this.pos };
105
+ throw error;
83
106
  }
84
107
  } else {
85
- throw new Error(`Unexpected end of input at position ${this.pos} [E3]`);
108
+ let error = new Error(`Unexpected end of input at position ${this.pos} [E3]`);
109
+ error.code = 'ParserError3';
110
+ error.parserContext = { input: this.input, pos: this.pos };
111
+ throw error;
86
112
  }
87
113
 
88
114
  this.pos += match[0].length;
@@ -93,11 +119,17 @@ class ParserInstance {
93
119
 
94
120
  async getSpace() {
95
121
  if (!this.remainder.length) {
96
- throw new Error(`Unexpected end of input at position ${this.pos} [E4]`);
122
+ let error = new Error(`Unexpected end of input at position ${this.pos} [E4]`);
123
+ error.code = 'ParserError4';
124
+ error.parserContext = { input: this.input, pos: this.pos };
125
+ throw error;
97
126
  }
98
127
 
99
128
  if (imapFormalSyntax.verify(this.remainder.charAt(0), imapFormalSyntax.SP()) >= 0) {
100
- throw new Error(`Unexpected char at position ${this.pos} [E5: ${JSON.stringify(this.remainder.charAt(0))}]`);
129
+ let error = new Error(`Unexpected char at position ${this.pos} [E5: ${JSON.stringify(this.remainder.charAt(0))}]`);
130
+ error.code = 'ParserError5';
131
+ error.parserContext = { input: this.input, element: this.remainder, pos: this.pos };
132
+ throw error;
101
133
  }
102
134
 
103
135
  this.pos++;
@@ -106,11 +138,17 @@ class ParserInstance {
106
138
 
107
139
  async getAttributes() {
108
140
  if (!this.remainder.length) {
109
- throw new Error(`Unexpected end of input at position ${this.pos} [E6]`);
141
+ let error = new Error(`Unexpected end of input at position ${this.pos} [E6]`);
142
+ error.code = 'ParserError6';
143
+ error.parserContext = { input: this.input, pos: this.pos };
144
+ throw error;
110
145
  }
111
146
 
112
147
  if (this.remainder.match(/^\s/)) {
113
- throw new Error(`Unexpected whitespace at position ${this.pos} [E7]`);
148
+ let error = new Error(`Unexpected whitespace at position ${this.pos} [E7]`);
149
+ error.code = 'ParserError7';
150
+ error.parserContext = { input: this.input, element: this.remainder, pos: this.pos };
151
+ throw error;
114
152
  }
115
153
 
116
154
  const tokenParser = new TokenParser(this, this.pos, this.remainder, this.options);
@@ -36,7 +36,10 @@ class TokenParser {
36
36
 
37
37
  // If the node was never closed, throw it
38
38
  if (!node.isClosed) {
39
- throw new Error(`Unexpected end of input at position ${this.pos + this.str.length - 1} [E6]`);
39
+ let error = new Error(`Unexpected end of input at position ${this.pos + this.str.length - 1} [E9]`);
40
+ error.code = 'ParserError9';
41
+ error.parserContext = { input: this.str, pos: this.pos + this.str.length - 1 };
42
+ throw error;
40
43
  }
41
44
 
42
45
  let type = (node.type || '').toString().toUpperCase();
@@ -149,7 +152,10 @@ class TokenParser {
149
152
  // ) closes a list
150
153
  case ')':
151
154
  if (this.currentNode.type !== 'LIST') {
152
- throw new Error(`Unexpected list terminator ) at position ${this.pos + i} [E7]`);
155
+ let error = new Error(`Unexpected list terminator ) at position ${this.pos + i} [E10]`);
156
+ error.code = 'ParserError10';
157
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
158
+ throw error;
153
159
  }
154
160
 
155
161
  this.currentNode.isClosed = true;
@@ -162,7 +168,10 @@ class TokenParser {
162
168
  // ] closes section group
163
169
  case ']':
164
170
  if (this.currentNode.type !== 'SECTION') {
165
- throw new Error(`Unexpected section terminator ] at position ${this.pos + i} [E8]`);
171
+ let error = new Error(`Unexpected section terminator ] at position ${this.pos + i} [E11]`);
172
+ error.code = 'ParserError11';
173
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
174
+ throw error;
166
175
  }
167
176
  this.currentNode.isClosed = true;
168
177
  this.currentNode.endPos = this.pos + i;
@@ -199,7 +208,10 @@ class TokenParser {
199
208
  break;
200
209
  }
201
210
 
202
- throw new Error(`Unexpected literal8 marker at position ${this.pos + i} [E9]`);
211
+ let error = new Error(`Unexpected literal8 marker at position ${this.pos + i} [E12]`);
212
+ error.code = 'ParserError12';
213
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
214
+ throw error;
203
215
  }
204
216
  this.expectedLiteralType = 'literal8';
205
217
  break;
@@ -282,7 +294,10 @@ class TokenParser {
282
294
  // Allow % to support LIST '' %
283
295
  // Allow 8bit characters (presumably unicode)
284
296
  if (imapFormalSyntax['ATOM-CHAR']().indexOf(chr) < 0 && chr !== '\\' && chr !== '%' && chr.charCodeAt(0) < 0x80) {
285
- throw new Error(`Unexpected char at position ${this.pos + i} [E10: ${JSON.stringify(chr)}]`);
297
+ let error = new Error(`Unexpected char at position ${this.pos + i} [E13: ${JSON.stringify(chr)}]`);
298
+ error.code = 'ParserError13';
299
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
300
+ throw error;
286
301
  }
287
302
 
288
303
  this.currentNode = this.createNode(this.currentNode, this.pos + i);
@@ -329,7 +344,10 @@ class TokenParser {
329
344
  if (chr === '[') {
330
345
  // allowed only for selected elements
331
346
  if (['BODY', 'BODY.PEEK', 'BINARY', 'BINARY.PEEK'].indexOf(this.currentNode.value.toUpperCase()) < 0) {
332
- throw new Error(`Unexpected section start char [ at position ${this.pos} [E11]`);
347
+ let error = new Error(`Unexpected section start char [ at position ${this.pos + i} [E14]`);
348
+ error.code = 'ParserError14';
349
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
350
+ throw error;
333
351
  }
334
352
  this.currentNode.endPos = this.pos + i;
335
353
  this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
@@ -340,7 +358,10 @@ class TokenParser {
340
358
  }
341
359
 
342
360
  if (chr === '<') {
343
- throw new Error(`Unexpected start of partial at position ${this.pos} [E12]`);
361
+ let error = new Error(`Unexpected start of partial at position ${this.pos + i} [E15]`);
362
+ error.code = 'ParserError15';
363
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
364
+ throw error;
344
365
  }
345
366
 
346
367
  // if the char is not ATOM compatible, throw. Allow \* as an exception
@@ -351,9 +372,15 @@ class TokenParser {
351
372
  !(chr === '*' && this.currentNode.value === '\\') &&
352
373
  (!this.parent || !this.parent.command || !['NO', 'BAD', 'OK'].includes(this.parent.command))
353
374
  ) {
354
- throw new Error(`Unexpected char at position ${this.pos + i} [E13: ${JSON.stringify(chr)}]`);
375
+ let error = new Error(`Unexpected char at position ${this.pos + i} [E16: ${JSON.stringify(chr)}]`);
376
+ error.code = 'ParserError16';
377
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
378
+ throw error;
355
379
  } else if (this.currentNode.value === '\\*') {
356
- throw new Error(`Unexpected char at position ${this.pos + i} [E14: ${JSON.stringify(chr)}]`);
380
+ let error = new Error(`Unexpected char at position ${this.pos + i} [E17: ${JSON.stringify(chr)}]`);
381
+ error.code = 'ParserError17';
382
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
383
+ throw error;
357
384
  }
358
385
 
359
386
  this.currentNode.value += chr;
@@ -375,7 +402,10 @@ class TokenParser {
375
402
  if (chr === '\\') {
376
403
  i++;
377
404
  if (i >= len) {
378
- throw new Error(`Unexpected end of input at position ${this.pos + i} [E15]`);
405
+ let error = new Error(`Unexpected end of input at position ${this.pos + i} [E18]`);
406
+ error.code = 'ParserError18';
407
+ error.parserContext = { input: this.str, pos: this.pos + i };
408
+ throw error;
379
409
  }
380
410
  chr = this.str.charAt(i);
381
411
  }
@@ -386,7 +416,10 @@ class TokenParser {
386
416
  case 'PARTIAL':
387
417
  if (chr === '>') {
388
418
  if (this.currentNode.value.substr(-1) === '.') {
389
- throw new Error(`Unexpected end of partial at position ${this.pos} [E16]`);
419
+ let error = new Error(`Unexpected end of partial at position ${this.pos + 1} [E19]`);
420
+ error.code = 'ParserError19';
421
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
422
+ throw error;
390
423
  }
391
424
  this.currentNode.endPos = this.pos + i;
392
425
  this.currentNode.isClosed = true;
@@ -397,15 +430,24 @@ class TokenParser {
397
430
  }
398
431
 
399
432
  if (chr === '.' && (!this.currentNode.value.length || this.currentNode.value.match(/\./))) {
400
- throw new Error(`Unexpected partial separator . at position ${this.pos} [E17]`);
433
+ let error = new Error(`Unexpected partial separator . at position ${this.pos + 1} [E20]`);
434
+ error.code = 'ParserError20';
435
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
436
+ throw error;
401
437
  }
402
438
 
403
439
  if (imapFormalSyntax.DIGIT().indexOf(chr) < 0 && chr !== '.') {
404
- throw new Error(`Unexpected char at position ${this.pos + i} [E18: ${JSON.stringify(chr)}]`);
440
+ let error = new Error(`Unexpected char at position ${this.pos + i} [E21: ${JSON.stringify(chr)}]`);
441
+ error.code = 'ParserError21';
442
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
443
+ throw error;
405
444
  }
406
445
 
407
446
  if (this.currentNode.value.match(/^0$|\.0$/) && chr !== '.') {
408
- throw new Error(`Invalid partial at position ${this.pos + i} [E19: ${JSON.stringify(chr)}]`);
447
+ let error = new Error(`Invalid partial at position ${this.pos + i} [E22: ${JSON.stringify(chr)}]`);
448
+ error.code = 'ParserError22';
449
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
450
+ throw error;
409
451
  }
410
452
 
411
453
  this.currentNode.value += chr;
@@ -444,14 +486,20 @@ class TokenParser {
444
486
 
445
487
  if (chr === '}') {
446
488
  if (!('literalLength' in this.currentNode)) {
447
- throw new Error(`Unexpected literal prefix end char } at position ${this.pos + i} [E20]`);
489
+ let error = new Error(`Unexpected literal prefix end char } at position ${this.pos + i} [E23]`);
490
+ error.code = 'ParserError23';
491
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
492
+ throw error;
448
493
  }
449
494
  if (this.str.charAt(i + 1) === '\n') {
450
495
  i++;
451
496
  } else if (this.str.charAt(i + 1) === '\r' && this.str.charAt(i + 2) === '\n') {
452
497
  i += 2;
453
498
  } else {
454
- throw new Error(`Unexpected char at position ${this.pos + i} [E21: ${JSON.stringify(chr)}]`);
499
+ let error = new Error(`Unexpected char at position ${this.pos + i} [E24: ${JSON.stringify(chr)}]`);
500
+ error.code = 'ParserError24';
501
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
502
+ throw error;
455
503
  }
456
504
 
457
505
  this.currentNode.literalLength = Number(this.currentNode.literalLength);
@@ -492,10 +540,16 @@ class TokenParser {
492
540
  break;
493
541
  }
494
542
  if (imapFormalSyntax.DIGIT().indexOf(chr) < 0) {
495
- throw new Error(`Unexpected char at position ${this.pos + i} [E22: ${JSON.stringify(chr)}]`);
543
+ let error = new Error(`Unexpected char at position ${this.pos + i} [E25: ${JSON.stringify(chr)}]`);
544
+ error.code = 'ParserError25';
545
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
546
+ throw error;
496
547
  }
497
548
  if (this.currentNode.literalLength === '0') {
498
- throw new Error(`Invalid literal at position ${this.pos + i} [E23]`);
549
+ let error = new Error(`Invalid literal at position ${this.pos + i} [E26]`);
550
+ error.code = 'ParserError26';
551
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
552
+ throw error;
499
553
  }
500
554
  this.currentNode.literalLength = (this.currentNode.literalLength || '') + chr;
501
555
  break;
@@ -504,11 +558,17 @@ class TokenParser {
504
558
  // space finishes the sequence set
505
559
  if (chr === ' ') {
506
560
  if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
507
- throw new Error(`Unexpected whitespace at position ${this.pos + i} [E24]`);
561
+ let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E27]`);
562
+ error.code = 'ParserError27';
563
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
564
+ throw error;
508
565
  }
509
566
 
510
567
  if (this.currentNode.value !== '*' && this.currentNode.value.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
511
- throw new Error(`Unexpected whitespace at position ${this.pos + i} [E25]`);
568
+ let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E28]`);
569
+ error.code = 'ParserError28';
570
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
571
+ throw error;
512
572
  }
513
573
 
514
574
  this.currentNode.isClosed = true;
@@ -531,25 +591,43 @@ class TokenParser {
531
591
 
532
592
  if (chr === ':') {
533
593
  if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
534
- throw new Error(`Unexpected range separator : at position ${this.pos + i} [E26]`);
594
+ let error = new Error(`Unexpected range separator : at position ${this.pos + i} [E29]`);
595
+ error.code = 'ParserError29';
596
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
597
+ throw error;
535
598
  }
536
599
  } else if (chr === '*') {
537
600
  if ([',', ':'].indexOf(this.currentNode.value.substr(-1)) < 0) {
538
- throw new Error(`Unexpected range wildcard at position ${this.pos + i} [E27]`);
601
+ let error = new Error(`Unexpected range wildcard at position ${this.pos + i} [E30]`);
602
+ error.code = 'ParserError30';
603
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
604
+ throw error;
539
605
  }
540
606
  } else if (chr === ',') {
541
607
  if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
542
- throw new Error(`Unexpected sequence separator , at position ${this.pos + i} [E28]`);
608
+ let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E31]`);
609
+ error.code = 'ParserError31';
610
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
611
+ throw error;
543
612
  }
544
613
  if (this.currentNode.value.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
545
- throw new Error(`Unexpected sequence separator , at position ${this.pos + i} [E29]`);
614
+ let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E32]`);
615
+ error.code = 'ParserError32';
616
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
617
+ throw error;
546
618
  }
547
619
  } else if (!chr.match(/\d/)) {
548
- throw new Error(`Unexpected char at position ${this.pos + i} [E30: ${JSON.stringify(chr)}]`);
620
+ let error = new Error(`Unexpected char at position ${this.pos + i} [E33: ${JSON.stringify(chr)}]`);
621
+ error.code = 'ParserError33';
622
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
623
+ throw error;
549
624
  }
550
625
 
551
626
  if (chr.match(/\d/) && this.currentNode.value.substr(-1) === '*') {
552
- throw new Error(`Unexpected number at position ${this.pos + i} [E31: ${JSON.stringify(chr)}]`);
627
+ let error = new Error(`Unexpected number at position ${this.pos + i} [E34: ${JSON.stringify(chr)}]`);
628
+ error.code = 'ParserError34';
629
+ error.parserContext = { input: this.str, pos: this.pos + i, chr };
630
+ throw error;
553
631
  }
554
632
 
555
633
  this.currentNode.value += chr;
package/lib/imap-flow.js CHANGED
@@ -2254,93 +2254,94 @@ class ImapFlow extends EventEmitter {
2254
2254
  */
2255
2255
  async *fetch(range, query, options) {
2256
2256
  options = options || {};
2257
- try {
2258
- if (!this.mailbox) {
2259
- // no mailbox selected, nothing to do
2260
- return;
2261
- }
2262
2257
 
2263
- range = await this.resolveRange(range, options);
2264
- if (!range) {
2265
- return false;
2266
- }
2258
+ if (!this.mailbox) {
2259
+ // no mailbox selected, nothing to do
2260
+ return;
2261
+ }
2267
2262
 
2268
- let finished = false;
2269
- let push = false;
2270
- let rowQueue = [];
2271
- let getNext = () =>
2272
- new Promise((resolve, reject) => {
2273
- let check = () => {
2274
- if (rowQueue.length) {
2275
- let entry = rowQueue.shift();
2276
- if (entry.err) {
2277
- return reject(entry.err);
2278
- } else {
2279
- return resolve(entry.value);
2280
- }
2281
- }
2282
- if (finished) {
2283
- return resolve(null);
2284
- }
2263
+ range = await this.resolveRange(range, options);
2264
+ if (!range) {
2265
+ return false;
2266
+ }
2285
2267
 
2286
- // wait until data is pushed to queue and try again
2287
- push = () => {
2288
- push = false;
2289
- check();
2290
- };
2291
- };
2292
- check();
2293
- });
2268
+ let finished = false;
2269
+ let push = false;
2270
+ let rowQueue = [];
2294
2271
 
2295
- this.run('FETCH', range, query, {
2296
- uid: !!options.uid,
2297
- binary: options.binary,
2298
- changedSince: options.changedSince,
2299
- onUntaggedFetch: (untagged, next) => {
2300
- rowQueue.push({
2301
- value: {
2302
- response: untagged,
2303
- next
2272
+ let getNext = () =>
2273
+ new Promise((resolve, reject) => {
2274
+ let check = () => {
2275
+ if (rowQueue.length) {
2276
+ let entry = rowQueue.shift();
2277
+ if (entry.err) {
2278
+ return reject(entry.err);
2279
+ } else {
2280
+ return resolve(entry.value);
2304
2281
  }
2305
- });
2306
- if (typeof push === 'function') {
2307
- push();
2308
2282
  }
2309
- }
2310
- })
2311
- .then(() => {
2312
- finished = true;
2313
- if (typeof push === 'function') {
2314
- push();
2283
+
2284
+ if (finished) {
2285
+ return resolve(null);
2315
2286
  }
2316
- })
2317
- .catch(err => {
2318
- rowQueue.push({ err });
2319
- });
2320
2287
 
2321
- let res;
2322
- while ((res = await getNext())) {
2323
- if (this.isClosed || this.socket.destroyed) {
2324
- let error = new Error('Connection closed');
2325
- error.code = 'EConnectionClosed';
2326
- throw error;
2327
- }
2288
+ // wait until data is pushed to queue and try again
2289
+ push = () => {
2290
+ push = false;
2291
+ check();
2292
+ };
2293
+ };
2294
+ check();
2295
+ });
2328
2296
 
2329
- if (res !== null) {
2330
- yield res.response;
2331
- res.next();
2297
+ this.run('FETCH', range, query, {
2298
+ uid: !!options.uid,
2299
+ binary: options.binary,
2300
+ changedSince: options.changedSince,
2301
+ onUntaggedFetch: (untagged, next) => {
2302
+ rowQueue.push({
2303
+ value: {
2304
+ response: untagged,
2305
+ next
2306
+ }
2307
+ });
2308
+ if (typeof push === 'function') {
2309
+ push();
2332
2310
  }
2333
2311
  }
2312
+ })
2313
+ .then(() => {
2314
+ finished = true;
2315
+ if (typeof push === 'function') {
2316
+ push();
2317
+ }
2318
+ })
2319
+ .catch(err => {
2320
+ rowQueue.push({ err });
2321
+ if (typeof push === 'function') {
2322
+ push();
2323
+ }
2324
+ });
2334
2325
 
2335
- if (!finished) {
2336
- // FETCH never finished!
2337
- let error = new Error('FETCH did not finish');
2338
- error.code = 'ENotFinished';
2326
+ let res;
2327
+ while ((res = await getNext())) {
2328
+ if (this.isClosed || this.socket.destroyed) {
2329
+ let error = new Error('Connection closed');
2330
+ error.code = 'EConnectionClosed';
2339
2331
  throw error;
2340
2332
  }
2341
- } catch (err) {
2342
- setImmediate(() => this.close());
2343
- throw err;
2333
+
2334
+ if (res !== null) {
2335
+ yield res.response;
2336
+ res.next();
2337
+ }
2338
+ }
2339
+
2340
+ if (!finished) {
2341
+ // FETCH never finished!
2342
+ let error = new Error('FETCH did not finish');
2343
+ error.code = 'ENotFinished';
2344
+ throw error;
2344
2345
  }
2345
2346
  }
2346
2347
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.153",
3
+ "version": "1.0.155",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "./lib/imap-flow.js",
6
6
  "scripts": {
@@ -32,7 +32,7 @@
32
32
  "@babel/eslint-plugin": "7.23.5",
33
33
  "@babel/plugin-syntax-class-properties": "7.12.13",
34
34
  "@babel/preset-env": "7.24.0",
35
- "@types/node": "20.11.22",
35
+ "@types/node": "20.11.24",
36
36
  "eslint": "8.57.0",
37
37
  "eslint-config-nodemailer": "1.2.0",
38
38
  "eslint-config-prettier": "9.1.0",
@@ -978,6 +978,14 @@ module.exports['IMAP Parser, NO with a dot'] = test =>
978
978
  test.deepEqual(parsed.attributes, [{ type: 'TEXT', value: 'Server Unavailable. 15' }]);
979
979
  });
980
980
 
981
+ module.exports['IMAP Parser, no tag or response (Exchange)'] = test =>
982
+ asyncWrapper(test, async test => {
983
+ let parsed = await parser('Server Unavailable. 15');
984
+ console.log(parsed);
985
+ test.equal(parsed.command, 'BAD');
986
+ test.deepEqual(parsed.attributes, [{ type: 'TEXT', value: 'Server Unavailable. 15' }]);
987
+ });
988
+
981
989
  module.exports['IMAP Parser, BAD with throttling'] = test =>
982
990
  asyncWrapper(test, async test => {
983
991
  let parsed = await parser('X BAD Request is throttled. Suggested Backoff Time: 92415 milliseconds');