@bobfrankston/rmfmail 1.1.4 → 1.1.5
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/bin/build-bundles.mjs +14 -4
- package/bin/mailx.js +8 -3
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +8 -3
- package/client/android-bootstrap.bundle.js +2151 -2
- package/client/android-bootstrap.bundle.js.map +4 -4
- package/package.json +1 -1
- package/packages/mailx-api/index.d.ts +2 -2
- package/packages/mailx-api/index.d.ts.map +1 -1
- package/packages/mailx-api/index.js +2 -2
- package/packages/mailx-api/index.js.map +1 -1
- package/packages/mailx-api/index.ts +3 -3
- package/packages/mailx-core/index.d.ts.map +1 -1
- package/packages/mailx-core/index.js +3 -2
- package/packages/mailx-core/index.js.map +1 -1
- package/packages/mailx-core/index.ts +3 -2
- package/packages/mailx-imap/index.d.ts +13 -4
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +16 -8
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +15 -7
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-server/index.d.ts.map +1 -1
- package/packages/mailx-server/index.js +4 -3
- package/packages/mailx-server/index.js.map +1 -1
- package/packages/mailx-server/index.ts +4 -3
- package/packages/mailx-service/db-worker.js +3 -4
- package/packages/mailx-service/db-worker.js.map +1 -1
- package/packages/mailx-service/db-worker.ts +5 -6
- package/packages/mailx-service/index.d.ts +20 -3
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +19 -17
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +18 -17
- package/packages/mailx-service/local-store.d.ts +7 -144
- package/packages/mailx-service/local-store.d.ts.map +1 -1
- package/packages/mailx-service/local-store.js +6 -511
- package/packages/mailx-service/local-store.js.map +1 -1
- package/packages/mailx-service/local-store.ts +7 -551
- package/packages/mailx-store/charset.d.ts +15 -0
- package/packages/mailx-store/charset.d.ts.map +1 -0
- package/packages/mailx-store/charset.js +61 -0
- package/packages/mailx-store/charset.js.map +1 -0
- package/packages/mailx-store/charset.ts +45 -0
- package/packages/mailx-store/index.d.ts +2 -0
- package/packages/mailx-store/index.d.ts.map +1 -1
- package/packages/mailx-store/index.js +2 -0
- package/packages/mailx-store/index.js.map +1 -1
- package/packages/mailx-store/index.ts +4 -0
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store/store.d.ts +169 -0
- package/packages/mailx-store/store.d.ts.map +1 -0
- package/packages/mailx-store/store.js +528 -0
- package/packages/mailx-store/store.js.map +1 -0
- package/packages/mailx-store/store.ts +567 -0
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-39436 → node_modules.npmglobalize-stash-11408}/.package-lock.json +0 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store — the nexus. Owns the local database, the .eml file store, the
|
|
3
|
+
* operations API, and the event bus. Single source of truth for everything
|
|
4
|
+
* about a mailx account that lives on this device.
|
|
5
|
+
*
|
|
6
|
+
* UI ──┐ ┌── Sync clients (IMAP, Gmail API, …)
|
|
7
|
+
* │ reads / writes / subscribes │ read pending actions, commit
|
|
8
|
+
* ↓ ↓ server-discovered changes
|
|
9
|
+
* [ Store ]
|
|
10
|
+
*
|
|
11
|
+
* Contract: every method here mutates local-only state. No IMAP, no Gmail,
|
|
12
|
+
* no SMTP, no DNS, no Drive API. Mutations emit on the bus; subscribers
|
|
13
|
+
* (UI in another process, in-process reconciler, etc.) react. Sync clients
|
|
14
|
+
* (mailx-imap, mailx-sync) hold a Store reference and never touch the
|
|
15
|
+
* underlying MailxDB / FileMessageStore directly.
|
|
16
|
+
*
|
|
17
|
+
* This was previously named `Store` and lived in mailx-service. It
|
|
18
|
+
* moved to mailx-store because mailx-imap (and other sync clients) must
|
|
19
|
+
* be able to consume it without depending on mailx-service — that's the
|
|
20
|
+
* "arrow points the right way" property of the architecture.
|
|
21
|
+
*/
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import { parseSerial } from "./parse-serial.js";
|
|
24
|
+
import { storeBus } from "./bus.js";
|
|
25
|
+
import { sanitizeHtml } from "@bobfrankston/mailx-types";
|
|
26
|
+
import { loadSettings, loadAllowlist } from "@bobfrankston/mailx-settings";
|
|
27
|
+
import { sniffAndFixCharset } from "./charset.js";
|
|
28
|
+
/** Parse `List-Unsubscribe` (RFC 2369) and `List-Unsubscribe-Post` (RFC 8058).
|
|
29
|
+
* mailparser only exposes ONE of mail/url even when both are present, so we
|
|
30
|
+
* also scan the raw header text for the full set of angle-bracketed URIs. */
|
|
31
|
+
function parseListUnsubscribe(headers) {
|
|
32
|
+
let mail = "";
|
|
33
|
+
let http = "";
|
|
34
|
+
let oneClick = false;
|
|
35
|
+
const raw = headers.get("list-unsubscribe");
|
|
36
|
+
const rawStr = typeof raw === "string" ? raw : (raw && typeof raw.text === "string" ? raw.text : "");
|
|
37
|
+
if (rawStr) {
|
|
38
|
+
const matches = rawStr.match(/<([^>]+)>/g) || [];
|
|
39
|
+
for (const m of matches) {
|
|
40
|
+
const url = m.slice(1, -1).trim();
|
|
41
|
+
if (!mail && /^mailto:/i.test(url))
|
|
42
|
+
mail = url;
|
|
43
|
+
else if (!http && /^https?:/i.test(url))
|
|
44
|
+
http = url;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!mail && !http) {
|
|
48
|
+
const listHeaders = headers.get("list");
|
|
49
|
+
if (listHeaders?.unsubscribe) {
|
|
50
|
+
const unsub = listHeaders.unsubscribe;
|
|
51
|
+
if (unsub.url)
|
|
52
|
+
http = Array.isArray(unsub.url) ? unsub.url[0] : unsub.url;
|
|
53
|
+
if (unsub.mail)
|
|
54
|
+
mail = `mailto:${Array.isArray(unsub.mail) ? unsub.mail[0] : unsub.mail}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const post = headers.get("list-unsubscribe-post");
|
|
58
|
+
const postStr = typeof post === "string" ? post : (post && typeof post.text === "string" ? post.text : "");
|
|
59
|
+
if (postStr && /one-?click/i.test(postStr))
|
|
60
|
+
oneClick = true;
|
|
61
|
+
return { listUnsubscribeMail: mail, listUnsubscribeHttp: http, listUnsubscribeOneClick: oneClick };
|
|
62
|
+
}
|
|
63
|
+
export class Store {
|
|
64
|
+
db;
|
|
65
|
+
bodyStore;
|
|
66
|
+
bus;
|
|
67
|
+
// Parsed-body LRU. `simpleParser` is the dominant cost of getMessage on
|
|
68
|
+
// a body that's already on disk — 100-500 ms for a typical Gmail message
|
|
69
|
+
// (heavier multipart/alternative + embedded-image swizzling than plainer
|
|
70
|
+
// IMAP mail). Stash the parsed result keyed by .eml path + mtime so
|
|
71
|
+
// repeat views of the same message return in microseconds.
|
|
72
|
+
//
|
|
73
|
+
// Invalidation is implicit: when the underlying .eml is overwritten
|
|
74
|
+
// (rare — bodies are immutable once cached, but reconcile can re-fetch
|
|
75
|
+
// on UIDVALIDITY change), the new mtime won't match the cached key.
|
|
76
|
+
// Capacity 200 entries × ~50 kB parsed envelope = ~10 MB ceiling.
|
|
77
|
+
static PARSED_LRU_CAPACITY = 200;
|
|
78
|
+
parsedLru = new Map();
|
|
79
|
+
parsedLruGet(key) {
|
|
80
|
+
const v = this.parsedLru.get(key);
|
|
81
|
+
if (!v)
|
|
82
|
+
return undefined;
|
|
83
|
+
// Bump recency.
|
|
84
|
+
this.parsedLru.delete(key);
|
|
85
|
+
this.parsedLru.set(key, v);
|
|
86
|
+
return v;
|
|
87
|
+
}
|
|
88
|
+
parsedLruPut(key, msg) {
|
|
89
|
+
this.parsedLru.delete(key);
|
|
90
|
+
this.parsedLru.set(key, msg);
|
|
91
|
+
while (this.parsedLru.size > Store.PARSED_LRU_CAPACITY) {
|
|
92
|
+
const oldest = this.parsedLru.keys().next().value;
|
|
93
|
+
if (oldest === undefined)
|
|
94
|
+
break;
|
|
95
|
+
this.parsedLru.delete(oldest);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Allowlist + settings caches. Both files live on the GDrive-mounted
|
|
99
|
+
// shared dir; their sync `readFileSync` calls in `loadAllowlist()` and
|
|
100
|
+
// `loadSettings()` can stall for seconds per call. getMessage runs on
|
|
101
|
+
// every preview click, so paying that twice each click made .eml
|
|
102
|
+
// display "go to GDrive" even though the body itself is local. Cached
|
|
103
|
+
// here; invalidated externally via invalidateConfigCaches() when the
|
|
104
|
+
// parent service receives a `configChanged` event.
|
|
105
|
+
_allowlistCache = null;
|
|
106
|
+
_settingsCache = null;
|
|
107
|
+
getCachedAllowlist() {
|
|
108
|
+
if (!this._allowlistCache)
|
|
109
|
+
this._allowlistCache = loadAllowlist();
|
|
110
|
+
return this._allowlistCache;
|
|
111
|
+
}
|
|
112
|
+
getCachedSettings() {
|
|
113
|
+
if (!this._settingsCache)
|
|
114
|
+
this._settingsCache = loadSettings();
|
|
115
|
+
return this._settingsCache;
|
|
116
|
+
}
|
|
117
|
+
invalidateConfigCaches() {
|
|
118
|
+
this._allowlistCache = null;
|
|
119
|
+
this._settingsCache = null;
|
|
120
|
+
}
|
|
121
|
+
constructor(
|
|
122
|
+
/** SQLite metadata index. Exposed as a public field — sync clients
|
|
123
|
+
* (mailx-imap, mailx-sync) read/write through it during the
|
|
124
|
+
* refactor. Future state: every external mutation routes through
|
|
125
|
+
* a Store method that emits a bus event; raw `store.db.X()` calls
|
|
126
|
+
* shrink to zero. Today, this is read mostly. */
|
|
127
|
+
db,
|
|
128
|
+
/** .eml file backend. Same migration story as `db` — sync clients
|
|
129
|
+
* read/write directly today, will route through Store methods. */
|
|
130
|
+
bodyStore,
|
|
131
|
+
/** Event bus for Store mutations. Defaults to the process-singleton
|
|
132
|
+
* so cross-package subscribers (bin/mailx.ts forwarder → WebView,
|
|
133
|
+
* in-process reconciler triggers) see all writes without explicit
|
|
134
|
+
* wiring. Tests can pass a fresh StoreBus for isolation. */
|
|
135
|
+
bus = storeBus) {
|
|
136
|
+
this.db = db;
|
|
137
|
+
this.bodyStore = bodyStore;
|
|
138
|
+
this.bus = bus;
|
|
139
|
+
}
|
|
140
|
+
// ── Account list (read-only here; mutations go through MailxService
|
|
141
|
+
// until the cloud-write path is part of the queue too) ──
|
|
142
|
+
/** DB-shape account list (id/name/email/lastSync). The richer
|
|
143
|
+
* AccountConfig (with imap/smtp/etc.) lives in accounts.jsonc and is
|
|
144
|
+
* loaded by mailx-settings, not the DB — that path stays in
|
|
145
|
+
* MailxService until step 3 of the local-first plan. */
|
|
146
|
+
getAccounts() {
|
|
147
|
+
return this.db.getAccounts();
|
|
148
|
+
}
|
|
149
|
+
// ── Folders ──
|
|
150
|
+
getFolders(accountId) {
|
|
151
|
+
return this.db.getFolders(accountId);
|
|
152
|
+
}
|
|
153
|
+
/** Look up a folder by RFC 6154 specialUse tag (`trash`, `drafts`, `sent`,
|
|
154
|
+
* `junk`, etc.) for the given account. Falls back to a case-insensitive
|
|
155
|
+
* path match for legacy rows where specialUse never got tagged.
|
|
156
|
+
* Returns null when the account has no such folder configured. */
|
|
157
|
+
findSpecialFolder(accountId, specialUse) {
|
|
158
|
+
const folders = this.db.getFolders(accountId);
|
|
159
|
+
const f = folders.find(x => x.specialUse === specialUse ||
|
|
160
|
+
x.path.toLowerCase() === specialUse.toLowerCase());
|
|
161
|
+
return f ? { id: f.id, path: f.path } : null;
|
|
162
|
+
}
|
|
163
|
+
// ── Message envelopes ──
|
|
164
|
+
/** Single envelope by (account, uid, folder). Null when the row isn't
|
|
165
|
+
* in the DB — caller decides whether to show "deleted" or queue a
|
|
166
|
+
* server lookup via the reconciler. */
|
|
167
|
+
getMessageEnvelope(accountId, uid, folderId) {
|
|
168
|
+
const env = this.db.getMessageByUid(accountId, uid, folderId);
|
|
169
|
+
return env || null;
|
|
170
|
+
}
|
|
171
|
+
/** Paginated message list for a (account, folder, ...) query. */
|
|
172
|
+
getMessages(query) {
|
|
173
|
+
return this.db.getMessages(query);
|
|
174
|
+
}
|
|
175
|
+
/** All-Inboxes view: union of every account's INBOX, paginated. */
|
|
176
|
+
getUnifiedInbox(page = 1, pageSize = 50) {
|
|
177
|
+
return this.db.getUnifiedInbox(page, pageSize);
|
|
178
|
+
}
|
|
179
|
+
/** Local FTS5 search. Server-scope search is the reconciler's job. */
|
|
180
|
+
searchMessages(query, page = 1, pageSize = 50, accountId, folderId, includeTrashSpam = false) {
|
|
181
|
+
return this.db.searchMessages(query, page, pageSize, accountId, folderId, includeTrashSpam);
|
|
182
|
+
}
|
|
183
|
+
// ── Message body (read-from-disk only) ──
|
|
184
|
+
/** Read a fully-parsed message (envelope + body + attachments) entirely
|
|
185
|
+
* from local state. Returns null when the envelope isn't known.
|
|
186
|
+
* Returns `{ ...envelope, cached: false }` when the envelope is known
|
|
187
|
+
* but the body file isn't on disk — UI shows a placeholder and the
|
|
188
|
+
* reconciler queues the fetch.
|
|
189
|
+
*
|
|
190
|
+
* `allowRemote=true` skips HTML sanitization. Used when the user has
|
|
191
|
+
* explicitly allowed remote content for this sender / domain. */
|
|
192
|
+
async getMessage(accountId, uid, allowRemote, folderId) {
|
|
193
|
+
const envelope = this.db.getMessageByUid(accountId, uid, folderId);
|
|
194
|
+
if (!envelope)
|
|
195
|
+
return null;
|
|
196
|
+
const allowList = this.getCachedAllowlist();
|
|
197
|
+
const senderAddr = (envelope.from?.address || "").toLowerCase();
|
|
198
|
+
const senderDomain = senderAddr.split("@")[1] || "";
|
|
199
|
+
const toAddrs = (envelope.to || []).map((a) => (a.address || "").toLowerCase());
|
|
200
|
+
// Allowlist auto-allow: trusted sender / domain / recipient skips
|
|
201
|
+
// sanitization. Same rule as the legacy service implementation.
|
|
202
|
+
if (!allowRemote) {
|
|
203
|
+
const senders = (allowList.senders || []).map((s) => (s || "").toLowerCase());
|
|
204
|
+
const domains = (allowList.domains || []).map((d) => (d || "").toLowerCase());
|
|
205
|
+
const recipients = (allowList.recipients || []).map((r) => (r || "").toLowerCase());
|
|
206
|
+
if (senders.includes(senderAddr) ||
|
|
207
|
+
domains.includes(senderDomain) ||
|
|
208
|
+
toAddrs.some((a) => recipients.includes(a))) {
|
|
209
|
+
allowRemote = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const isFlagged = !!((allowList.flaggedSenders || []).some((s) => (s || "").toLowerCase() === senderAddr) ||
|
|
213
|
+
(allowList.flaggedDomains || []).some((d) => (d || "").toLowerCase() === senderDomain));
|
|
214
|
+
// Resolve body path: prefer the row's own bodyPath, fall back to
|
|
215
|
+
// the historical lookup. Either may be empty (body never fetched).
|
|
216
|
+
let storedPath = envelope.bodyPath || "";
|
|
217
|
+
if (!storedPath)
|
|
218
|
+
storedPath = this.db.getMessageBodyPath(accountId, uid) || "";
|
|
219
|
+
const empty = {
|
|
220
|
+
...envelope,
|
|
221
|
+
bodyHtml: "", bodyText: "",
|
|
222
|
+
hasRemoteContent: false, remoteAllowed: allowRemote,
|
|
223
|
+
attachments: [],
|
|
224
|
+
cached: false,
|
|
225
|
+
deliveredTo: "", returnPath: "",
|
|
226
|
+
listUnsubscribe: "", listUnsubscribeMail: "", listUnsubscribeHttp: "", listUnsubscribeOneClick: false,
|
|
227
|
+
emlPath: "",
|
|
228
|
+
isFlagged,
|
|
229
|
+
};
|
|
230
|
+
if (!storedPath)
|
|
231
|
+
return empty;
|
|
232
|
+
if (!await this.bodyStore.hasByPath(storedPath))
|
|
233
|
+
return empty;
|
|
234
|
+
// Parse-cache lookup. Key = path + mtime so a re-fetched body
|
|
235
|
+
// (mtime changes) invalidates automatically. mtime stat is cheap
|
|
236
|
+
// (sub-ms) compared to the parse it avoids (~100-500 ms).
|
|
237
|
+
let mtimeMs = 0;
|
|
238
|
+
try {
|
|
239
|
+
mtimeMs = (await fs.promises.stat(storedPath)).mtimeMs;
|
|
240
|
+
}
|
|
241
|
+
catch { /* will fall through and fail at readByPath */ }
|
|
242
|
+
const cacheKey = `${storedPath}|${mtimeMs}`;
|
|
243
|
+
const cached = this.parsedLruGet(cacheKey);
|
|
244
|
+
if (cached) {
|
|
245
|
+
// Allowlist state can change between views even with the same
|
|
246
|
+
// body cached; recompute the volatile fields and overlay.
|
|
247
|
+
return { ...cached, remoteAllowed: allowRemote, isFlagged };
|
|
248
|
+
}
|
|
249
|
+
let raw;
|
|
250
|
+
try {
|
|
251
|
+
raw = await this.bodyStore.readByPath(storedPath);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
// File path is in DB but the file is missing — the prefetch
|
|
255
|
+
// dot is lying. Caller (UI) should mark the row as broken and
|
|
256
|
+
// the reconciler should re-fetch. Don't crash; return as if
|
|
257
|
+
// not cached.
|
|
258
|
+
return empty;
|
|
259
|
+
}
|
|
260
|
+
// Parse + sanitize. Same logic as today's MailxService.getMessage,
|
|
261
|
+
// but executed only when the body is local — never on a fresh
|
|
262
|
+
// server fetch.
|
|
263
|
+
const adjusted = sniffAndFixCharset(raw);
|
|
264
|
+
// simpleParser blocks the event loop while it parses — visible cost
|
|
265
|
+
// on >100 KB messages. Time it so the log makes the cost concrete:
|
|
266
|
+
// when this number is high, that's why other IPC calls (the next
|
|
267
|
+
// message click, the folder-count update tick) stall.
|
|
268
|
+
const _parseT0 = Date.now();
|
|
269
|
+
const parsed = await parseSerial(adjusted);
|
|
270
|
+
const _parseMs = Date.now() - _parseT0;
|
|
271
|
+
if (_parseMs > 50) {
|
|
272
|
+
console.log(` [parse] simpleParser ${_parseMs}ms for ${(raw.length / 1024).toFixed(0)} KB (${storedPath})`);
|
|
273
|
+
}
|
|
274
|
+
let bodyHtml = parsed.html || "";
|
|
275
|
+
const bodyText = parsed.text || "";
|
|
276
|
+
// Backfill FTS body_text now that we've parsed the body. upsertMessage
|
|
277
|
+
// only had the short `preview` snippet at index time; without this
|
|
278
|
+
// backfill, searches miss any word that only appears deeper in the
|
|
279
|
+
// body. Fire-and-forget — failures are non-fatal, never block the
|
|
280
|
+
// user's preview render.
|
|
281
|
+
if (bodyText) {
|
|
282
|
+
try {
|
|
283
|
+
this.db.updateFtsBodyByUid(accountId, envelope.folderId, uid, bodyText);
|
|
284
|
+
}
|
|
285
|
+
catch { /* */ }
|
|
286
|
+
}
|
|
287
|
+
let hasRemoteContent = false;
|
|
288
|
+
// Filter out "spurious" attachments: mailing-list footers and signature
|
|
289
|
+
// blocks frequently arrive as a separate text/plain MIME part with
|
|
290
|
+
// Content-Disposition: inline and no filename. mailparser dutifully
|
|
291
|
+
// surfaces them as attachments. Showing them as attachment chips
|
|
292
|
+
// confuses the user (Bob 2026-05-13: spurious image-shaped chip on
|
|
293
|
+
// a list message that had no real attachment). Rule: if the part is
|
|
294
|
+
// text/* AND has no filename AND is dispositionally inline (or no
|
|
295
|
+
// disposition at all), it's body content, not a real attachment.
|
|
296
|
+
// Keep the original index even for filtered rows so getAttachment's
|
|
297
|
+
// index lookup still works for the surviving ones.
|
|
298
|
+
const attachments = (parsed.attachments || [])
|
|
299
|
+
.map((a, i) => {
|
|
300
|
+
const mime = (a.contentType || "application/octet-stream").toLowerCase();
|
|
301
|
+
const disposition = (a.contentDisposition || "").toLowerCase();
|
|
302
|
+
const isSpuriousTextPart = mime.startsWith("text/")
|
|
303
|
+
&& !a.filename
|
|
304
|
+
&& (disposition === "inline" || disposition === "");
|
|
305
|
+
return { a, i, isSpuriousTextPart };
|
|
306
|
+
})
|
|
307
|
+
.filter(x => !x.isSpuriousTextPart)
|
|
308
|
+
.map(x => ({
|
|
309
|
+
id: x.i,
|
|
310
|
+
filename: x.a.filename || `attachment-${x.i}`,
|
|
311
|
+
mimeType: x.a.contentType || "application/octet-stream",
|
|
312
|
+
size: x.a.size || 0,
|
|
313
|
+
contentId: x.a.contentId || "",
|
|
314
|
+
}));
|
|
315
|
+
if (bodyHtml && !allowRemote) {
|
|
316
|
+
const result = sanitizeHtml(bodyHtml);
|
|
317
|
+
bodyHtml = result.html;
|
|
318
|
+
hasRemoteContent = result.hasRemoteContent;
|
|
319
|
+
}
|
|
320
|
+
// Header extraction — Delivered-To, Return-Path, List-Unsubscribe.
|
|
321
|
+
// mlproc preprocesses Delivered-To server-side, so the inner
|
|
322
|
+
// address is already clean by the time mailx sees it. We take the
|
|
323
|
+
// last entry of the chain (the final-delivery hop). The
|
|
324
|
+
// `relayDomains` filtering layer was retired 2026-05-13: nothing
|
|
325
|
+
// configures it in practice and the conditional made the code
|
|
326
|
+
// harder to reason about than the one-liner that replaces it.
|
|
327
|
+
let deliveredTo = "";
|
|
328
|
+
const rawDelivered = parsed.headers.get("delivered-to");
|
|
329
|
+
if (rawDelivered) {
|
|
330
|
+
const deliveredList = Array.isArray(rawDelivered) ? rawDelivered : [rawDelivered];
|
|
331
|
+
const d = deliveredList[deliveredList.length - 1];
|
|
332
|
+
deliveredTo = typeof d === "string" ? d : d?.text || d?.address || String(d);
|
|
333
|
+
}
|
|
334
|
+
const hdr = (key) => {
|
|
335
|
+
let v = parsed.headers.get(key);
|
|
336
|
+
if (!v)
|
|
337
|
+
return "";
|
|
338
|
+
if (Array.isArray(v))
|
|
339
|
+
v = v[0];
|
|
340
|
+
if (typeof v === "string")
|
|
341
|
+
return v;
|
|
342
|
+
if (typeof v === "object" && v !== null) {
|
|
343
|
+
if ("text" in v)
|
|
344
|
+
return v.text || "";
|
|
345
|
+
if ("value" in v)
|
|
346
|
+
return String(v.value);
|
|
347
|
+
if ("address" in v)
|
|
348
|
+
return v.address || "";
|
|
349
|
+
}
|
|
350
|
+
return String(v);
|
|
351
|
+
};
|
|
352
|
+
const returnPath = hdr("return-path").replace(/[<>]/g, "");
|
|
353
|
+
const { listUnsubscribeMail, listUnsubscribeHttp, listUnsubscribeOneClick } = parseListUnsubscribe(parsed.headers);
|
|
354
|
+
const listUnsubscribe = listUnsubscribeHttp || listUnsubscribeMail;
|
|
355
|
+
const result = {
|
|
356
|
+
...envelope,
|
|
357
|
+
bodyHtml, bodyText,
|
|
358
|
+
hasRemoteContent, remoteAllowed: allowRemote,
|
|
359
|
+
attachments,
|
|
360
|
+
cached: true,
|
|
361
|
+
deliveredTo, returnPath,
|
|
362
|
+
listUnsubscribe, listUnsubscribeMail, listUnsubscribeHttp, listUnsubscribeOneClick,
|
|
363
|
+
// body_path in the DB is stored relative to the body-store
|
|
364
|
+
// basePath; resolve to absolute so the UI's "Source" button
|
|
365
|
+
// can hand the path to an OS file open without re-deriving
|
|
366
|
+
// basePath every time.
|
|
367
|
+
emlPath: this.bodyStore.absolutePath(storedPath),
|
|
368
|
+
isFlagged,
|
|
369
|
+
};
|
|
370
|
+
// Memoize the parsed result for subsequent views of the same UID.
|
|
371
|
+
// `remoteAllowed` and `isFlagged` get re-overlaid on read so a flag
|
|
372
|
+
// toggle or allowlist edit doesn't require a re-parse to take
|
|
373
|
+
// effect (see the parsedLruGet path above).
|
|
374
|
+
//
|
|
375
|
+
// BUT don't poison the cache with an empty parse — a malformed .eml
|
|
376
|
+
// or a fetch that left a stub file gives bodyHtml === "" AND
|
|
377
|
+
// bodyText === ""; caching that means future views serve emptiness
|
|
378
|
+
// until the daemon restarts. Let the next view try again; the
|
|
379
|
+
// parse cost on a 9 kB .eml is ~20 ms, so re-parsing an oddity
|
|
380
|
+
// is cheap.
|
|
381
|
+
const hasContent = (bodyHtml && bodyHtml.length > 0) || (bodyText && bodyText.length > 0);
|
|
382
|
+
if (mtimeMs > 0 && hasContent)
|
|
383
|
+
this.parsedLruPut(cacheKey, result);
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
// ── Calendar / tasks / contacts (read paths) ──
|
|
387
|
+
getCalendarEvents(accountId, fromMs, toMs) {
|
|
388
|
+
return this.db.getCalendarEvents(accountId, fromMs, toMs);
|
|
389
|
+
}
|
|
390
|
+
getTasks(accountId, includeCompleted = false) {
|
|
391
|
+
return this.db.getTasks(accountId, includeCompleted);
|
|
392
|
+
}
|
|
393
|
+
searchContacts(query, limit = 10) {
|
|
394
|
+
return this.db.searchContacts(query, limit);
|
|
395
|
+
}
|
|
396
|
+
listContacts(query, page = 1, pageSize = 100) {
|
|
397
|
+
return this.db.listContacts(query, page, pageSize);
|
|
398
|
+
}
|
|
399
|
+
// ── Write paths (local-only; mirror to server is queued separately) ──
|
|
400
|
+
/** Update a message's flag set. Local DB write completes synchronously;
|
|
401
|
+
* the server-mirror enqueue is the caller's responsibility (typically
|
|
402
|
+
* via SyncQueue.enqueueFlag) so callers that don't want a server push
|
|
403
|
+
* — pure-local UI state like "pin in pane" — can skip it.
|
|
404
|
+
*
|
|
405
|
+
* Publishes:
|
|
406
|
+
* `message:<uuid>` { kind: "flagsChanged" }
|
|
407
|
+
* `folder:<id>` (auto fan-out)
|
|
408
|
+
*/
|
|
409
|
+
updateFlags(accountId, uid, folderId, flags) {
|
|
410
|
+
this.db.updateMessageFlags(accountId, uid, flags);
|
|
411
|
+
const env = this.db.getMessageByUid(accountId, uid, folderId);
|
|
412
|
+
const msgUuid = env?.uuid;
|
|
413
|
+
if (msgUuid) {
|
|
414
|
+
this.bus.publish({
|
|
415
|
+
topic: `message:${msgUuid}`,
|
|
416
|
+
kind: "flagsChanged",
|
|
417
|
+
accountId, folderId, uid, msgUuid, flags,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/** Move a message between folders in the same account. Adds a tombstone
|
|
422
|
+
* on the Message-ID so the next sync doesn't re-import the pre-move row
|
|
423
|
+
* in the source folder before the server-side MOVE completes; tombstone
|
|
424
|
+
* is cleared on terminal IMAP failure (see processSyncActions).
|
|
425
|
+
*
|
|
426
|
+
* Returns true if a local row existed and was moved, false otherwise.
|
|
427
|
+
*
|
|
428
|
+
* Publishes:
|
|
429
|
+
* `message:<uuid>` { kind: "messageMoved", folderId: source, targetFolderId }
|
|
430
|
+
* `folder:<source>` and `folder:<target>` (auto fan-out + explicit count)
|
|
431
|
+
*/
|
|
432
|
+
moveMessage(accountId, uid, fromFolderId, targetFolderId) {
|
|
433
|
+
const env = this.db.getMessageByUid(accountId, uid, fromFolderId);
|
|
434
|
+
if (!env)
|
|
435
|
+
return false;
|
|
436
|
+
if (env.messageId)
|
|
437
|
+
this.db.addTombstone(accountId, env.messageId, env.subject || "");
|
|
438
|
+
const moved = this.db.moveMessageLocal(accountId, uid, fromFolderId, targetFolderId);
|
|
439
|
+
if (!moved)
|
|
440
|
+
return false;
|
|
441
|
+
this.db.recalcFolderCounts(fromFolderId);
|
|
442
|
+
this.db.recalcFolderCounts(targetFolderId);
|
|
443
|
+
const msgUuid = env?.uuid;
|
|
444
|
+
if (msgUuid) {
|
|
445
|
+
this.bus.publish({
|
|
446
|
+
topic: `message:${msgUuid}`,
|
|
447
|
+
kind: "messageMoved",
|
|
448
|
+
accountId, folderId: fromFolderId, targetFolderId, uid, msgUuid,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
// Folder count change isn't tied to a specific message uuid — publish
|
|
452
|
+
// to both folder topics directly. (The fan-out only covers the source
|
|
453
|
+
// via the message event's folderId; target needs its own publish.)
|
|
454
|
+
this.bus.publish({
|
|
455
|
+
topic: `folder:${targetFolderId}`,
|
|
456
|
+
kind: "folderCountsChanged",
|
|
457
|
+
accountId, folderId: targetFolderId,
|
|
458
|
+
});
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
/** Trash a message. If a trash folder is configured and the message is
|
|
462
|
+
* not already in it, this is a move-to-trash. If the message is already
|
|
463
|
+
* in trash (or no trash exists), it's a hard delete + body unlink.
|
|
464
|
+
*
|
|
465
|
+
* Returns "moved-to-trash" or "expunged" so the caller knows whether
|
|
466
|
+
* to enqueue an IMAP MOVE or a DELETE+EXPUNGE on the queue.
|
|
467
|
+
*/
|
|
468
|
+
trashMessage(accountId, uid, folderId, trashFolderId) {
|
|
469
|
+
const env = this.db.getMessageByUid(accountId, uid, folderId);
|
|
470
|
+
if (env?.messageId)
|
|
471
|
+
this.db.addTombstone(accountId, env.messageId, env.subject || "");
|
|
472
|
+
const msgUuid = env?.uuid;
|
|
473
|
+
if (trashFolderId != null && trashFolderId !== folderId) {
|
|
474
|
+
this.db.moveMessageLocal(accountId, uid, folderId, trashFolderId);
|
|
475
|
+
this.db.recalcFolderCounts(folderId);
|
|
476
|
+
this.db.recalcFolderCounts(trashFolderId);
|
|
477
|
+
if (msgUuid) {
|
|
478
|
+
this.bus.publish({
|
|
479
|
+
topic: `message:${msgUuid}`,
|
|
480
|
+
kind: "messageMoved",
|
|
481
|
+
accountId, folderId, targetFolderId: trashFolderId, uid, msgUuid,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
this.bus.publish({
|
|
485
|
+
topic: `folder:${trashFolderId}`,
|
|
486
|
+
kind: "folderCountsChanged",
|
|
487
|
+
accountId, folderId: trashFolderId,
|
|
488
|
+
});
|
|
489
|
+
return "moved-to-trash";
|
|
490
|
+
}
|
|
491
|
+
this.db.deleteMessage(accountId, uid, "user-initiated trash (already in trash → expunge)", "Store.trashMessage");
|
|
492
|
+
this.db.recalcFolderCounts(folderId);
|
|
493
|
+
if (msgUuid) {
|
|
494
|
+
this.bus.publish({
|
|
495
|
+
topic: `message:${msgUuid}`,
|
|
496
|
+
kind: "messageRemoved",
|
|
497
|
+
accountId, folderId, uid, msgUuid,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return "expunged";
|
|
501
|
+
}
|
|
502
|
+
/** Restore a message from trash back to its original folder. Local-only;
|
|
503
|
+
* caller handles the queue (cancel-pending-MOVE vs queue-counter-MOVE).
|
|
504
|
+
* Returns true if a local row was moved. */
|
|
505
|
+
undeleteMessage(accountId, uid, trashFolderId, originalFolderId) {
|
|
506
|
+
const moved = this.db.moveMessageLocal(accountId, uid, trashFolderId, originalFolderId);
|
|
507
|
+
if (!moved)
|
|
508
|
+
return false;
|
|
509
|
+
this.db.recalcFolderCounts(trashFolderId);
|
|
510
|
+
this.db.recalcFolderCounts(originalFolderId);
|
|
511
|
+
const env = this.db.getMessageByUid(accountId, uid, originalFolderId);
|
|
512
|
+
const msgUuid = env?.uuid;
|
|
513
|
+
if (msgUuid) {
|
|
514
|
+
this.bus.publish({
|
|
515
|
+
topic: `message:${msgUuid}`,
|
|
516
|
+
kind: "messageMoved",
|
|
517
|
+
accountId, folderId: trashFolderId, targetFolderId: originalFolderId, uid, msgUuid,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
this.bus.publish({
|
|
521
|
+
topic: `folder:${originalFolderId}`,
|
|
522
|
+
kind: "folderCountsChanged",
|
|
523
|
+
accountId, folderId: originalFolderId,
|
|
524
|
+
});
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["store.ts"],"names":[],"mappings":"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"}
|