@bobfrankston/mailx-types 0.1.45 → 0.1.47

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/mailx-api.d.ts CHANGED
@@ -78,10 +78,26 @@ export interface MailxApi {
78
78
  moveFolderToTrash(accountId: string, folderId: number): Promise<void>;
79
79
  emptyFolder(accountId: string, folderId: number): Promise<void>;
80
80
  sendMessage(msg: any): Promise<void>;
81
- saveDraft(accountId: string, subject: string, bodyHtml: string, bodyText: string, to?: string, cc?: string, previousDraftUid?: number, draftId?: string, bcc?: string): Promise<{
81
+ /** Positional here because both service classes implement it that way;
82
+ * the bridge is what turns the client's single params object into these
83
+ * arguments. Keep the tail in sync with jsonrpc's `case "saveDraft"` —
84
+ * this list had already fallen behind by one (`from`), and `attachments`
85
+ * made it two (2026-08-11). A param missing here is a param silently
86
+ * dropped on the way to the daemon. */
87
+ saveDraft(accountId: string, subject: string, bodyHtml: string, bodyText: string, to?: string, cc?: string, previousDraftUid?: number, draftId?: string, bcc?: string, from?: string, attachments?: {
88
+ filename: string;
89
+ mimeType: string;
90
+ dataBase64: string;
91
+ }[]): Promise<{
82
92
  draftUid: number | null;
83
93
  draftId: string;
84
94
  }>;
95
+ /** Open a compose-window attachment in the OS default app. Optional:
96
+ * desktop-only (Android has no staging dir to launch from). */
97
+ openPendingAttachment?(filename: string, mimeType: string, dataBase64: string): Promise<{
98
+ ok: boolean;
99
+ path: string;
100
+ }>;
85
101
  deleteDraft(accountId: string, draftUid: number, draftId?: string): Promise<void>;
86
102
  getOutboxStatus(): any | Promise<any>;
87
103
  listQueuedOutgoing(): any[] | Promise<any[]>;
package/mime-build.js CHANGED
@@ -64,14 +64,18 @@ export function buildMimeMessage(opts) {
64
64
  `MIME-Version: 1.0`,
65
65
  ];
66
66
  const commonHeaders = headers.filter((h) => h !== null);
67
- // ── draft: a single text/html part and nothing else ──
67
+ // ── draft: a single text/html part, plus attachments if there are any ──
68
+ // "Simple" means no alternative and no inline-image extraction — it does
69
+ // NOT mean "drop the user's files". This branch used to `return` before
70
+ // ever reading opts.attachments, so a draft saved with a 65KB image was
71
+ // written body-only and the file was gone on reopen (Bob 2026-08-11).
68
72
  if (opts.simpleHtml) {
69
73
  const body = opts.bodyHtml || opts.bodyText || "";
70
- const raw = [
71
- ...commonHeaders,
72
- `Content-Type: text/html; charset=UTF-8`,
73
- `Content-Transfer-Encoding: quoted-printable`,
74
- ].join("\r\n") + `\r\n\r\n` + encodeQuotedPrintable(body);
74
+ const htmlPart = {
75
+ headers: [`Content-Type: text/html; charset=UTF-8`, `Content-Transfer-Encoding: quoted-printable`],
76
+ body: encodeQuotedPrintable(body),
77
+ };
78
+ const raw = wrapWithAttachments(commonHeaders, htmlPart, opts.attachments || [], newBoundary);
75
79
  return { raw, messageId, inlineImageCount: 0 };
76
80
  }
77
81
  // Pasted images live in the editor HTML as data: URIs — fine in a WebView,
@@ -130,31 +134,32 @@ export function buildMimeMessage(opts) {
130
134
  body: textEncoded,
131
135
  };
132
136
  };
133
- const attachments = opts.attachments || [];
134
- let raw;
135
- if (attachments.length > 0) {
136
- const mixedBoundary = newBoundary();
137
- const inner = makeInner();
138
- const parts = [
139
- `--${mixedBoundary}\r\n` + inner.headers.join("\r\n") + `\r\n\r\n` + inner.body,
140
- ];
141
- for (const att of attachments) {
142
- // A quote or newline in a filename would break out of the header.
143
- const filename = (att.filename || "attachment").replace(/[\r\n"]/g, "_");
144
- const mime = att.mimeType || "application/octet-stream";
145
- parts.push(`--${mixedBoundary}\r\n` +
146
- `Content-Type: ${mime}; name="${filename}"\r\n` +
147
- `Content-Disposition: attachment; filename="${filename}"\r\n` +
148
- `Content-Transfer-Encoding: base64\r\n\r\n` +
149
- `${wrap76(att.dataBase64 || "")}\r\n`);
150
- }
151
- raw = [...commonHeaders, `Content-Type: multipart/mixed; boundary="${mixedBoundary}"`].join("\r\n")
152
- + `\r\n\r\n` + parts.join("") + `--${mixedBoundary}--\r\n`;
137
+ const raw = wrapWithAttachments(commonHeaders, makeInner(), opts.attachments || [], newBoundary);
138
+ return { raw, messageId, inlineImageCount: inlineImages.length };
139
+ }
140
+ /** Emit `inner` as the whole body, or — when there are attachments — as the
141
+ * first part of a multipart/mixed followed by one base64 part per file.
142
+ * Shared by the draft and send paths so a file can never be dropped by one
143
+ * of them alone. */
144
+ function wrapWithAttachments(commonHeaders, inner, attachments, newBoundary) {
145
+ if (attachments.length === 0) {
146
+ return [...commonHeaders, ...inner.headers].join("\r\n") + `\r\n\r\n` + inner.body;
153
147
  }
154
- else {
155
- const inner = makeInner();
156
- raw = [...commonHeaders, ...inner.headers].join("\r\n") + `\r\n\r\n` + inner.body;
148
+ const mixedBoundary = newBoundary();
149
+ const parts = [
150
+ `--${mixedBoundary}\r\n` + inner.headers.join("\r\n") + `\r\n\r\n` + inner.body,
151
+ ];
152
+ for (const att of attachments) {
153
+ // A quote or newline in a filename would break out of the header.
154
+ const filename = (att.filename || "attachment").replace(/[\r\n"]/g, "_");
155
+ const mime = att.mimeType || "application/octet-stream";
156
+ parts.push(`--${mixedBoundary}\r\n` +
157
+ `Content-Type: ${mime}; name="${filename}"\r\n` +
158
+ `Content-Disposition: attachment; filename="${filename}"\r\n` +
159
+ `Content-Transfer-Encoding: base64\r\n\r\n` +
160
+ `${wrap76(att.dataBase64 || "")}\r\n`);
157
161
  }
158
- return { raw, messageId, inlineImageCount: inlineImages.length };
162
+ return [...commonHeaders, `Content-Type: multipart/mixed; boundary="${mixedBoundary}"`].join("\r\n")
163
+ + `\r\n\r\n` + parts.join("") + `--${mixedBoundary}--\r\n`;
159
164
  }
160
165
  //# sourceMappingURL=mime-build.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-types",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",