@zaclinley/dsh-mailharbor 0.13.1 → 0.14.0

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/lib/index.js CHANGED
@@ -128,14 +128,14 @@ function transportOptions(settings, credentials, security = {}) {
128
128
  pool: false
129
129
  };
130
130
  }
131
- function createTransport(settings, credentials, security = {}) {
131
+ function createTransport2(settings, credentials, security = {}) {
132
132
  return nodemailer.createTransport(transportOptions(settings, credentials, security));
133
133
  }
134
134
  function recipientsOf(value) {
135
135
  return value.split(/[,;]/).map((part) => addressOf(part)).filter((address) => isEmail(address));
136
136
  }
137
137
  async function verifySmtp(settings, credentials, security = {}) {
138
- const transport = createTransport(settings, credentials, security);
138
+ const transport = createTransport2(settings, credentials, security);
139
139
  try {
140
140
  await transport.verify();
141
141
  return { ok: true, host: settings.host, port: Number(settings.port) };
@@ -149,7 +149,7 @@ async function sendMail(settings, credentials, account, mail, security = {}) {
149
149
  const to = recipientsOf(mail.to);
150
150
  const cc = recipientsOf(mail.cc ?? "");
151
151
  if (to.length === 0) throw new Error("\u6536\u4EF6\u4EBA\u4E0D\u80FD\u4E3A\u7A7A");
152
- const transport = createTransport(settings, credentials, security);
152
+ const transport = createTransport2(settings, credentials, security);
153
153
  try {
154
154
  const info = await transport.sendMail({
155
155
  from: settings.fromName === "" ? from : { name: settings.fromName, address: from },
@@ -572,6 +572,28 @@ var init_imap = __esm({
572
572
  }
573
573
  });
574
574
  }
575
+ /** Append a message to the account's Drafts mailbox. */
576
+ async appendDraft(raw) {
577
+ const boxes = await this.mailboxes();
578
+ const box = boxes.find((item) => String(item.specialUse ?? "").toLowerCase() === "\\drafts") ?? boxes.find((item) => /drafts|草稿/i.test(item.name)) ?? boxes.find((item) => item.name.toLowerCase() === "drafts");
579
+ const path = box?.path ?? "Drafts";
580
+ return this.run(async (client) => {
581
+ const result = await client.append(path, raw, ["\\Draft"]);
582
+ if (result === false) throw new Error("\u670D\u52A1\u5668\u62D2\u7EDD\u4FDD\u5B58\u8349\u7A3F");
583
+ return { mailbox: path, uid: Number(result.uid) };
584
+ });
585
+ }
586
+ /** Delete one message (used when replacing a mirrored draft). */
587
+ async deleteMessage(mailbox, uid) {
588
+ return this.run(async (client) => {
589
+ const lock = await client.getMailboxLock(mailbox);
590
+ try {
591
+ await client.messageDelete(String(uid), { uid: true });
592
+ } finally {
593
+ lock.release();
594
+ }
595
+ });
596
+ }
575
597
  /** Move one message to another mailbox (server-side). */
576
598
  async move(mailbox, uid, target) {
577
599
  return this.run(async (client) => {
@@ -1006,6 +1028,30 @@ function moveLocalMessage(file, id, mailbox) {
1006
1028
  }
1007
1029
  }
1008
1030
 
1031
+ // src/host/draft-mime.ts
1032
+ import { createTransport } from "nodemailer";
1033
+ async function draftMime(account, draft) {
1034
+ const transport = createTransport({ streamTransport: true, buffer: true, newline: "unix" });
1035
+ const from = account.smtp.fromName === "" ? account.email : `"${account.smtp.fromName}" <${account.email}>`;
1036
+ const info = await transport.sendMail({
1037
+ from,
1038
+ to: draft.to,
1039
+ cc: draft.cc,
1040
+ bcc: draft.bcc,
1041
+ subject: draft.subject,
1042
+ html: draft.html,
1043
+ attachments: draft.attachments.map((file) => ({
1044
+ filename: file.filename,
1045
+ contentType: file.contentType,
1046
+ content: Buffer.from(file.data, "base64")
1047
+ })),
1048
+ ...draft.inReplyTo === void 0 ? {} : { inReplyTo: draft.inReplyTo },
1049
+ ...draft.references === void 0 ? {} : { references: draft.references }
1050
+ });
1051
+ if (Buffer.isBuffer(info.message)) return info.message;
1052
+ return Buffer.from(info.message);
1053
+ }
1054
+
1009
1055
  // src/host/backup.ts
1010
1056
  init_address();
1011
1057
  import { createHash as createHash2 } from "node:crypto";
@@ -2747,7 +2793,7 @@ ${conversation.trim()}
2747
2793
  }
2748
2794
 
2749
2795
  // src/host/version.ts
2750
- var VERSION = "0.13.1";
2796
+ var VERSION = "0.14.0";
2751
2797
 
2752
2798
  // src/host/api.ts
2753
2799
  init_smtp();
@@ -3031,13 +3077,40 @@ function buildRouter(runtime) {
3031
3077
  router.post("/drafts", async ({ url, body }) => {
3032
3078
  const { account } = accountFrom(runtime, url);
3033
3079
  const payload = await body();
3034
- const draft = runtime.drafts(account).save(normalizeDraftInput(payload, account.id));
3035
- return { draft };
3080
+ const store = runtime.drafts(account);
3081
+ const draft = store.save(normalizeDraftInput(payload, account.id));
3082
+ if (payload.remote !== true) return { draft };
3083
+ if (!runtime.hasCredentials(account)) return { draft, syncError: "\u672A\u767B\u5F55\uFF0C\u8349\u7A3F\u4EC5\u4FDD\u5B58\u5728\u672C\u5730" };
3084
+ try {
3085
+ const raw = await draftMime(account, draft);
3086
+ const provider = runtime.provider(account);
3087
+ const appended = await provider.appendDraft(raw);
3088
+ if (draft.remoteUid !== void 0 && draft.remoteMailbox !== void 0) {
3089
+ try {
3090
+ await provider.deleteMessage(draft.remoteMailbox, draft.remoteUid);
3091
+ } catch {
3092
+ }
3093
+ }
3094
+ const updated = store.save({ ...draft, remoteUid: appended.uid, remoteMailbox: appended.mailbox });
3095
+ return { draft: updated };
3096
+ } catch (error) {
3097
+ return {
3098
+ draft,
3099
+ syncError: error instanceof Error ? error.message : String(error)
3100
+ };
3101
+ }
3036
3102
  });
3037
- router.delete("/drafts/:id", ({ url, params }) => {
3103
+ router.delete("/drafts/:id", async ({ url, params }) => {
3038
3104
  const { account } = accountFrom(runtime, url);
3105
+ const draft = runtime.drafts(account).get(params.id ?? "");
3039
3106
  const removed = runtime.drafts(account).remove(params.id ?? "");
3040
3107
  if (!removed) throw new HttpError(404, "\u8349\u7A3F\u4E0D\u5B58\u5728");
3108
+ if (draft?.remoteUid !== void 0 && draft.remoteMailbox !== void 0 && runtime.hasCredentials(account)) {
3109
+ try {
3110
+ await runtime.provider(account).deleteMessage(draft.remoteMailbox, draft.remoteUid);
3111
+ } catch {
3112
+ }
3113
+ }
3041
3114
  return { ok: true };
3042
3115
  });
3043
3116
  router.post("/send", async ({ url, body }) => {
@@ -4685,7 +4758,9 @@ var DraftStore = class {
4685
4758
  data: text(file.data)
4686
4759
  })) : [],
4687
4760
  inReplyTo: text(input.inReplyTo),
4688
- references: text(input.references)
4761
+ references: text(input.references),
4762
+ ...input.remoteUid === void 0 ? existing?.remoteUid === void 0 ? {} : { remoteUid: existing.remoteUid } : { remoteUid: Number(input.remoteUid) },
4763
+ ...input.remoteMailbox === void 0 ? existing?.remoteMailbox === void 0 ? {} : { remoteMailbox: existing.remoteMailbox } : { remoteMailbox: text(input.remoteMailbox) }
4689
4764
  };
4690
4765
  if (existing !== void 0) {
4691
4766
  Object.assign(existing, fields, { updatedAt: now2 });