@bobfrankston/iflow-direct 0.1.23 → 0.1.24

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/imap-compat.d.ts CHANGED
@@ -36,7 +36,7 @@ export declare class CompatImapClient {
36
36
  logout(): Promise<void>;
37
37
  /** Get folder list */
38
38
  getFolderList(): Promise<NativeFolder[]>;
39
- /** Detect special folders from folder list */
39
+ /** Extract special folders from folder list */
40
40
  getSpecialFolders(folders: NativeFolder[]): SpecialFolders;
41
41
  /** Fetch messages since a UID in a mailbox */
42
42
  fetchMessagesSinceUid(mailbox: string, sinceUid: number, options?: {
package/imap-compat.js CHANGED
@@ -5,6 +5,16 @@
5
5
  */
6
6
  import { NativeImapClient } from "./imap-native.js";
7
7
  import { FetchedMessage } from "./fetched-message.js";
8
+ // We may need some special cases in the future
9
+ const defaultFolders = {
10
+ inbox: "INBOX", // Uppercase -- we'll need a reality check
11
+ sent: "Sent",
12
+ trash: "Trash",
13
+ drafts: "Drafts",
14
+ spam: "_Spam",
15
+ junk: "_Junk",
16
+ archive: "Archive"
17
+ };
8
18
  /**
9
19
  * Compatibility IMAP client — wraps NativeImapClient with the old ImapClient API.
10
20
  * Each method creates its own select/operation/close cycle.
@@ -39,55 +49,63 @@ export class CompatImapClient {
39
49
  await this.ensureConnected();
40
50
  return this.native.listFolders();
41
51
  }
42
- /** Detect special folders from folder list */
52
+ /** Extract special folders from folder list */
43
53
  getSpecialFolders(folders) {
44
54
  // Two-pass detection so RFC 6154 \Drafts / \Sent / \Trash / \Junk /
45
55
  // \Archive flags always win over name-based guesses. Previous single-pass
46
56
  // `else if (flag || name)` could tag a stray folder named "DRAFT" as the
47
57
  // drafts folder on Gmail, beating out [Imap]/Drafts which has the real
48
58
  // \Drafts flag — Gmail then rejects APPEND to "DRAFT" as NONEXISTENT.
49
- const result = {};
59
+ let result = {};
50
60
  const leafName = (f) => {
51
61
  const delim = f.delimiter || ".";
52
- return (f.path.split(delim).pop() || f.path).toLowerCase();
62
+ return (f.path.split(delim).at(-1) ?? f.path).toLowerCase();
53
63
  };
54
- const flagsLower = (f) => f.flags.map(fl => fl.toLowerCase());
55
- // Pass 1: RFC 6154 flags only — authoritative if present
64
+ const flagSet = (f) => new Set(f.flags.map(fl => fl.toLowerCase()));
65
+ // const df = 'y:\\temp\\folders.csv';
66
+ // fs.writeFileSync(df, "path,flags\n" + folders.map(f => `${f.path},"${f.flags.join(";")}"`).join("\n"));
67
+ // console.log(`Wrote folder list to ${df}`);
68
+ // RFC 6154 flags only — authoritative if present
69
+ // Note that Dovecot does not have them but we check INBOX for the case
56
70
  for (const f of folders) {
57
- const flags = flagsLower(f);
58
- if (!result.inbox && (flags.includes("\\inbox") || leafName(f) === "inbox"))
59
- result.inbox = f.path;
60
- if (!result.sent && flags.includes("\\sent"))
61
- result.sent = f.path;
62
- if (!result.trash && flags.includes("\\trash"))
63
- result.trash = f.path;
64
- if (!result.drafts && flags.includes("\\drafts"))
65
- result.drafts = f.path;
66
- if (!result.archive && flags.includes("\\archive"))
67
- result.archive = f.path;
68
- if (!result.junk && (flags.includes("\\junk") || flags.includes("\\spam"))) {
71
+ const flags = flagSet(f);
72
+ // Debubing
73
+ const dbgignore = ['\\haschildren', '\\hasnochildren', '\\noselect', '\\unmarked'];
74
+ const dbgfgall = Array.from(flags);
75
+ const dbgfg = dbgfgall.filter(fl => !dbgignore.includes(fl));
76
+ if (flags.has("\\inbox") || leafName(f) === "inbox")
77
+ result.inbox ??= f.path;
78
+ if (flags.has("\\sent"))
79
+ result.sent ??= f.path;
80
+ if (flags.has("\\trash"))
81
+ result.trash ??= f.path;
82
+ if (flags.has("\\drafts"))
83
+ result.drafts ??= f.path;
84
+ if (flags.has("\\archive"))
85
+ result.archive ??= f.path;
86
+ if (flags.has("\\junk"))
69
87
  result.junk = f.path;
70
- if (!result.spam)
71
- result.spam = f.path;
72
- }
73
- }
74
- // Pass 2: exact leaf-name fallback for servers that don't advertise flags
75
- for (const f of folders) {
76
- const name = leafName(f);
77
- if (!result.sent && (name === "sent" || name === "sent items" || name === "sent mail"))
78
- result.sent = f.path;
79
- if (!result.trash && (name === "trash" || name === "deleted items" || name === "deleted"))
80
- result.trash = f.path;
81
- if (!result.drafts && (name === "drafts" || name === "draft"))
82
- result.drafts = f.path;
83
- if (!result.archive && (name === "archive" || name === "archives"))
84
- result.archive = f.path;
85
- if (!result.junk && (name === "spam" || name === "junk" || name === "junk email" || name === "junk e-mail")) {
86
- result.junk = f.path;
87
- if (!result.spam)
88
- result.spam = f.path;
89
- }
88
+ if (flags.has("\\spam"))
89
+ result.spam ??= f.path;
90
90
  }
91
+ result = { ...defaultFolders, ...result }; // Fill in any missing defaults
92
+ // // Pass 2: exact leaf-name fallback for servers that don't advertise flags
93
+ // const sentNames = new Set(["sent", "sent items", "sent mail"]);
94
+ // const trashNames = new Set(["trash", "deleted items", "deleted"]);
95
+ // const draftsNames = new Set(["drafts", "draft"]);
96
+ // const archiveNames = new Set(["archive", "archives"]);
97
+ // const junkNames = new Set(["spam", "junk", "junk email", "junk e-mail"]);
98
+ // for (const f of folders) {
99
+ // const name = leafName(f);
100
+ // if (sentNames.has(name)) result.sent ??= f.path;
101
+ // if (trashNames.has(name)) result.trash ??= f.path;
102
+ // if (draftsNames.has(name)) result.drafts ??= f.path;
103
+ // if (archiveNames.has(name)) result.archive ??= f.path;
104
+ // if (junkNames.has(name)) {
105
+ // result.junk ??= f.path;
106
+ // result.spam ??= f.path;
107
+ // }
108
+ // }
91
109
  return result;
92
110
  }
93
111
  /** Fetch messages since a UID in a mailbox */
package/imap-native.js CHANGED
@@ -386,7 +386,7 @@ export class NativeImapClient {
386
386
  const chunk = uids.slice(i, i + chunkSize);
387
387
  const msgs = await this.fetchMessages(chunk.join(","), options);
388
388
  allMessages.push(...msgs);
389
- console.log(` [fetch] ${allMessages.length}/${uids.length} (chunk of ${chunk.length})`);
389
+ // console.log(` [fetch] ${allMessages.length}/${uids.length} (chunk of ${chunk.length})`);
390
390
  if (onChunk)
391
391
  onChunk(msgs);
392
392
  if (chunkSize < this.fetchChunkSizeMax)
@@ -405,7 +405,7 @@ export class NativeImapClient {
405
405
  return [];
406
406
  // Reverse so newest messages (highest UIDs) come first
407
407
  uids.reverse();
408
- console.log(` [fetch] ${uids.length} UIDs to fetch (newest first)`);
408
+ // console.log(` [fetch] ${uids.length} UIDs to fetch (newest first)`);
409
409
  const allMessages = [];
410
410
  let chunkSize = this.fetchChunkSize;
411
411
  for (let i = 0; i < uids.length; i += chunkSize) {
@@ -788,9 +788,9 @@ export class NativeImapClient {
788
788
  const rest_bytes = encoded.subarray(neededBytes);
789
789
  let literal = new TextDecoder().decode(literal_bytes);
790
790
  this.buffer = new TextDecoder().decode(rest_bytes);
791
- if (neededBytes !== literal.length) {
792
- console.log(` [imap] literal: ${neededBytes} bytes → ${literal.length} chars (multi-byte corrected)`);
793
- }
791
+ // if (neededBytes !== literal.length) {
792
+ // console.log(`iflow-direct: [imap] literal: ${neededBytes} bytes → ${literal.length} chars (multi-byte corrected)`);
793
+ // }
794
794
  // For non-BODY literals (e.g. display names in ENVELOPE), wrap in quotes
795
795
  // so tokenizeParenList treats them as a single token
796
796
  if (!this.pendingCommand.currentLiteralKey) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/iflow-direct",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "Direct IMAP client — transport-agnostic, no Node.js dependencies, browser-ready",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",
package/tasks.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "tsc: watch",
6
+ "type": "shell",
7
+ "command": "tsc",
8
+ "args": ["--watch"],
9
+ "runOptions": {
10
+ "runOn": "folderOpen"
11
+ },
12
+ "problemMatcher": "$tsc-watch",
13
+ "isBackground": true,
14
+ "group": {
15
+ "kind": "build",
16
+ "isDefault": true
17
+ }
18
+ }
19
+ ]
20
+ }