proton-mail-bridge-client 2.1.3 → 2.1.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.
package/dist/index.d.ts CHANGED
@@ -23,6 +23,8 @@ export declare function extractUnsubscribeInfo(detail: EmailDetail): {
23
23
  mailto?: string;
24
24
  url?: string;
25
25
  };
26
+ export declare function buildReplyHtml(detail: EmailDetail, htmlBody: string): string;
27
+ export declare function buildForwardHtml(detail: EmailDetail, htmlBody?: string): string;
26
28
  export declare function getReplyRecipients(detail: EmailDetail, ownerEmail: string, replyAll: boolean): {
27
29
  to: string[];
28
30
  cc: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AASA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAYnE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAA2H,iBAAiB,EAA+B,MAAM,mCAAmC,CAAC;AAC5N,OAAO,EAAkB,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,OAAO,KAAK,EAMV,mBAAmB,EAEnB,WAAW,EAIX,WAAW,EAIX,gBAAgB,EAIjB,MAAM,kBAAkB,CAAC;AAmB1B,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAmrDnF,eAAO,MAAM,eAAe,aAyB1B,CAAC;AAgGH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAC1B,OAAO,CAAC,CAAC,CAAC,CAwBZ;AAmED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK3G;AAKD,wBAAgB,0BAA0B,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAI1G;AASD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAa9E;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAS7F;AA4KD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,OAAO,GAChB;IAAE,EAAE,EAAE,MAAM,EAAE,CAAC;IAAC,EAAE,EAAE,MAAM,EAAE,CAAA;CAAE,CAyBhC;AA6ED,wBAAgB,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CA4CtE;AAcD,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYlE;AAgWD,wBAAgB,4BAA4B,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAKjI;AA8PD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAEzE;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAOzE;AAED,UAAU,mBAAmB;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AASD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAS,EAC9B,MAAM,EAAE,MAAM,EACd,kBAAkB,CAAC,EAAE,MAAM,GAC1B,mBAAmB,EAAE,CA8CvB;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,mBAAmB,EAC3B,gBAAgB,EAAE,mBAAmB,EAAE,GACtC,mBAAmB,CAmBrB;AAWD,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,MAAM,EAAE,GACjB,GAAG,CAAC,MAAM,GAAG,SAAS,EAAE;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAYvE;AAOD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,mBAAmB,CAQ3G;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,mBAAmB,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,mBAAmB,EAAE,CAK/G;AAID,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,mBAAmB,CASpF;AAsQD,wBAAgB,kBAAkB,IAAI,gBAAgB,CAkGrD;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE;IACP,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4oIP;AAED,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAmE1C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, ListResources
12
12
  import { isMainModule } from "./is-main.js";
13
13
  import { AnalyticsService } from "./services/analytics-service.js";
14
14
  import { BULK_ITEM_TIMEOUT_MS, describeImapError, isLikelyAuthenticationError, isLikelyConnectionError, isLikelyTlsMismatchError, UID_VALIDITY_MISMATCH_ERROR } from "./services/simple-imap-service.js";
15
- import { applySignature } from "./services/smtp-service.js";
15
+ import { applySignature, plainTextToHtml } from "./services/smtp-service.js";
16
16
  import { ensureValidEmails, foldQuotedHistory, isTextLikeMimeType, isValidEmail, lowerCaseAddress, normalizeBoolean, normalizeLimit, normalizeJsonValue, parseEmailId, parseEmails, projectFields, renderMarkdown, slugifyAccountAddress, splitAccountPrefix, stringifyForJson, withAccountPrefix, } from "./utils/helpers.js";
17
17
  import { AccountManager } from "./services/account-manager.js";
18
18
  import { logger } from "./utils/logger.js";
@@ -2069,6 +2069,53 @@ function buildForwardText(detail, body) {
2069
2069
  .filter((line, index, array) => line !== "" || (index > 0 && array[index - 1] !== ""))
2070
2070
  .join("\n");
2071
2071
  }
2072
+ // HTML counterparts of buildReplyText/buildForwardText — needed because a
2073
+ // markdown-authored reply/forward (markdownBody) renders its OWN new text to a
2074
+ // real, separate htmlBody, but buildReplyText/buildForwardText only ever wrapped
2075
+ // the PLAIN TEXT `body` with the quoted/forwarded original. That original never
2076
+ // made it into htmlBody at all — an HTML-viewing recipient of a markdown reply
2077
+ // saw only the new text and signature, with the entire original message missing
2078
+ // (confirmed on generated MIME messages), while the text/plain part had it via
2079
+ // buildReplyText. Uses the original message's own detail.html when present
2080
+ // (real markup, not reconstructed); falls back to escaping detail.text/preview
2081
+ // through plainTextToHtml otherwise, same as a plain-text send's own html
2082
+ // alternative (see smtp-service.ts's plainTextToHtml comment).
2083
+ export function buildReplyHtml(detail, htmlBody) {
2084
+ const originalHtml = typeof detail.html === "string" && detail.html
2085
+ ? detail.html
2086
+ : plainTextToHtml(detail.text || detail.preview || "");
2087
+ const fromText = formatAddressList(detail.from);
2088
+ const dateText = detail.date || detail.internalDate || "an unknown date";
2089
+ return [
2090
+ htmlBody,
2091
+ `<p>On ${escapeHtmlForQuote(dateText)}, ${escapeHtmlForQuote(fromText || "the sender")} wrote:</p>`,
2092
+ `<blockquote>${originalHtml}</blockquote>`,
2093
+ ].join("\n");
2094
+ }
2095
+ export function buildForwardHtml(detail, htmlBody) {
2096
+ const originalHtml = typeof detail.html === "string" && detail.html
2097
+ ? detail.html
2098
+ : plainTextToHtml(detail.text || detail.preview || "");
2099
+ return [
2100
+ htmlBody?.trim() || "",
2101
+ "<p>---------- Forwarded message ---------</p>",
2102
+ `<p>From: ${escapeHtmlForQuote(formatAddressList(detail.from))}<br>`,
2103
+ `Date: ${escapeHtmlForQuote(detail.date || detail.internalDate || "")}<br>`,
2104
+ `Subject: ${escapeHtmlForQuote(detail.subject)}<br>`,
2105
+ `To: ${escapeHtmlForQuote(formatAddressList(detail.to))}${detail.cc.length > 0 ? `<br>Cc: ${escapeHtmlForQuote(formatAddressList(detail.cc))}` : ""}</p>`,
2106
+ originalHtml,
2107
+ ]
2108
+ .filter((part) => part !== "")
2109
+ .join("\n");
2110
+ }
2111
+ function escapeHtmlForQuote(value) {
2112
+ return value
2113
+ .replace(/&/g, "&amp;")
2114
+ .replace(/</g, "&lt;")
2115
+ .replace(/>/g, "&gt;")
2116
+ .replace(/"/g, "&quot;")
2117
+ .replace(/'/g, "&#39;");
2118
+ }
2072
2119
  export function getReplyRecipients(detail, ownerEmail, replyAll) {
2073
2120
  const owner = lowerCaseAddress(ownerEmail);
2074
2121
  const primary = addressValues(detail.replyTo).length > 0 ? detail.replyTo : detail.from;
@@ -3634,8 +3681,14 @@ export function createServer(config, options = {}) {
3634
3681
  const includeQuoteReply = normalizeBoolean(args.includeQuote, true);
3635
3682
  // Signature goes right after the user's own reply text, before the
3636
3683
  // quoted original — not at the very end, after the quote.
3637
- const signedReply = applySignature(body, htmlBody, normalizeBoolean(args.appendSignature, true));
3684
+ const signedReply = applySignature(body, htmlBody, normalizeBoolean(args.appendSignature, true), isHtml);
3638
3685
  const replyBody = includeQuoteReply ? buildReplyText(detail, signedReply.body) : signedReply.body;
3686
+ // A separate htmlBody (the markdown-rendered case) needs the SAME quoted
3687
+ // original merged in — see buildReplyHtml's comment for why this was
3688
+ // previously missing entirely from the html part.
3689
+ const replyHtmlBody = includeQuoteReply && signedReply.htmlBody !== undefined
3690
+ ? buildReplyHtml(detail, signedReply.htmlBody)
3691
+ : signedReply.htmlBody;
3639
3692
  if (config.runtime.restrictOutboundToSelf) {
3640
3693
  const selfAddr = config.smtp.username.toLowerCase();
3641
3694
  const allR = [...to, ...cc, ...extraBcc];
@@ -3654,7 +3707,7 @@ export function createServer(config, options = {}) {
3654
3707
  subject: prefixedSubject(detail.subject, "Re:"),
3655
3708
  body: replyBody,
3656
3709
  isHtml,
3657
- htmlBody: signedReply.htmlBody,
3710
+ htmlBody: replyHtmlBody,
3658
3711
  fromName: fromNameReply,
3659
3712
  from: fromReply,
3660
3713
  sanitizeHtml: sanitizeHtmlReply,
@@ -3730,8 +3783,12 @@ export function createServer(config, options = {}) {
3730
3783
  const includeQuoteRa = normalizeBoolean(args.includeQuote, true);
3731
3784
  // Signature goes right after the user's own reply text, before the
3732
3785
  // quoted original — not at the very end, after the quote.
3733
- const signedReplyRa = applySignature(bodyRa, htmlBodyRa, normalizeBoolean(args.appendSignature, true));
3786
+ const signedReplyRa = applySignature(bodyRa, htmlBodyRa, normalizeBoolean(args.appendSignature, true), isHtmlRa);
3734
3787
  const replyBodyRa = includeQuoteRa ? buildReplyText(detailRa, signedReplyRa.body) : signedReplyRa.body;
3788
+ // Same fix as reply_to_email — see buildReplyHtml's comment.
3789
+ const replyHtmlBodyRa = includeQuoteRa && signedReplyRa.htmlBody !== undefined
3790
+ ? buildReplyHtml(detailRa, signedReplyRa.htmlBody)
3791
+ : signedReplyRa.htmlBody;
3735
3792
  const resultRa = await withAudit(auditService, name, args, async () => sendBundleForRa.smtpService.sendEmail({
3736
3793
  to: toRa,
3737
3794
  cc: ccRa,
@@ -3739,7 +3796,7 @@ export function createServer(config, options = {}) {
3739
3796
  subject: prefixedSubject(detailRa.subject, "Re:"),
3740
3797
  body: replyBodyRa,
3741
3798
  isHtml: isHtmlRa,
3742
- htmlBody: signedReplyRa.htmlBody,
3799
+ htmlBody: replyHtmlBodyRa,
3743
3800
  fromName: fromNameRa,
3744
3801
  from: fromRa,
3745
3802
  sanitizeHtml: sanitizeHtmlRa,
@@ -3838,7 +3895,13 @@ export function createServer(config, options = {}) {
3838
3895
  // Signature goes right after the user's own note, before the
3839
3896
  // "---------- Forwarded message ---------" block — not at the very
3840
3897
  // end, after the forwarded content.
3841
- const signedFwd = applySignature(body ?? "", htmlBody, normalizeBoolean(args.appendSignature, true));
3898
+ const signedFwd = applySignature(body ?? "", htmlBody, normalizeBoolean(args.appendSignature, true), isHtml);
3899
+ // Same fix as reply_to_email/reply_all_email — see buildReplyHtml's
3900
+ // comment (applies equally to a markdown-authored forward's separate
3901
+ // htmlBody, which buildForwardText never touches).
3902
+ const forwardHtmlBody = signedFwd.htmlBody !== undefined
3903
+ ? buildForwardHtml(detail, signedFwd.htmlBody)
3904
+ : signedFwd.htmlBody;
3842
3905
  const result = await withAudit(auditService, name, args, async () => sendBundleForFwd.smtpService.sendEmail({
3843
3906
  to,
3844
3907
  cc,
@@ -3846,7 +3909,7 @@ export function createServer(config, options = {}) {
3846
3909
  subject: prefixedSubject(detail.subject, "Fwd:"),
3847
3910
  body: buildForwardText(detail, signedFwd.body),
3848
3911
  isHtml,
3849
- htmlBody: signedFwd.htmlBody,
3912
+ htmlBody: forwardHtmlBody,
3850
3913
  fromName: fromNameFwd,
3851
3914
  from: fromFwd,
3852
3915
  sanitizeHtml: sanitizeHtmlFwd,
@@ -3944,14 +4007,19 @@ export function createServer(config, options = {}) {
3944
4007
  ]);
3945
4008
  }
3946
4009
  case "create_reply_draft": {
3947
- const detail = await imapService.getEmailById(requireString(args, "emailId"));
4010
+ // emailId carries the same "<slug>::" account prefix as everywhere else —
4011
+ // this previously always read via the PRIMARY account's imapService
4012
+ // regardless of that prefix, so replying to a non-primary account's email
4013
+ // either resolved the wrong message or threw not-found.
4014
+ const { bundle: createReplyReadBundle, rest: createReplyEmailIdRest } = resolveAccountForEmailId(requireString(args, "emailId"));
4015
+ const detail = await createReplyReadBundle.imapService.getEmailById(createReplyEmailIdRest);
3948
4016
  const body = requireString(args, "body");
3949
4017
  const isHtml = normalizeBoolean(args.isHtml, false);
3950
4018
  const replyAll = normalizeBoolean(args.replyAll, false);
3951
4019
  const attachments = optionalAttachmentList(args.attachments);
3952
4020
  const extraCc = parseEmails(optionalString(args, "cc"));
3953
4021
  const extraBcc = parseEmails(optionalString(args, "bcc"));
3954
- const recipients = getReplyRecipients(detail, config.smtp.username, replyAll);
4022
+ const recipients = getReplyRecipients(detail, createReplyReadBundle.config.smtp.username, replyAll);
3955
4023
  const cc = uniqueAddresses([...recipients.cc, ...extraCc]);
3956
4024
  const to = uniqueAddresses(recipients.to);
3957
4025
  ensureValidEmails(to, "to");
@@ -3960,10 +4028,15 @@ export function createServer(config, options = {}) {
3960
4028
  if (to.length === 0) {
3961
4029
  throw new McpError(ErrorCode.InvalidParams, "Unable to infer reply recipient.");
3962
4030
  }
3963
- // Same from-based draftStore routing as create_draft — see its comment.
4031
+ // Same from-based draftStore routing as create_draft — see its comment
4032
+ // except the no-`from` default is the ACCOUNT THE ORIGINAL MESSAGE BELONGS
4033
+ // TO, not always the primary: a reply drafted against a non-primary
4034
+ // account's email with no explicit `from` should default to replying as
4035
+ // that same account, not silently switch to primary. `from` still wins
4036
+ // when it names a different configured account.
3964
4037
  const createReplyDraftBundle = optionalString(args, "from") !== undefined
3965
- ? accountManager.byAddress(optionalString(args, "from")) ?? primaryBundle
3966
- : primaryBundle;
4038
+ ? accountManager.byAddress(optionalString(args, "from")) ?? createReplyReadBundle
4039
+ : createReplyReadBundle;
3967
4040
  const result = await withAudit(auditService, name, args, async () => {
3968
4041
  const draft = await createReplyDraftBundle.draftStore.createDraft({
3969
4042
  mode: "reply",
@@ -4009,7 +4082,9 @@ export function createServer(config, options = {}) {
4009
4082
  ]);
4010
4083
  }
4011
4084
  case "create_forward_draft": {
4012
- const detail = await imapService.getEmailById(requireString(args, "emailId"));
4085
+ // Same emailId account resolution as create_reply_draft — see its comment.
4086
+ const { bundle: createForwardReadBundle, rest: createForwardEmailIdRest } = resolveAccountForEmailId(requireString(args, "emailId"));
4087
+ const detail = await createForwardReadBundle.imapService.getEmailById(createForwardEmailIdRest);
4013
4088
  const to = parseEmails(requireString(args, "to"));
4014
4089
  const cc = parseEmails(optionalString(args, "cc"));
4015
4090
  const bcc = parseEmails(optionalString(args, "bcc"));
@@ -4027,13 +4102,15 @@ export function createServer(config, options = {}) {
4027
4102
  const originalAttachments = includeAttachments
4028
4103
  ? await Promise.all(detail.attachments
4029
4104
  .filter((a) => a.id)
4030
- .map((a) => imapService.getAttachmentForForward(detail.id, a.id)))
4105
+ .map((a) => createForwardReadBundle.imapService.getAttachmentForForward(detail.id, a.id)))
4031
4106
  : [];
4032
4107
  const attachments = [...originalAttachments, ...callerAttachments];
4033
- // Same from-based draftStore routing as create_draftsee its comment.
4108
+ // Same from-based draftStore routing as create_reply_draftdefaults to
4109
+ // the ORIGINAL MESSAGE's own account, not always primary, when `from`
4110
+ // isn't given.
4034
4111
  const createForwardDraftBundle = optionalString(args, "from") !== undefined
4035
- ? accountManager.byAddress(optionalString(args, "from")) ?? primaryBundle
4036
- : primaryBundle;
4112
+ ? accountManager.byAddress(optionalString(args, "from")) ?? createForwardReadBundle
4113
+ : createForwardReadBundle;
4037
4114
  const result = await withAudit(auditService, name, args, async () => {
4038
4115
  const draft = await createForwardDraftBundle.draftStore.createDraft({
4039
4116
  mode: "forward",
@@ -6335,10 +6412,15 @@ export function createServer(config, options = {}) {
6335
6412
  if (to.length === 0) {
6336
6413
  throw new McpError(ErrorCode.InvalidParams, "Unable to infer reply recipient.");
6337
6414
  }
6338
- // Same from-based draftStore routing as create_draftsee its comment.
6415
+ // Same from-based draftStore routing as create_reply_draftdefaults to
6416
+ // the THREAD's own account (threadReplyBundle, already resolved above from
6417
+ // the threadId's own prefix), not always primary, when `from` isn't given.
6418
+ // Previously always defaulted to primary regardless of which account the
6419
+ // thread actually belonged to — a reply drafted with no explicit `from`
6420
+ // against a non-primary account's thread silently switched accounts.
6339
6421
  const createThreadReplyDraftBundle = optionalString(args, "from") !== undefined
6340
- ? accountManager.byAddress(optionalString(args, "from")) ?? primaryBundle
6341
- : primaryBundle;
6422
+ ? accountManager.byAddress(optionalString(args, "from")) ?? threadReplyBundle
6423
+ : threadReplyBundle;
6342
6424
  const result = await withAudit(auditService, name, args, async () => {
6343
6425
  const draft = await createThreadReplyDraftBundle.draftStore.createDraft({
6344
6426
  mode: "reply",