@rndmjoker/proton-mcp 0.4.4

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.
Files changed (141) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +405 -0
  3. package/dist/activity.d.ts +49 -0
  4. package/dist/activity.js +72 -0
  5. package/dist/activity.js.map +1 -0
  6. package/dist/bridge/certificate.d.ts +75 -0
  7. package/dist/bridge/certificate.js +176 -0
  8. package/dist/bridge/certificate.js.map +1 -0
  9. package/dist/bridge/connection.d.ts +99 -0
  10. package/dist/bridge/connection.js +244 -0
  11. package/dist/bridge/connection.js.map +1 -0
  12. package/dist/bridge/errors.d.ts +35 -0
  13. package/dist/bridge/errors.js +56 -0
  14. package/dist/bridge/errors.js.map +1 -0
  15. package/dist/config.d.ts +92 -0
  16. package/dist/config.js +142 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/credentials/availability.d.ts +62 -0
  19. package/dist/credentials/availability.js +107 -0
  20. package/dist/credentials/availability.js.map +1 -0
  21. package/dist/credentials/encrypted-file.d.ts +50 -0
  22. package/dist/credentials/encrypted-file.js +156 -0
  23. package/dist/credentials/encrypted-file.js.map +1 -0
  24. package/dist/credentials/keyring.d.ts +48 -0
  25. package/dist/credentials/keyring.js +97 -0
  26. package/dist/credentials/keyring.js.map +1 -0
  27. package/dist/credentials/plain-file.d.ts +24 -0
  28. package/dist/credentials/plain-file.js +78 -0
  29. package/dist/credentials/plain-file.js.map +1 -0
  30. package/dist/credentials/probe-keyring.d.ts +11 -0
  31. package/dist/credentials/probe-keyring.js +57 -0
  32. package/dist/credentials/probe-keyring.js.map +1 -0
  33. package/dist/credentials/session.d.ts +15 -0
  34. package/dist/credentials/session.js +21 -0
  35. package/dist/credentials/session.js.map +1 -0
  36. package/dist/credentials/store.d.ts +80 -0
  37. package/dist/credentials/store.js +94 -0
  38. package/dist/credentials/store.js.map +1 -0
  39. package/dist/in-flight.d.ts +33 -0
  40. package/dist/in-flight.js +78 -0
  41. package/dist/in-flight.js.map +1 -0
  42. package/dist/mail/actions.d.ts +116 -0
  43. package/dist/mail/actions.js +265 -0
  44. package/dist/mail/actions.js.map +1 -0
  45. package/dist/mail/attachments.d.ts +39 -0
  46. package/dist/mail/attachments.js +86 -0
  47. package/dist/mail/attachments.js.map +1 -0
  48. package/dist/mail/compose.d.ts +264 -0
  49. package/dist/mail/compose.js +395 -0
  50. package/dist/mail/compose.js.map +1 -0
  51. package/dist/mail/drafts.d.ts +138 -0
  52. package/dist/mail/drafts.js +449 -0
  53. package/dist/mail/drafts.js.map +1 -0
  54. package/dist/mail/ids.d.ts +78 -0
  55. package/dist/mail/ids.js +135 -0
  56. package/dist/mail/ids.js.map +1 -0
  57. package/dist/mail/labels.d.ts +61 -0
  58. package/dist/mail/labels.js +139 -0
  59. package/dist/mail/labels.js.map +1 -0
  60. package/dist/mail/markup.d.ts +91 -0
  61. package/dist/mail/markup.js +265 -0
  62. package/dist/mail/markup.js.map +1 -0
  63. package/dist/mail/messages.d.ts +147 -0
  64. package/dist/mail/messages.js +219 -0
  65. package/dist/mail/messages.js.map +1 -0
  66. package/dist/mail/quote.d.ts +65 -0
  67. package/dist/mail/quote.js +157 -0
  68. package/dist/mail/quote.js.map +1 -0
  69. package/dist/mail/search.d.ts +66 -0
  70. package/dist/mail/search.js +107 -0
  71. package/dist/mail/search.js.map +1 -0
  72. package/dist/mail/send.d.ts +61 -0
  73. package/dist/mail/send.js +132 -0
  74. package/dist/mail/send.js.map +1 -0
  75. package/dist/mime/parse.d.ts +110 -0
  76. package/dist/mime/parse.js +194 -0
  77. package/dist/mime/parse.js.map +1 -0
  78. package/dist/server.d.ts +13 -0
  79. package/dist/server.js +387 -0
  80. package/dist/server.js.map +1 -0
  81. package/dist/session.d.ts +86 -0
  82. package/dist/session.js +220 -0
  83. package/dist/session.js.map +1 -0
  84. package/dist/settings.d.ts +52 -0
  85. package/dist/settings.js +80 -0
  86. package/dist/settings.js.map +1 -0
  87. package/dist/tools/actions.d.ts +21 -0
  88. package/dist/tools/actions.js +133 -0
  89. package/dist/tools/actions.js.map +1 -0
  90. package/dist/tools/capabilities.d.ts +52 -0
  91. package/dist/tools/capabilities.js +96 -0
  92. package/dist/tools/capabilities.js.map +1 -0
  93. package/dist/tools/confirm.d.ts +84 -0
  94. package/dist/tools/confirm.js +212 -0
  95. package/dist/tools/confirm.js.map +1 -0
  96. package/dist/tools/drafts.d.ts +21 -0
  97. package/dist/tools/drafts.js +199 -0
  98. package/dist/tools/drafts.js.map +1 -0
  99. package/dist/tools/failures.d.ts +46 -0
  100. package/dist/tools/failures.js +157 -0
  101. package/dist/tools/failures.js.map +1 -0
  102. package/dist/tools/format.d.ts +55 -0
  103. package/dist/tools/format.js +220 -0
  104. package/dist/tools/format.js.map +1 -0
  105. package/dist/tools/guidance.d.ts +38 -0
  106. package/dist/tools/guidance.js +226 -0
  107. package/dist/tools/guidance.js.map +1 -0
  108. package/dist/tools/interface.d.ts +28 -0
  109. package/dist/tools/interface.js +63 -0
  110. package/dist/tools/interface.js.map +1 -0
  111. package/dist/tools/labels.d.ts +21 -0
  112. package/dist/tools/labels.js +92 -0
  113. package/dist/tools/labels.js.map +1 -0
  114. package/dist/tools/list-folders.d.ts +14 -0
  115. package/dist/tools/list-folders.js +70 -0
  116. package/dist/tools/list-folders.js.map +1 -0
  117. package/dist/tools/messages.d.ts +9 -0
  118. package/dist/tools/messages.js +221 -0
  119. package/dist/tools/messages.js.map +1 -0
  120. package/dist/tools/send.d.ts +29 -0
  121. package/dist/tools/send.js +236 -0
  122. package/dist/tools/send.js.map +1 -0
  123. package/dist/tools/sign-in-required.d.ts +94 -0
  124. package/dist/tools/sign-in-required.js +168 -0
  125. package/dist/tools/sign-in-required.js.map +1 -0
  126. package/dist/web/layout.d.ts +75 -0
  127. package/dist/web/layout.js +590 -0
  128. package/dist/web/layout.js.map +1 -0
  129. package/dist/web/pages.d.ts +79 -0
  130. package/dist/web/pages.js +284 -0
  131. package/dist/web/pages.js.map +1 -0
  132. package/dist/web/sections.d.ts +105 -0
  133. package/dist/web/sections.js +207 -0
  134. package/dist/web/sections.js.map +1 -0
  135. package/dist/web/security.d.ts +122 -0
  136. package/dist/web/security.js +180 -0
  137. package/dist/web/security.js.map +1 -0
  138. package/dist/web/server.d.ts +153 -0
  139. package/dist/web/server.js +622 -0
  140. package/dist/web/server.js.map +1 -0
  141. package/package.json +70 -0
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Quoting a message inside a reply, with its formatting intact.
3
+ *
4
+ * The obvious way to quote is to convert the original to text. It is safe and
5
+ * it is what this server did first, and measured against a real mailbox it
6
+ * turned out to be too expensive: 94 percent of ordinary mail is formatted, so
7
+ * every reply would throw away the look of nearly everything it answers.
8
+ *
9
+ * The other obvious way is to keep the original's markup and cut it down to
10
+ * what this server permits itself to write. Also measured, on the same
11
+ * mailbox: **83 percent of real messages would need work, with a median of 87
12
+ * removals each.** That is not a plainer quote, it is a demolished one, and the
13
+ * pieces that carry the look are the first to go.
14
+ *
15
+ * So the quote keeps the original **byte for byte**, with one exception that
16
+ * the measurement made unavoidable.
17
+ *
18
+ * ## The exception, and why it is not negotiable
19
+ *
20
+ * A `<style>` block belongs to a document, not to the element it sits in. Embed
21
+ * one inside a quote and its rules apply to the whole message, including the
22
+ * reply written above it. Measured on the same sample:
23
+ *
24
+ * | | |
25
+ * | :--- | :--- |
26
+ * | Formatted messages carrying a `<style>` block | 66 percent |
27
+ * | Of those, rules aimed at bare elements rather than classes | 56 percent |
28
+ * | The selectors, in order | `body`, `a`, `table`, `p`, `img`, `td`, `blockquote` |
29
+ *
30
+ * `blockquote` appears in thirty-six of them, which is the element this quote
31
+ * is wrapped in. So in more than half of all replies, the quoted message's
32
+ * stylesheet would reach out and restyle the sender's own words. A rule as
33
+ * short as `p { display: none }` would make the reply disappear while leaving
34
+ * the quote in place, and the person who confirmed the send would never know.
35
+ *
36
+ * Everything else stays. Inline styles, classes, tables, colours, alignment,
37
+ * images, links, spacing: untouched, character for character, because the parts
38
+ * that are kept are copied out of the original string rather than rebuilt from
39
+ * a parse tree. A re-serialised quote is a quote somebody rewrote.
40
+ *
41
+ * ## Images
42
+ *
43
+ * Measured: **none** of the 687 images in the sample were embedded by content
44
+ * id; every one was an ordinary address. So a quote needs no files carried
45
+ * along and its pictures simply work. Content ids are handled anyway, because
46
+ * "none in this sample" is not "never", and a quote with a broken image would
47
+ * be worse than one without.
48
+ */
49
+ export interface PreparedQuote {
50
+ /** The original's markup, minus what reaches outside the quote. */
51
+ html: string;
52
+ /** Which elements were dropped, for the answer and for the confirmation. */
53
+ removed: string[];
54
+ /** Content ids the quote still refers to, which have to travel with it. */
55
+ contentIds: string[];
56
+ }
57
+ /**
58
+ * Prepares a message's markup for use as a quote.
59
+ *
60
+ * Works by cutting ranges out of the original string rather than by rebuilding
61
+ * it. Every character that is not inside a removed element survives exactly as
62
+ * it was written, which is the difference between quoting a message and
63
+ * paraphrasing it.
64
+ */
65
+ export declare function prepareQuotedMarkup(html: string): PreparedQuote;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Quoting a message inside a reply, with its formatting intact.
3
+ *
4
+ * The obvious way to quote is to convert the original to text. It is safe and
5
+ * it is what this server did first, and measured against a real mailbox it
6
+ * turned out to be too expensive: 94 percent of ordinary mail is formatted, so
7
+ * every reply would throw away the look of nearly everything it answers.
8
+ *
9
+ * The other obvious way is to keep the original's markup and cut it down to
10
+ * what this server permits itself to write. Also measured, on the same
11
+ * mailbox: **83 percent of real messages would need work, with a median of 87
12
+ * removals each.** That is not a plainer quote, it is a demolished one, and the
13
+ * pieces that carry the look are the first to go.
14
+ *
15
+ * So the quote keeps the original **byte for byte**, with one exception that
16
+ * the measurement made unavoidable.
17
+ *
18
+ * ## The exception, and why it is not negotiable
19
+ *
20
+ * A `<style>` block belongs to a document, not to the element it sits in. Embed
21
+ * one inside a quote and its rules apply to the whole message, including the
22
+ * reply written above it. Measured on the same sample:
23
+ *
24
+ * | | |
25
+ * | :--- | :--- |
26
+ * | Formatted messages carrying a `<style>` block | 66 percent |
27
+ * | Of those, rules aimed at bare elements rather than classes | 56 percent |
28
+ * | The selectors, in order | `body`, `a`, `table`, `p`, `img`, `td`, `blockquote` |
29
+ *
30
+ * `blockquote` appears in thirty-six of them, which is the element this quote
31
+ * is wrapped in. So in more than half of all replies, the quoted message's
32
+ * stylesheet would reach out and restyle the sender's own words. A rule as
33
+ * short as `p { display: none }` would make the reply disappear while leaving
34
+ * the quote in place, and the person who confirmed the send would never know.
35
+ *
36
+ * Everything else stays. Inline styles, classes, tables, colours, alignment,
37
+ * images, links, spacing: untouched, character for character, because the parts
38
+ * that are kept are copied out of the original string rather than rebuilt from
39
+ * a parse tree. A re-serialised quote is a quote somebody rewrote.
40
+ *
41
+ * ## Images
42
+ *
43
+ * Measured: **none** of the 687 images in the sample were embedded by content
44
+ * id; every one was an ordinary address. So a quote needs no files carried
45
+ * along and its pictures simply work. Content ids are handled anyway, because
46
+ * "none in this sample" is not "never", and a quote with a broken image would
47
+ * be worse than one without.
48
+ */
49
+ import { Parser } from 'htmlparser2';
50
+ /**
51
+ * Elements dropped from a quote, with everything inside them.
52
+ *
53
+ * All of them are document-level: they say something about the message as a
54
+ * whole rather than about the place they stand in, so inside a quote they act
55
+ * outside it. That is the entire criterion, and it is why the list is short.
56
+ */
57
+ const DOCUMENT_LEVEL = new Set([
58
+ 'style', 'script', 'head', 'meta', 'link', 'title', 'base', 'noscript',
59
+ ]);
60
+ /** Wrappers whose contents are kept and whose tags are not. */
61
+ const UNWRAP = new Set(['html', 'body']);
62
+ /**
63
+ * Prepares a message's markup for use as a quote.
64
+ *
65
+ * Works by cutting ranges out of the original string rather than by rebuilding
66
+ * it. Every character that is not inside a removed element survives exactly as
67
+ * it was written, which is the difference between quoting a message and
68
+ * paraphrasing it.
69
+ */
70
+ export function prepareQuotedMarkup(html) {
71
+ /** Ranges to cut, as [start, end) in the original string. */
72
+ const cuts = [];
73
+ const removed = [];
74
+ const contentIds = [];
75
+ /** Set while inside a dropped element, so nested tags are not counted twice. */
76
+ let dropDepth = 0;
77
+ let dropStart = 0;
78
+ let dropName = '';
79
+ const parser = new Parser({
80
+ onopentag(name, attributes) {
81
+ const tag = name.toLowerCase();
82
+ if (dropDepth > 0) {
83
+ if (tag === dropName)
84
+ dropDepth += 1;
85
+ return;
86
+ }
87
+ if (DOCUMENT_LEVEL.has(tag)) {
88
+ dropDepth = 1;
89
+ dropName = tag;
90
+ dropStart = parser.startIndex;
91
+ return;
92
+ }
93
+ if (UNWRAP.has(tag)) {
94
+ cuts.push([parser.startIndex, parser.endIndex + 1]);
95
+ return;
96
+ }
97
+ if (tag === 'img' && typeof attributes.src === 'string') {
98
+ const src = attributes.src.trim();
99
+ if (src.toLowerCase().startsWith('cid:'))
100
+ contentIds.push(src.slice(4));
101
+ }
102
+ },
103
+ oncomment() {
104
+ // Comments are cut out of a quote, and it is not tidiness. Outlook acts
105
+ // on conditional comments, so a `<style>` block inside
106
+ // `<!--[if mso]>` is a stylesheet that a parser reads as a comment and
107
+ // a mail client executes. Measured on a real mailbox: three of 151
108
+ // messages hid a style block exactly there, and one hid a `<noscript>`.
109
+ // Every unit test in this module passed while that hole was open.
110
+ //
111
+ // Matching on `[if mso]` would close those four and leave the next
112
+ // variant open. Dropping comments closes the shape of the hole: nothing
113
+ // renders a comment except this one client, and what it renders is a
114
+ // layout fallback inside a quote.
115
+ if (dropDepth === 0)
116
+ cuts.push([parser.startIndex, parser.endIndex + 1]);
117
+ },
118
+ onclosetag(name) {
119
+ const tag = name.toLowerCase();
120
+ if (dropDepth > 0 && tag === dropName) {
121
+ dropDepth -= 1;
122
+ if (dropDepth === 0) {
123
+ cuts.push([dropStart, parser.endIndex + 1]);
124
+ removed.push(`<${tag}>`);
125
+ }
126
+ return;
127
+ }
128
+ if (dropDepth === 0 && UNWRAP.has(tag)) {
129
+ cuts.push([parser.startIndex, parser.endIndex + 1]);
130
+ }
131
+ },
132
+ }, {
133
+ decodeEntities: false,
134
+ lowerCaseTags: true,
135
+ lowerCaseAttributeNames: true,
136
+ recognizeSelfClosing: true,
137
+ });
138
+ parser.write(html);
139
+ parser.end();
140
+ // An element left open at the end of the document still has to be cut, or
141
+ // its content would ride along inside a quote.
142
+ if (dropDepth > 0) {
143
+ cuts.push([dropStart, html.length]);
144
+ removed.push(`<${dropName}>`);
145
+ }
146
+ cuts.sort((a, b) => a[0] - b[0]);
147
+ let out = '';
148
+ let at = 0;
149
+ for (const [start, end] of cuts) {
150
+ if (start > at)
151
+ out += html.slice(at, start);
152
+ at = Math.max(at, end);
153
+ }
154
+ out += html.slice(at);
155
+ return { html: out.trim(), removed: [...new Set(removed)], contentIds };
156
+ }
157
+ //# sourceMappingURL=quote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote.js","sourceRoot":"","sources":["../../src/mail/quote.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Searching messages.
3
+ *
4
+ * Measured against a mailbox of 26816 messages: a
5
+ * full-text search takes between 1.7 and 3.3 seconds, a search that finds
6
+ * nothing takes just as long. The Bridge walks its local database linearly,
7
+ * there is no index, so the time grows with the size of the mailbox.
8
+ *
9
+ * That is slow but usable, which is why this server builds no index of its own.
10
+ * An index would mean mirroring the decrypted mailbox to disk, undoing locally
11
+ * what Proton does for you.
12
+ *
13
+ * Metadata queries are cheap by comparison: unread came back in 3 ms, a size
14
+ * filter in 437 ms.
15
+ */
16
+ import type { SearchObject } from 'imapflow';
17
+ import type { Connection } from '../bridge/connection.js';
18
+ import { type MessageHeader, type OrderingCost } from './messages.js';
19
+ export interface SearchCriteria {
20
+ /** Free text, searched in the body and in headers. The expensive part. */
21
+ text?: string;
22
+ /** Substring of the subject. */
23
+ subject?: string;
24
+ /** Substring of the sender address or display name. */
25
+ from?: string;
26
+ /** Substring of a recipient address. */
27
+ to?: string;
28
+ /** Only messages on or after this date. */
29
+ since?: Date;
30
+ /** Only messages before this date. */
31
+ before?: Date;
32
+ /** true for unread only, false for read only. */
33
+ seen?: boolean;
34
+ /** Only starred messages. */
35
+ flagged?: boolean;
36
+ /** Minimum size in bytes. */
37
+ largerThan?: number;
38
+ }
39
+ export interface SearchResult {
40
+ path: string;
41
+ /** Number of matches, independent of paging. */
42
+ total: number;
43
+ offset: number;
44
+ headers: MessageHeader[];
45
+ /** How long the search itself took, so a caller can judge the cost. */
46
+ elapsedMs: number;
47
+ /** What putting the hits in order cost, separate from the search. */
48
+ ordering: OrderingCost;
49
+ /** True when the search included a full-text term, which is the slow case. */
50
+ fullText: boolean;
51
+ }
52
+ /** Translates the criteria into what imapflow expects. */
53
+ export declare function buildQuery(criteria: SearchCriteria): SearchObject;
54
+ /** Whether a query contains the expensive full-text part. */
55
+ export declare function isFullText(criteria: SearchCriteria): boolean;
56
+ export interface SearchOptions extends SearchCriteria {
57
+ limit?: number;
58
+ offset?: number;
59
+ }
60
+ /**
61
+ * Searches one mailbox and returns headers, never bodies.
62
+ *
63
+ * "All Mail" is the mailbox to search when the caller does not care where a
64
+ * message lives, since it holds everything including trash.
65
+ */
66
+ export declare function searchMessages(connection: Connection, path: string, options: SearchOptions): Promise<SearchResult>;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Searching messages.
3
+ *
4
+ * Measured against a mailbox of 26816 messages: a
5
+ * full-text search takes between 1.7 and 3.3 seconds, a search that finds
6
+ * nothing takes just as long. The Bridge walks its local database linearly,
7
+ * there is no index, so the time grows with the size of the mailbox.
8
+ *
9
+ * That is slow but usable, which is why this server builds no index of its own.
10
+ * An index would mean mirroring the decrypted mailbox to disk, undoing locally
11
+ * what Proton does for you.
12
+ *
13
+ * Metadata queries are cheap by comparison: unread came back in 3 ms, a size
14
+ * filter in 437 ms.
15
+ */
16
+ import { BridgeError } from '../bridge/errors.js';
17
+ import { fetchDates, fetchHeaders, orderNewestFirst, MAX_LIST_LIMIT, } from './messages.js';
18
+ /** Translates the criteria into what imapflow expects. */
19
+ export function buildQuery(criteria) {
20
+ const query = {};
21
+ if (criteria.text)
22
+ query.text = criteria.text;
23
+ if (criteria.subject)
24
+ query.header = { subject: criteria.subject };
25
+ if (criteria.from)
26
+ query.from = criteria.from;
27
+ if (criteria.to)
28
+ query.to = criteria.to;
29
+ if (criteria.since)
30
+ query.since = criteria.since;
31
+ if (criteria.before)
32
+ query.before = criteria.before;
33
+ if (criteria.seen !== undefined)
34
+ query.seen = criteria.seen;
35
+ if (criteria.flagged !== undefined)
36
+ query.flagged = criteria.flagged;
37
+ if (criteria.largerThan !== undefined)
38
+ query.larger = criteria.largerThan;
39
+ // An empty query would match everything, which is list_messages rather than
40
+ // a search. Saying so is better than silently returning the whole mailbox.
41
+ if (Object.keys(query).length === 0) {
42
+ throw new BridgeError('A search needs at least one criterion. Pass text, subject, from, to, since, before, ' +
43
+ 'unread, starred or largerThan. To see everything in a mailbox use list_messages instead.');
44
+ }
45
+ return query;
46
+ }
47
+ /** Whether a query contains the expensive full-text part. */
48
+ export function isFullText(criteria) {
49
+ return Boolean(criteria.text);
50
+ }
51
+ /**
52
+ * Searches one mailbox and returns headers, never bodies.
53
+ *
54
+ * "All Mail" is the mailbox to search when the caller does not care where a
55
+ * message lives, since it holds everything including trash.
56
+ */
57
+ export async function searchMessages(connection, path, options) {
58
+ const { limit: rawLimit, offset: rawOffset, ...criteria } = options;
59
+ const limit = Math.min(Math.max(rawLimit ?? 25, 1), MAX_LIST_LIMIT);
60
+ const offset = Math.max(rawOffset ?? 0, 0);
61
+ // Built before opening the mailbox, so an empty query fails fast.
62
+ const query = buildQuery(criteria);
63
+ const started = process.hrtime.bigint();
64
+ const uids = await connection.withMailbox(path, async (client, status) => {
65
+ if (status.messages === 0)
66
+ return [];
67
+ const hits = await client.search(query, { uid: true });
68
+ // imapflow answers with false rather than an empty array when a search
69
+ // yields nothing, and `?? []` would let that through.
70
+ return Array.isArray(hits) ? hits : [];
71
+ });
72
+ const elapsedMs = Math.round(Number(process.hrtime.bigint() - started) / 1e6);
73
+ if (uids.length === 0) {
74
+ return {
75
+ path,
76
+ total: 0,
77
+ offset,
78
+ headers: [],
79
+ elapsedMs,
80
+ ordering: { messages: 0, elapsedMs: 0 },
81
+ fullText: isFullText(criteria),
82
+ };
83
+ }
84
+ // Ordering reads the date of every hit, because a page can only be cut out of
85
+ // a list that is already in order. Only the dates: the full header fetch below
86
+ // still covers one page, which is what keeps this affordable. A common word
87
+ // matched 6803 messages in the measured mailbox.
88
+ const orderStarted = process.hrtime.bigint();
89
+ const ordered = await connection.withMailbox(path, async (client) => orderNewestFirst(await fetchDates(client, uids)));
90
+ const ordering = {
91
+ messages: uids.length,
92
+ elapsedMs: Math.round(Number(process.hrtime.bigint() - orderStarted) / 1e6),
93
+ };
94
+ const page = ordered.slice(offset, offset + limit);
95
+ // Same helper the listing uses, so both produce identical entries.
96
+ const headers = await connection.withMailbox(path, (client) => fetchHeaders(client, page));
97
+ return {
98
+ path,
99
+ total: ordered.length,
100
+ offset,
101
+ headers,
102
+ elapsedMs,
103
+ ordering,
104
+ fullText: isFullText(criteria),
105
+ };
106
+ }
107
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/mail/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Sending over the Bridge's SMTP interface.
3
+ *
4
+ * This is the only code in the server that can make something leave the
5
+ * machine, so it is deliberately small and says no in more places than it says
6
+ * yes. The confirmation itself is not here: it belongs to the tool layer, where
7
+ * the protocol's round trip lives. What is here is the part that must be true
8
+ * regardless of who asks.
9
+ *
10
+ * ## The certificate
11
+ *
12
+ * The Bridge password goes over SMTP exactly as it goes over IMAP, so the
13
+ * connection is pinned the same way. Measured on 31.07.2026: **the Bridge
14
+ * presents the same certificate on both ports**, fingerprint for fingerprint,
15
+ * so the entry recorded for IMAP is what SMTP is verified against and no second
16
+ * pin is needed. That is also why nothing here disables the check: if Proton
17
+ * ever gives the two ports different certificates, sending stops with a
18
+ * verification failure instead of quietly handing the password to whatever
19
+ * answered.
20
+ *
21
+ * ## No verification afterwards
22
+ *
23
+ * Measured on 31.07.2026: SMTP took about six and a half seconds to accept a
24
+ * message and answer `250 2.0.0 OK: queued`, and the message showed up over
25
+ * IMAP about three seconds after that. Nothing here looks for what it just
26
+ * sent. What SMTP accepted is what is reported, and the answer says what that
27
+ * does and does not mean.
28
+ *
29
+ * ## What Proton keeps
30
+ *
31
+ * Also measured, and it is the reason replying exists as a sending tool rather
32
+ * than only as a draft tool: **a sent message keeps its `In-Reply-To`.** A
33
+ * stored draft does not, and gets Proton's internal thread id in `References`
34
+ * instead. `References` is normalised on the way out, keeping the entries
35
+ * Proton can resolve and dropping the ones it cannot, which is reasonable
36
+ * behaviour and leaves the threading intact.
37
+ *
38
+ * The blind copies were verified to be absent from the delivered headers while
39
+ * the blind recipient still received the message. That is the whole reason the
40
+ * envelope is stated separately below.
41
+ */
42
+ import type { Config, BridgeCredentials } from '../config.js';
43
+ import { type Draft } from './compose.js';
44
+ export interface SendOutcome {
45
+ messageId: string;
46
+ /** Addresses the Bridge took responsibility for. */
47
+ accepted: string[];
48
+ /** Addresses it refused. A message can be half delivered. */
49
+ rejected: string[];
50
+ /** What the Bridge said, for the record. */
51
+ response: string;
52
+ }
53
+ /**
54
+ * Sends a message.
55
+ *
56
+ * The exact bytes that were built are handed over as the message, with the
57
+ * envelope stated separately. That is not a detail: the envelope is what
58
+ * carries the blind copies, and the built message must not, so the two have to
59
+ * be given apart from one another.
60
+ */
61
+ export declare function sendMessage(config: Config, getCredentials: () => Promise<BridgeCredentials | undefined>, readOnly: boolean, draft: Draft): Promise<SendOutcome>;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Sending over the Bridge's SMTP interface.
3
+ *
4
+ * This is the only code in the server that can make something leave the
5
+ * machine, so it is deliberately small and says no in more places than it says
6
+ * yes. The confirmation itself is not here: it belongs to the tool layer, where
7
+ * the protocol's round trip lives. What is here is the part that must be true
8
+ * regardless of who asks.
9
+ *
10
+ * ## The certificate
11
+ *
12
+ * The Bridge password goes over SMTP exactly as it goes over IMAP, so the
13
+ * connection is pinned the same way. Measured on 31.07.2026: **the Bridge
14
+ * presents the same certificate on both ports**, fingerprint for fingerprint,
15
+ * so the entry recorded for IMAP is what SMTP is verified against and no second
16
+ * pin is needed. That is also why nothing here disables the check: if Proton
17
+ * ever gives the two ports different certificates, sending stops with a
18
+ * verification failure instead of quietly handing the password to whatever
19
+ * answered.
20
+ *
21
+ * ## No verification afterwards
22
+ *
23
+ * Measured on 31.07.2026: SMTP took about six and a half seconds to accept a
24
+ * message and answer `250 2.0.0 OK: queued`, and the message showed up over
25
+ * IMAP about three seconds after that. Nothing here looks for what it just
26
+ * sent. What SMTP accepted is what is reported, and the answer says what that
27
+ * does and does not mean.
28
+ *
29
+ * ## What Proton keeps
30
+ *
31
+ * Also measured, and it is the reason replying exists as a sending tool rather
32
+ * than only as a draft tool: **a sent message keeps its `In-Reply-To`.** A
33
+ * stored draft does not, and gets Proton's internal thread id in `References`
34
+ * instead. `References` is normalised on the way out, keeping the entries
35
+ * Proton can resolve and dropping the ones it cannot, which is reasonable
36
+ * behaviour and leaves the threading intact.
37
+ *
38
+ * The blind copies were verified to be absent from the delivered headers while
39
+ * the blind recipient still received the message. That is the whole reason the
40
+ * envelope is stated separately below.
41
+ */
42
+ import { createTransport } from 'nodemailer';
43
+ import { BridgeError } from '../bridge/errors.js';
44
+ import { ensurePinned } from '../bridge/certificate.js';
45
+ import { buildMessage, allRecipients } from './compose.js';
46
+ /**
47
+ * Opens an authenticated SMTP connection to the Bridge.
48
+ *
49
+ * Built per send rather than held open. Sending is rare, and a connection that
50
+ * sits idle holding the Bridge password is a worse trade than a handshake.
51
+ */
52
+ async function transport(config, credentials) {
53
+ // The pin is recorded against the IMAP port because that is where the first
54
+ // connection happens. Measured to be the same certificate on the SMTP port,
55
+ // so this is the material to verify against; a difference fails the
56
+ // handshake rather than being waved through.
57
+ const pinned = await ensurePinned(config.host, config.imapPort);
58
+ return createTransport({
59
+ host: config.host,
60
+ port: config.smtpPort,
61
+ // Plain connection upgraded via STARTTLS, which is what the Bridge offers.
62
+ secure: false,
63
+ // Not "if available": without this a server that drops STARTTLS from its
64
+ // greeting would get the password in the clear.
65
+ requireTLS: true,
66
+ auth: { user: credentials.user, pass: credentials.pass },
67
+ tls: {
68
+ rejectUnauthorized: true,
69
+ ca: [pinned.pem],
70
+ // The certificate does not carry 127.0.0.1 as a subject alternative
71
+ // name, so the hostname check has to go. It adds nothing on top of
72
+ // pinning: matching one specific certificate says more than matching a
73
+ // name written inside it.
74
+ checkServerIdentity: () => undefined,
75
+ },
76
+ logger: false,
77
+ });
78
+ }
79
+ function assertWritable(readOnly) {
80
+ if (readOnly) {
81
+ throw new BridgeError('The server is running read-only, so sending is refused. This is set by ' +
82
+ 'PROTON_MCP_READ_ONLY and the web interface reports it as the current mode.');
83
+ }
84
+ }
85
+ /**
86
+ * Sends a message.
87
+ *
88
+ * The exact bytes that were built are handed over as the message, with the
89
+ * envelope stated separately. That is not a detail: the envelope is what
90
+ * carries the blind copies, and the built message must not, so the two have to
91
+ * be given apart from one another.
92
+ */
93
+ export async function sendMessage(config, getCredentials, readOnly, draft) {
94
+ assertWritable(readOnly);
95
+ const credentials = await getCredentials();
96
+ if (!credentials) {
97
+ throw new BridgeError('No Bridge credentials are available, so there is nothing to send with. Sign in through ' +
98
+ 'the configuration page first.');
99
+ }
100
+ const recipients = allRecipients(draft);
101
+ if (recipients.length === 0) {
102
+ throw new BridgeError('A message needs at least one recipient.');
103
+ }
104
+ // Built without the blind copies in the headers. They travel in the envelope
105
+ // below, where the other recipients never see them.
106
+ const raw = await buildMessage(draft, { keepBcc: false });
107
+ const mailer = await transport(config, credentials);
108
+ try {
109
+ const info = await mailer.sendMail({
110
+ // The bytes, not the fields: what was confirmed is what goes out.
111
+ raw,
112
+ envelope: {
113
+ from: draft.from.address,
114
+ to: recipients.map((r) => r.address),
115
+ },
116
+ });
117
+ return {
118
+ messageId: draft.messageId,
119
+ accepted: (info.accepted ?? []).map(String),
120
+ rejected: (info.rejected ?? []).map(String),
121
+ response: info.response ?? '',
122
+ };
123
+ }
124
+ catch (error) {
125
+ throw new BridgeError(`The Bridge did not accept the message: ${error.message}. ` +
126
+ 'Nothing was sent, and it can be sent again once the Bridge is reachable.', 'unreachable', { cause: error });
127
+ }
128
+ finally {
129
+ mailer.close();
130
+ }
131
+ }
132
+ //# sourceMappingURL=send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/mail/send.ts"],"names":[],"mappings":"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"}