@bobfrankston/iflow-direct 0.1.55 → 0.1.57

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/README.md CHANGED
@@ -11,19 +11,25 @@ and the desktop/browser architecture diagram.
11
11
 
12
12
  ```bash
13
13
  npm install @bobfrankston/iflow-direct
14
- # desktop TCP transport:
15
- npm install @bobfrankston/tcp-transport
14
+ # desktop (Node) TCP transport implementation:
15
+ npm install @bobfrankston/node-tcp-transport
16
16
  ```
17
17
 
18
- ## Quick start (desktop)
18
+ `@bobfrankston/tcp-transport` is the *interface* package (`TcpTransport` is a
19
+ type, plus the browser/Android `BridgeTcpTransport`); the class you instantiate
20
+ in Node is `NodeTcpTransport` from `@bobfrankston/node-tcp-transport`
21
+ (node:net/tls). `@bobfrankston/iflow-node` is a back-compat shim re-exporting
22
+ it as `NodeTransport` — new code should not use it.
23
+
24
+ ## Quick start (Node)
19
25
 
20
26
  ```typescript
21
27
  import { NativeImapClient } from "@bobfrankston/iflow-direct";
22
- import { TcpTransport } from "@bobfrankston/tcp-transport";
28
+ import { NodeTcpTransport } from "@bobfrankston/node-tcp-transport";
23
29
 
24
30
  const client = new NativeImapClient(
25
31
  { server: "imap.example.com", port: 993, username: "me", password: "..." },
26
- () => new TcpTransport(),
32
+ () => new NodeTcpTransport(),
27
33
  );
28
34
  await client.connect();
29
35
  await client.select("INBOX");
@@ -31,6 +37,23 @@ const msgs = await client.fetchMessages("1:50", { source: false });
31
37
  await client.logout();
32
38
  ```
33
39
 
40
+ For OAuth (Gmail, Office 365), omit `password` and supply a `tokenProvider` —
41
+ an async function returning a current access token; the client calls it at
42
+ authentication time, so refresh logic lives in the provider:
43
+
44
+ ```typescript
45
+ const client = new NativeImapClient(
46
+ {
47
+ server: "imap.gmail.com", port: 993, username: "me@gmail.com",
48
+ tokenProvider: async () => myOauth.getFreshAccessToken(),
49
+ },
50
+ () => new NodeTcpTransport(),
51
+ );
52
+ ```
53
+
54
+ (mailx pairs this with `@bobfrankston/oauthsupport`, but any source of a valid
55
+ token works.)
56
+
34
57
  For the legacy API shape (`getFolderList`, etc.) use `CompatImapClient` from the
35
58
  same package.
36
59
 
@@ -19,6 +19,7 @@ export declare class FetchedMessage implements NativeFetchedMessage {
19
19
  uid: number;
20
20
  flags: Set<string>;
21
21
  date: Date | null;
22
+ sentDate?: Date;
22
23
  subject: string;
23
24
  messageId: string;
24
25
  from: AddressData[];
@@ -24,6 +24,7 @@ export class FetchedMessage {
24
24
  uid;
25
25
  flags;
26
26
  date;
27
+ sentDate;
27
28
  subject;
28
29
  messageId;
29
30
  from;
package/imap-native.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface NativeFetchedMessage {
14
14
  uid: number;
15
15
  flags: Set<string>;
16
16
  date: Date | null;
17
+ sentDate?: Date;
17
18
  subject: string;
18
19
  messageId: string;
19
20
  from: proto.AddressData[];
package/imap-native.js CHANGED
@@ -706,6 +706,17 @@ export class NativeImapClient {
706
706
  return proto.parseSearchResponse(r.text);
707
707
  }
708
708
  }
709
+ // No untagged * SEARCH seen. If the tagged status is anything but OK,
710
+ // the search FAILED — throw so callers can tell "failed" from "no
711
+ // matches". Silently returning [] here let a failed HEADER search read
712
+ // as "message missing from server", and mailx's sent-sweep then
713
+ // re-APPENDed a copy every 5-minute tick — 3,704 duplicates of one
714
+ // message over ~13 days (2026-07-02). Empty-but-OK still returns []
715
+ // (some servers omit the * SEARCH line when there are zero hits).
716
+ const tagged = responses.find(r => r.tag === tag);
717
+ if (!tagged || tagged.type !== "OK") {
718
+ throw new Error(`SEARCH failed: ${tagged ? `${tagged.type} ${tagged.text || ""}` : "no tagged response"}`);
719
+ }
709
720
  return [];
710
721
  }
711
722
  /** Set flags on a message */
@@ -1513,6 +1524,11 @@ export class NativeImapClient {
1513
1524
  if (envMatch) {
1514
1525
  const env = proto.parseEnvelope(envMatch[1]);
1515
1526
  msg.date = msg.date || env.date;
1527
+ // Expose the ENVELOPE Date: header (the SENT time) separately
1528
+ // from msg.date (INTERNALDATE = server-received time). Callers
1529
+ // that want messages to cluster by when they were sent — not by
1530
+ // per-account delivery time — use sentDate. Additive/optional.
1531
+ msg.sentDate = env.date || undefined;
1516
1532
  msg.subject = env.subject;
1517
1533
  msg.messageId = env.messageId;
1518
1534
  msg.from = env.from;
package/imap-protocol.js CHANGED
@@ -302,8 +302,16 @@ export function parseEnvelope(envStr) {
302
302
  result.to = parseAddressList(tokens[5]);
303
303
  result.cc = parseAddressList(tokens[6]);
304
304
  result.bcc = parseAddressList(tokens[7]);
305
- result.inReplyTo = unquote(tokens[8]);
306
- result.messageId = unquote(tokens[9]);
305
+ // Trim fold whitespace. Servers hand back the ENVELOPE msg-id /
306
+ // in-reply-to with the original header's folding intact when the
307
+ // Message-ID sat on a continuation line ("Message-ID:\r\n\t<...>")
308
+ // — the value arrives as "\t<...>". Stored untrimmed, that tab
309
+ // poisoned every later HEADER search ("missing on server") and
310
+ // fueled mailx's sent-sweep re-APPEND loop: 3,707 duplicates of
311
+ // one Outlook-composed reply (2026-07-02). Trim here so no
312
+ // consumer ever sees the fold whitespace.
313
+ result.inReplyTo = unquote(tokens[8]).trim();
314
+ result.messageId = unquote(tokens[9]).trim();
307
315
  }
308
316
  }
309
317
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/iflow-direct",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "description": "Direct IMAP client — transport-agnostic, no Node.js dependencies, browser-ready",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",
@@ -19,7 +19,7 @@
19
19
  "author": "Bob Frankston",
20
20
  "license": "ISC",
21
21
  "dependencies": {
22
- "@bobfrankston/tcp-transport": "^0.1.7"
22
+ "@bobfrankston/tcp-transport": "^0.1.8"
23
23
  },
24
24
  "exports": {
25
25
  ".": {
@@ -50,7 +50,7 @@
50
50
  },
51
51
  ".transformedSnapshot": {
52
52
  "dependencies": {
53
- "@bobfrankston/tcp-transport": "^0.1.7"
53
+ "@bobfrankston/tcp-transport": "^0.1.8"
54
54
  }
55
55
  }
56
56
  }