-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathworker.js
More file actions
115 lines (100 loc) · 3.6 KB
/
Copy pathworker.js
File metadata and controls
115 lines (100 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import PostalMime from "postal-mime";
const MAX_MAILS = 50;
const STORAGE_KEY = "inbox";
// ─── 收信 Handler ────────────────────────────────────────────────
export async function email(message, env) {
const parser = new PostalMime();
const raw = await streamToArrayBuffer(message.raw);
const parsed = await parser.parse(raw);
const mail = {
id: crypto.randomUUID(),
receivedAt: new Date().toISOString(),
from: message.from,
to: message.to,
subject: parsed.subject ?? "(no subject)",
text: parsed.text ?? "",
html: parsed.html ?? "",
// 只保留附件 metadata,不存 base64 內容,避免撞 KV 25MB 限制
attachments: (parsed.attachments ?? []).map((a) => ({
filename: a.filename ?? "unknown",
mimeType: a.mimeType ?? "application/octet-stream",
size: a.content?.byteLength ?? 0,
})),
};
const existing = await loadInbox(env);
const updated = [mail, ...existing].slice(0, MAX_MAILS);
await env.MAIL_KV.put(STORAGE_KEY, JSON.stringify(updated));
}
// ─── HTTP API Handler ─────────────────────────────────────────────
export default {
email,
async fetch(request, env) {
if (request.headers.get("X-Auth-Key") !== env.AUTH_KEY) {
return json({ error: "unauthorized" }, 401);
}
const url = new URL(request.url);
const mails = await loadInbox(env);
// GET /latest → 最新一封完整郵件
if (url.pathname === "/latest") {
return mails.length ? json(mails[0]) : json({ error: "no mail" }, 404);
}
// GET /mails?limit=10 → 最近 N 封列表(不含正文)
if (url.pathname === "/mails" && request.method === "GET") {
const limit = Math.min(
parseInt(url.searchParams.get("limit") ?? "10"),
MAX_MAILS
);
const list = mails
.slice(0, limit)
.map(({ id, receivedAt, from, to, subject, attachments }) => ({
id,
receivedAt,
from,
to,
subject,
attachments,
}));
return json(list);
}
// DELETE /mails → 清空收件匣
if (url.pathname === "/mails" && request.method === "DELETE") {
await env.MAIL_KV.delete(STORAGE_KEY);
return json({ ok: true });
}
// GET /mail/:id → 單封完整內容
const match = url.pathname.match(/^\/mail\/(.+)$/);
if (match) {
const found = mails.find((x) => x.id === match[1]);
return found ? json(found) : json({ error: "not found" }, 404);
}
return json({ error: "unknown route" }, 404);
},
};
// ─── 工具函數 ─────────────────────────────────────────────────────
async function loadInbox(env) {
const raw = await env.MAIL_KV.get(STORAGE_KEY);
return raw ? JSON.parse(raw) : [];
}
function json(data, status = 200) {
return new Response(JSON.stringify(data, null, 2), {
status,
headers: { "Content-Type": "application/json" },
});
}
async function streamToArrayBuffer(stream) {
const reader = stream.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const total = chunks.reduce((n, c) => n + c.byteLength, 0);
const buf = new Uint8Array(total);
let offset = 0;
for (const chunk of chunks) {
buf.set(chunk, offset);
offset += chunk.byteLength;
}
return buf.buffer;
}