@bobfrankston/mailx 1.0.451 → 1.0.453
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/mailx.js.map +1 -0
- package/bin/mailx.ts +1498 -0
- package/bin/postinstall.js.map +1 -0
- package/bin/postinstall.ts +41 -0
- package/bin/tsconfig.json +10 -0
- package/client/.gitattributes +10 -0
- package/client/app.js +51 -2
- package/client/app.js.map +1 -0
- package/client/app.ts +3112 -0
- package/client/components/address-book.js.map +1 -0
- package/client/components/address-book.ts +204 -0
- package/client/components/alarms.js.map +1 -0
- package/client/components/alarms.ts +276 -0
- package/client/components/calendar-sidebar.js.map +1 -0
- package/client/components/calendar-sidebar.ts +474 -0
- package/client/components/calendar.js.map +1 -0
- package/client/components/calendar.ts +211 -0
- package/client/components/context-menu.js.map +1 -0
- package/client/components/context-menu.ts +95 -0
- package/client/components/folder-picker.js.map +1 -0
- package/client/components/folder-picker.ts +127 -0
- package/client/components/folder-tree.js.map +1 -0
- package/client/components/folder-tree.ts +1069 -0
- package/client/components/message-list.js.map +1 -0
- package/client/components/message-list.ts +1129 -0
- package/client/components/message-viewer.js.map +1 -0
- package/client/components/message-viewer.ts +1257 -0
- package/client/components/outbox-view.js.map +1 -0
- package/client/components/outbox-view.ts +102 -0
- package/client/components/tasks.js.map +1 -0
- package/client/components/tasks.ts +234 -0
- package/client/compose/compose.js.map +1 -0
- package/client/compose/compose.ts +1231 -0
- package/client/compose/editor.js.map +1 -0
- package/client/compose/editor.ts +599 -0
- package/client/compose/ghost-text.js.map +1 -0
- package/client/compose/ghost-text.ts +140 -0
- package/client/index.html +1 -0
- package/client/lib/android-bootstrap.js.map +1 -0
- package/client/lib/android-bootstrap.ts +9 -0
- package/client/lib/api-client.js.map +1 -0
- package/client/lib/api-client.ts +439 -0
- package/client/lib/local-service.js.map +1 -0
- package/client/lib/local-service.ts +646 -0
- package/client/lib/local-store.js.map +1 -0
- package/client/lib/local-store.ts +283 -0
- package/client/lib/message-state.js.map +1 -0
- package/client/lib/message-state.ts +140 -0
- package/client/tsconfig.json +19 -0
- package/package.json +18 -16
- package/packages/mailx-api/.gitattributes +10 -0
- package/packages/mailx-api/index.d.ts.map +1 -0
- package/packages/mailx-api/index.js.map +1 -0
- package/packages/mailx-api/index.ts +283 -0
- package/packages/mailx-api/package.json +1 -1
- package/packages/mailx-api/tsconfig.json +9 -0
- package/packages/mailx-compose/.gitattributes +10 -0
- package/packages/mailx-compose/index.d.ts.map +1 -0
- package/packages/mailx-compose/index.js.map +1 -0
- package/packages/mailx-compose/index.ts +85 -0
- package/packages/mailx-compose/tsconfig.json +9 -0
- package/packages/mailx-core/index.d.ts.map +1 -0
- package/packages/mailx-core/index.js.map +1 -0
- package/packages/mailx-core/index.ts +424 -0
- package/packages/mailx-core/ipc.d.ts.map +1 -0
- package/packages/mailx-core/ipc.js.map +1 -0
- package/packages/mailx-core/ipc.ts +62 -0
- package/packages/mailx-core/package.json +1 -1
- package/packages/mailx-core/tsconfig.json +9 -0
- package/packages/mailx-host/.gitattributes +10 -0
- package/packages/mailx-host/index.d.ts.map +1 -0
- package/packages/mailx-host/index.js.map +1 -0
- package/packages/mailx-host/index.ts +38 -0
- package/packages/mailx-host/package.json +10 -2
- package/packages/mailx-host/tsconfig.json +9 -0
- package/packages/mailx-send/.gitattributes +10 -0
- package/packages/mailx-send/cli-queue.d.ts.map +1 -0
- package/packages/mailx-send/cli-queue.js.map +1 -0
- package/packages/mailx-send/cli-queue.ts +62 -0
- package/packages/mailx-send/cli-send.d.ts.map +1 -0
- package/packages/mailx-send/cli-send.js.map +1 -0
- package/packages/mailx-send/cli-send.ts +83 -0
- package/packages/mailx-send/cli.d.ts.map +1 -0
- package/packages/mailx-send/cli.js.map +1 -0
- package/packages/mailx-send/cli.ts +126 -0
- package/packages/mailx-send/index.d.ts.map +1 -0
- package/packages/mailx-send/index.js.map +1 -0
- package/packages/mailx-send/index.ts +333 -0
- package/packages/mailx-send/mailsend/cli.d.ts.map +1 -0
- package/packages/mailx-send/mailsend/cli.js.map +1 -0
- package/packages/mailx-send/mailsend/cli.ts +81 -0
- package/packages/mailx-send/mailsend/index.d.ts.map +1 -0
- package/packages/mailx-send/mailsend/index.js.map +1 -0
- package/packages/mailx-send/mailsend/index.ts +333 -0
- package/packages/mailx-send/mailsend/package-lock.json +65 -0
- package/packages/mailx-send/mailsend/tsconfig.json +21 -0
- package/packages/mailx-send/package-lock.json +65 -0
- package/packages/mailx-send/package.json +1 -1
- package/packages/mailx-send/tsconfig.json +21 -0
- package/packages/mailx-server/.gitattributes +10 -0
- package/packages/mailx-server/index.d.ts.map +1 -0
- package/packages/mailx-server/index.js.map +1 -0
- package/packages/mailx-server/index.ts +429 -0
- package/packages/mailx-server/package.json +1 -1
- package/packages/mailx-server/tsconfig.json +9 -0
- package/packages/mailx-service/google-sync.d.ts.map +1 -0
- package/packages/mailx-service/google-sync.js.map +1 -0
- package/packages/mailx-service/google-sync.ts +238 -0
- package/packages/mailx-service/index.d.ts.map +1 -0
- package/packages/mailx-service/index.js.map +1 -0
- package/packages/mailx-service/index.ts +2461 -0
- package/packages/mailx-service/jsonrpc.d.ts.map +1 -0
- package/packages/mailx-service/jsonrpc.js.map +1 -0
- package/packages/mailx-service/jsonrpc.ts +268 -0
- package/packages/mailx-service/package.json +1 -1
- package/packages/mailx-service/tsconfig.json +9 -0
- package/packages/mailx-settings/.gitattributes +10 -0
- package/packages/mailx-settings/cloud.d.ts.map +1 -0
- package/packages/mailx-settings/cloud.js.map +1 -0
- package/packages/mailx-settings/cloud.ts +388 -0
- package/packages/mailx-settings/index.d.ts.map +1 -0
- package/packages/mailx-settings/index.js.map +1 -0
- package/packages/mailx-settings/index.ts +892 -0
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-settings/tsconfig.json +9 -0
- package/packages/mailx-store/.gitattributes +10 -0
- package/packages/mailx-store/db.d.ts.map +1 -0
- package/packages/mailx-store/db.js.map +1 -0
- package/packages/mailx-store/db.ts +2007 -0
- package/packages/mailx-store/file-store.d.ts.map +1 -0
- package/packages/mailx-store/file-store.js.map +1 -0
- package/packages/mailx-store/file-store.ts +82 -0
- package/packages/mailx-store/index.d.ts.map +1 -0
- package/packages/mailx-store/index.js.map +1 -0
- package/packages/mailx-store/index.ts +7 -0
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store/tsconfig.json +9 -0
- package/packages/mailx-store-web/android-bootstrap.d.ts.map +1 -0
- package/packages/mailx-store-web/android-bootstrap.js.map +1 -0
- package/packages/mailx-store-web/android-bootstrap.ts +1262 -0
- package/packages/mailx-store-web/db.d.ts.map +1 -0
- package/packages/mailx-store-web/db.js.map +1 -0
- package/packages/mailx-store-web/db.ts +756 -0
- package/packages/mailx-store-web/gmail-api-web.d.ts.map +1 -0
- package/packages/mailx-store-web/gmail-api-web.js.map +1 -0
- package/packages/mailx-store-web/gmail-api-web.ts +11 -0
- package/packages/mailx-store-web/imap-web-provider.d.ts.map +1 -0
- package/packages/mailx-store-web/imap-web-provider.js.map +1 -0
- package/packages/mailx-store-web/imap-web-provider.ts +156 -0
- package/packages/mailx-store-web/index.d.ts.map +1 -0
- package/packages/mailx-store-web/index.js.map +1 -0
- package/packages/mailx-store-web/index.ts +10 -0
- package/packages/mailx-store-web/main-thread-host.d.ts.map +1 -0
- package/packages/mailx-store-web/main-thread-host.js.map +1 -0
- package/packages/mailx-store-web/main-thread-host.ts +322 -0
- package/packages/mailx-store-web/package.json +4 -4
- package/packages/mailx-store-web/provider-types.d.ts.map +1 -0
- package/packages/mailx-store-web/provider-types.js.map +1 -0
- package/packages/mailx-store-web/provider-types.ts +7 -0
- package/packages/mailx-store-web/sync-manager.d.ts.map +1 -0
- package/packages/mailx-store-web/sync-manager.js.map +1 -0
- package/packages/mailx-store-web/sync-manager.ts +508 -0
- package/packages/mailx-store-web/tsconfig.json +10 -0
- package/packages/mailx-store-web/web-jsonrpc.d.ts.map +1 -0
- package/packages/mailx-store-web/web-jsonrpc.js.map +1 -0
- package/packages/mailx-store-web/web-jsonrpc.ts +116 -0
- package/packages/mailx-store-web/web-message-store.d.ts.map +1 -0
- package/packages/mailx-store-web/web-message-store.js.map +1 -0
- package/packages/mailx-store-web/web-message-store.ts +97 -0
- package/packages/mailx-store-web/web-service.d.ts.map +1 -0
- package/packages/mailx-store-web/web-service.js.map +1 -0
- package/packages/mailx-store-web/web-service.ts +616 -0
- package/packages/mailx-store-web/web-settings.d.ts.map +1 -0
- package/packages/mailx-store-web/web-settings.js.map +1 -0
- package/packages/mailx-store-web/web-settings.ts +522 -0
- package/packages/mailx-store-web/worker-entry.d.ts.map +1 -0
- package/packages/mailx-store-web/worker-entry.js.map +1 -0
- package/packages/mailx-store-web/worker-entry.ts +215 -0
- package/packages/mailx-store-web/worker-tcp-transport.d.ts.map +1 -0
- package/packages/mailx-store-web/worker-tcp-transport.js.map +1 -0
- package/packages/mailx-store-web/worker-tcp-transport.ts +101 -0
- package/packages/mailx-types/.gitattributes +10 -0
- package/packages/mailx-types/index.d.ts.map +1 -0
- package/packages/mailx-types/index.js.map +1 -0
- package/packages/mailx-types/index.ts +498 -0
- package/packages/mailx-types/package.json +1 -1
- package/packages/mailx-types/tsconfig.json +9 -0
- package/tsconfig.base.json +2 -1
- package/tsconfig.json +9 -0
- package/build-apk.cmd +0 -3
- package/npmg.bat +0 -6
- package/packages/mailx-imap/index.d.ts +0 -442
- package/packages/mailx-imap/index.js +0 -3684
- package/packages/mailx-imap/package.json +0 -25
- package/packages/mailx-imap/providers/gmail-api.d.ts +0 -8
- package/packages/mailx-imap/providers/gmail-api.js +0 -8
- package/packages/mailx-imap/providers/types.d.ts +0 -9
- package/packages/mailx-imap/providers/types.js +0 -9
- package/packages/mailx-imap/tsconfig.tsbuildinfo +0 -1
- package/rebuild.cmd +0 -23
- package/tdview.cmd +0 -2
- package/temp.ps1 +0 -10
- package/test-smtp-direct.mjs +0 -4
- package/unbash.cmd +0 -55
- package/unwedge.cmd +0 -1
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-compatible MailxService for Android/browser.
|
|
3
|
+
* Replaces @bobfrankston/mailx-service which depends on Node.js (fs, dns, mailparser).
|
|
4
|
+
*
|
|
5
|
+
* Key differences from desktop:
|
|
6
|
+
* - Uses WebMailxDB (wa-sqlite) instead of MailxDB (node:sqlite)
|
|
7
|
+
* - Uses WebMessageStore (IndexedDB) instead of FileMessageStore (filesystem)
|
|
8
|
+
* - Uses postal-mime or manual header parsing instead of mailparser's simpleParser
|
|
9
|
+
* - Settings via IndexedDB + GDrive API instead of filesystem
|
|
10
|
+
* - No dns.resolveMx — provider detection is static (Gmail/Outlook/Yahoo/iCloud)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { WebMailxDB } from "./db.js";
|
|
14
|
+
import type { WebMessageStore } from "./web-message-store.js";
|
|
15
|
+
import type { Folder, AutocompleteSettings } from "@bobfrankston/mailx-types";
|
|
16
|
+
import { sanitizeHtml, encodeQuotedPrintable, htmlToPlainText } from "@bobfrankston/mailx-types";
|
|
17
|
+
import {
|
|
18
|
+
loadSettings, saveSettings, loadAccounts, loadAllowlist, saveAllowlist,
|
|
19
|
+
loadAutocomplete, saveAutocomplete, getStorageInfo
|
|
20
|
+
} from "./web-settings.js";
|
|
21
|
+
|
|
22
|
+
// sanitizeHtml and encodeQuotedPrintable imported from @bobfrankston/mailx-types (shared with desktop)
|
|
23
|
+
|
|
24
|
+
// ── Simple email parser (replaces mailparser for browser) ──
|
|
25
|
+
|
|
26
|
+
interface ParsedMessage {
|
|
27
|
+
html: string;
|
|
28
|
+
text: string;
|
|
29
|
+
headers: Map<string, string>;
|
|
30
|
+
attachments: { filename: string; contentType: string; size: number; contentId: string; content: Uint8Array }[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Parse an RFC 2822 message from raw bytes. Handles basic MIME. */
|
|
34
|
+
function parseEmailSource(raw: string): ParsedMessage {
|
|
35
|
+
const headers = new Map<string, string>();
|
|
36
|
+
const headerEnd = raw.indexOf("\r\n\r\n");
|
|
37
|
+
const headerSection = headerEnd >= 0 ? raw.substring(0, headerEnd) : raw;
|
|
38
|
+
const body = headerEnd >= 0 ? raw.substring(headerEnd + 4) : "";
|
|
39
|
+
|
|
40
|
+
// Parse headers (handle continuations)
|
|
41
|
+
const headerLines = headerSection.split("\r\n");
|
|
42
|
+
let lastKey = "";
|
|
43
|
+
for (const line of headerLines) {
|
|
44
|
+
if (line.startsWith(" ") || line.startsWith("\t")) {
|
|
45
|
+
// Continuation
|
|
46
|
+
if (lastKey) {
|
|
47
|
+
headers.set(lastKey, (headers.get(lastKey) || "") + " " + line.trim());
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
const colon = line.indexOf(":");
|
|
51
|
+
if (colon > 0) {
|
|
52
|
+
lastKey = line.substring(0, colon).toLowerCase().trim();
|
|
53
|
+
headers.set(lastKey, line.substring(colon + 1).trim());
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const contentType = headers.get("content-type") || "text/plain";
|
|
59
|
+
const transferEncoding = (headers.get("content-transfer-encoding") || "").toLowerCase();
|
|
60
|
+
const attachments: ParsedMessage["attachments"] = [];
|
|
61
|
+
|
|
62
|
+
// Check for multipart
|
|
63
|
+
const boundaryMatch = contentType.match(/boundary="?([^";\s]+)"?/i);
|
|
64
|
+
if (boundaryMatch) {
|
|
65
|
+
const boundary = boundaryMatch[1];
|
|
66
|
+
return parseMimeParts(body, boundary, headers);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Single part
|
|
70
|
+
let decoded = decodeBody(body, transferEncoding);
|
|
71
|
+
const isHtml = contentType.includes("text/html");
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
html: isHtml ? decoded : "",
|
|
75
|
+
text: isHtml ? "" : decoded,
|
|
76
|
+
headers,
|
|
77
|
+
attachments,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseMimeParts(body: string, boundary: string, topHeaders: Map<string, string>): ParsedMessage {
|
|
82
|
+
const parts = body.split("--" + boundary);
|
|
83
|
+
let html = "";
|
|
84
|
+
let text = "";
|
|
85
|
+
const attachments: ParsedMessage["attachments"] = [];
|
|
86
|
+
|
|
87
|
+
for (let i = 1; i < parts.length; i++) {
|
|
88
|
+
const part = parts[i];
|
|
89
|
+
if (part.startsWith("--")) break; // End marker
|
|
90
|
+
|
|
91
|
+
const partHeaderEnd = part.indexOf("\r\n\r\n");
|
|
92
|
+
if (partHeaderEnd < 0) continue;
|
|
93
|
+
const partHeaderSection = part.substring(0, partHeaderEnd);
|
|
94
|
+
const partBody = part.substring(partHeaderEnd + 4).replace(/\r?\n$/, "");
|
|
95
|
+
|
|
96
|
+
// Parse part headers
|
|
97
|
+
const partHeaders = new Map<string, string>();
|
|
98
|
+
const partHeaderLines = partHeaderSection.split("\r\n");
|
|
99
|
+
let lastKey = "";
|
|
100
|
+
for (const line of partHeaderLines) {
|
|
101
|
+
if (line.startsWith(" ") || line.startsWith("\t")) {
|
|
102
|
+
if (lastKey) partHeaders.set(lastKey, (partHeaders.get(lastKey) || "") + " " + line.trim());
|
|
103
|
+
} else {
|
|
104
|
+
const colon = line.indexOf(":");
|
|
105
|
+
if (colon > 0) {
|
|
106
|
+
lastKey = line.substring(0, colon).toLowerCase().trim();
|
|
107
|
+
partHeaders.set(lastKey, line.substring(colon + 1).trim());
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const partType = partHeaders.get("content-type") || "text/plain";
|
|
113
|
+
const partEncoding = (partHeaders.get("content-transfer-encoding") || "").toLowerCase();
|
|
114
|
+
const disposition = partHeaders.get("content-disposition") || "";
|
|
115
|
+
|
|
116
|
+
// Nested multipart
|
|
117
|
+
const nestedBoundary = partType.match(/boundary="?([^";\s]+)"?/i);
|
|
118
|
+
if (nestedBoundary) {
|
|
119
|
+
const nested = parseMimeParts(partBody, nestedBoundary[1], topHeaders);
|
|
120
|
+
if (!html && nested.html) html = nested.html;
|
|
121
|
+
if (!text && nested.text) text = nested.text;
|
|
122
|
+
attachments.push(...nested.attachments);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (disposition.includes("attachment") || (partType.includes("application/") && !partType.includes("text/"))) {
|
|
127
|
+
const filenameMatch = disposition.match(/filename="?([^";\r\n]+)"?/i) || partType.match(/name="?([^";\r\n]+)"?/i);
|
|
128
|
+
const decoded = decodeBody(partBody, partEncoding);
|
|
129
|
+
attachments.push({
|
|
130
|
+
filename: filenameMatch?.[1]?.trim() || `attachment-${attachments.length}`,
|
|
131
|
+
contentType: partType.split(";")[0].trim(),
|
|
132
|
+
size: decoded.length,
|
|
133
|
+
contentId: (partHeaders.get("content-id") || "").replace(/[<>]/g, ""),
|
|
134
|
+
content: new TextEncoder().encode(decoded),
|
|
135
|
+
});
|
|
136
|
+
} else if (partType.includes("text/html")) {
|
|
137
|
+
const charsetMatch = partType.match(/charset="?([^";\s]+)"?/i);
|
|
138
|
+
html = decodeBody(partBody, partEncoding, charsetMatch?.[1] || "utf-8");
|
|
139
|
+
} else if (partType.includes("text/plain")) {
|
|
140
|
+
const charsetMatch = partType.match(/charset="?([^";\s]+)"?/i);
|
|
141
|
+
text = decodeBody(partBody, partEncoding, charsetMatch?.[1] || "utf-8");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { html, text, headers: topHeaders, attachments };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function decodeBody(body: string, encoding: string, charset: string = "utf-8"): string {
|
|
149
|
+
// Step 1: decode the transfer encoding to a byte array
|
|
150
|
+
let bytes: Uint8Array;
|
|
151
|
+
if (encoding === "base64") {
|
|
152
|
+
try {
|
|
153
|
+
const binary = atob(body.replace(/\s/g, ""));
|
|
154
|
+
bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
|
|
155
|
+
} catch {
|
|
156
|
+
return body;
|
|
157
|
+
}
|
|
158
|
+
} else if (encoding === "quoted-printable") {
|
|
159
|
+
// Decode QP into bytes (NOT into a string — multi-byte UTF-8 must stay as bytes)
|
|
160
|
+
const cleaned = body.replace(/=\r?\n/g, "");
|
|
161
|
+
const out: number[] = [];
|
|
162
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
163
|
+
const c = cleaned[i];
|
|
164
|
+
if (c === "=" && i + 2 < cleaned.length && /[0-9A-Fa-f]{2}/.test(cleaned.substr(i + 1, 2))) {
|
|
165
|
+
out.push(parseInt(cleaned.substr(i + 1, 2), 16));
|
|
166
|
+
i += 2;
|
|
167
|
+
} else {
|
|
168
|
+
// Existing character — encode as its byte (assumes ASCII for QP source)
|
|
169
|
+
out.push(c.charCodeAt(0) & 0xff);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
bytes = new Uint8Array(out);
|
|
173
|
+
} else if (encoding === "7bit" || encoding === "8bit" || encoding === "" || encoding === "binary") {
|
|
174
|
+
// No transfer encoding — body is already a string of single-byte chars
|
|
175
|
+
bytes = Uint8Array.from(body, c => c.charCodeAt(0) & 0xff);
|
|
176
|
+
} else {
|
|
177
|
+
// Unknown encoding — return as-is
|
|
178
|
+
return body;
|
|
179
|
+
}
|
|
180
|
+
// Step 2: decode bytes using the declared charset (default UTF-8)
|
|
181
|
+
try {
|
|
182
|
+
const normalized = charset.toLowerCase().replace("windows-", "windows-").replace("iso-", "iso-");
|
|
183
|
+
return new TextDecoder(normalized).decode(bytes);
|
|
184
|
+
} catch {
|
|
185
|
+
// Unknown charset — fall back to UTF-8 with replacement chars
|
|
186
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Quoted-printable encoding (for compose/send) ──
|
|
191
|
+
|
|
192
|
+
// encodeQuotedPrintable imported from @bobfrankston/mailx-types
|
|
193
|
+
|
|
194
|
+
// ── Types for sync manager ──
|
|
195
|
+
|
|
196
|
+
export interface WebSyncManager {
|
|
197
|
+
syncAll(): Promise<void>;
|
|
198
|
+
syncFolders(accountId: string): Promise<Folder[]>;
|
|
199
|
+
syncFolder(accountId: string, folderId: number): Promise<void>;
|
|
200
|
+
fetchMessageBody(accountId: string, folderId: number, uid: number): Promise<Uint8Array | null>;
|
|
201
|
+
updateFlagsLocal(accountId: string, uid: number, folderId: number, flags: string[]): Promise<void>;
|
|
202
|
+
trashMessage(accountId: string, folderId: number, uid: number): Promise<void>;
|
|
203
|
+
trashMessages(accountId: string, messages: { uid: number; folderId: number }[]): Promise<void>;
|
|
204
|
+
moveMessage(accountId: string, uid: number, folderId: number, targetFolderId: number): Promise<void>;
|
|
205
|
+
moveMessages(accountId: string, messages: { uid: number; folderId: number }[], targetFolderId: number): Promise<void>;
|
|
206
|
+
moveMessageCrossAccount(accountId: string, uid: number, folderId: number, targetAccountId: string, targetFolderId: number): Promise<void>;
|
|
207
|
+
undeleteMessage(accountId: string, uid: number, folderId: number): Promise<void>;
|
|
208
|
+
queueOutgoingLocal(accountId: string, rawMessage: string): void | Promise<void>;
|
|
209
|
+
saveDraft(accountId: string, raw: string, previousDraftUid?: number, draftId?: string): Promise<number | null>;
|
|
210
|
+
deleteDraft(accountId: string, draftUid: number): Promise<void>;
|
|
211
|
+
reauthenticate(accountId: string): Promise<boolean>;
|
|
212
|
+
searchOnServer(accountId: string, folderPath: string, criteria: any): Promise<number[]>;
|
|
213
|
+
syncAllContacts(): Promise<void>;
|
|
214
|
+
addAccount(account: any): Promise<void>;
|
|
215
|
+
on(event: string, handler: (...args: any[]) => void): void;
|
|
216
|
+
emit(event: string, ...args: any[]): void;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── Service ──
|
|
220
|
+
|
|
221
|
+
export class WebMailxService {
|
|
222
|
+
constructor(
|
|
223
|
+
private db: WebMailxDB,
|
|
224
|
+
private bodyStore: WebMessageStore,
|
|
225
|
+
private syncManager: WebSyncManager,
|
|
226
|
+
) {}
|
|
227
|
+
|
|
228
|
+
// ── Accounts ──
|
|
229
|
+
|
|
230
|
+
async getAccounts(): Promise<any[]> {
|
|
231
|
+
const dbAccounts = this.db.getAccounts();
|
|
232
|
+
const settings = await loadSettings();
|
|
233
|
+
const ordered: any[] = [];
|
|
234
|
+
for (const cfg of settings.accounts) {
|
|
235
|
+
const a = dbAccounts.find(d => d.id === cfg.id);
|
|
236
|
+
if (a) ordered.push({ ...a, label: cfg.label, defaultSend: cfg.defaultSend || false });
|
|
237
|
+
}
|
|
238
|
+
for (const a of dbAccounts) {
|
|
239
|
+
if (!ordered.find((o: any) => o.id === a.id)) ordered.push(a);
|
|
240
|
+
}
|
|
241
|
+
return ordered;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── Folders ──
|
|
245
|
+
|
|
246
|
+
getFolders(accountId: string): Folder[] {
|
|
247
|
+
return this.db.getFolders(accountId);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Messages ──
|
|
251
|
+
|
|
252
|
+
getUnifiedInbox(page = 1, pageSize = 50): any {
|
|
253
|
+
return this.db.getUnifiedInbox(page, pageSize);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
getMessages(accountId: string, folderId: number, page = 1, pageSize = 50, sort = "date", sortDir = "desc", search?: string): any {
|
|
257
|
+
return this.db.getMessages({ accountId, folderId, page, pageSize, sort: sort as any, sortDir: sortDir as any, search });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async getMessage(accountId: string, uid: number, allowRemote = false, folderId?: number): Promise<any> {
|
|
261
|
+
const envelope = this.db.getMessageByUid(accountId, uid, folderId);
|
|
262
|
+
if (!envelope) throw new Error("Message not found");
|
|
263
|
+
|
|
264
|
+
let bodyHtml = "";
|
|
265
|
+
let bodyText = "";
|
|
266
|
+
let hasRemoteContent = false;
|
|
267
|
+
let attachments: { id: number; filename: string; mimeType: string; size: number; contentId: string }[] = [];
|
|
268
|
+
|
|
269
|
+
let raw: Uint8Array | null = null;
|
|
270
|
+
try {
|
|
271
|
+
raw = await this.syncManager.fetchMessageBody(accountId, envelope.folderId, envelope.uid);
|
|
272
|
+
} catch (fetchErr: any) {
|
|
273
|
+
// Mirror the desktop service: surface as structured bodyError
|
|
274
|
+
// so the viewer shows its dedicated error banner instead of
|
|
275
|
+
// rendering the message text verbatim in the body area.
|
|
276
|
+
const rawErr = fetchErr.message || "fetch failed";
|
|
277
|
+
const isTransient = /connection|Too many|UNAVAILABLE|rate|429|5\d\d|timeout|ENOTFOUND|ECONNRESET|ETIMEDOUT/i.test(rawErr);
|
|
278
|
+
return {
|
|
279
|
+
...envelope, bodyHtml: "", bodyText: "",
|
|
280
|
+
bodyError: rawErr, bodyErrorTransient: isTransient,
|
|
281
|
+
hasRemoteContent: false, remoteAllowed: false, attachments: [], deliveredTo: "", returnPath: "", listUnsubscribe: ""
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!raw) {
|
|
286
|
+
return {
|
|
287
|
+
...envelope, bodyHtml: "", bodyText: "",
|
|
288
|
+
bodyError: "Message body not cached locally and the server fetch returned nothing.",
|
|
289
|
+
bodyErrorTransient: true,
|
|
290
|
+
hasRemoteContent: false, remoteAllowed: false, attachments: [], deliveredTo: "", returnPath: "", listUnsubscribe: ""
|
|
291
|
+
};
|
|
292
|
+
} else {
|
|
293
|
+
const source = new TextDecoder().decode(raw);
|
|
294
|
+
const parsed = parseEmailSource(source);
|
|
295
|
+
bodyHtml = parsed.html || "";
|
|
296
|
+
bodyText = parsed.text || "";
|
|
297
|
+
attachments = (parsed.attachments || []).map((a, i) => ({
|
|
298
|
+
id: i,
|
|
299
|
+
filename: a.filename || `attachment-${i}`,
|
|
300
|
+
mimeType: a.contentType || "application/octet-stream",
|
|
301
|
+
size: a.size || 0,
|
|
302
|
+
contentId: a.contentId || ""
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Sanitize HTML
|
|
307
|
+
if (bodyHtml && !allowRemote) {
|
|
308
|
+
const allowList = await loadAllowlist();
|
|
309
|
+
const senderAddr = envelope.from?.address || "";
|
|
310
|
+
const senderDomain = senderAddr.split("@")[1] || "";
|
|
311
|
+
const toAddrs = (envelope.to || []).map((a: any) => a.address);
|
|
312
|
+
const isAllowed = allowList.senders.includes(senderAddr) ||
|
|
313
|
+
allowList.domains.includes(senderDomain) ||
|
|
314
|
+
toAddrs.some((a: string) => allowList.recipients?.includes(a));
|
|
315
|
+
|
|
316
|
+
if (isAllowed) {
|
|
317
|
+
allowRemote = true;
|
|
318
|
+
} else {
|
|
319
|
+
const result = sanitizeHtml(bodyHtml);
|
|
320
|
+
bodyHtml = result.html;
|
|
321
|
+
hasRemoteContent = result.hasRemoteContent;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
...envelope, bodyHtml, bodyText, hasRemoteContent, remoteAllowed: allowRemote,
|
|
327
|
+
attachments, deliveredTo: "", returnPath: "", listUnsubscribe: "",
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async updateFlags(accountId: string, uid: number, flags: string[]): Promise<void> {
|
|
332
|
+
const envelope = this.db.getMessageByUid(accountId, uid);
|
|
333
|
+
await this.syncManager.updateFlagsLocal(accountId, uid, envelope?.folderId || 0, flags);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ── Remote content allow-list ──
|
|
337
|
+
|
|
338
|
+
async allowRemoteContent(type: "sender" | "domain" | "recipient", value: string): Promise<void> {
|
|
339
|
+
const list = await loadAllowlist();
|
|
340
|
+
if (type === "sender" && !list.senders.includes(value)) list.senders.push(value);
|
|
341
|
+
else if (type === "domain" && !list.domains.includes(value)) list.domains.push(value);
|
|
342
|
+
else if (type === "recipient") {
|
|
343
|
+
if (!list.recipients) list.recipients = [];
|
|
344
|
+
if (!list.recipients.includes(value)) list.recipients.push(value);
|
|
345
|
+
}
|
|
346
|
+
await saveAllowlist(list);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ── Search ──
|
|
350
|
+
|
|
351
|
+
async search(q: string, page = 1, pageSize = 50, scope = "all", accountId?: string, folderId?: number): Promise<any> {
|
|
352
|
+
if (!q.trim()) return { items: [], total: 0, page, pageSize };
|
|
353
|
+
// On mobile, always use local search (no server-side IMAP search)
|
|
354
|
+
if (scope === "current" && accountId && folderId) {
|
|
355
|
+
return this.db.searchMessages(q, page, pageSize, accountId, folderId);
|
|
356
|
+
}
|
|
357
|
+
return this.db.searchMessages(q, page, pageSize);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ── Sync ──
|
|
361
|
+
|
|
362
|
+
getSyncPending(): { pending: number } {
|
|
363
|
+
return { pending: this.db.getTotalPendingSyncCount() };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async syncAll(): Promise<void> {
|
|
367
|
+
await this.syncManager.syncAll();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async syncAccount(accountId: string): Promise<void> {
|
|
371
|
+
const folders = await this.syncManager.syncFolders(accountId);
|
|
372
|
+
// INBOX-first: await INBOX so the UI re-renders new mail immediately,
|
|
373
|
+
// then fire the rest in the background so labels don't block the
|
|
374
|
+
// list. S57 (Android parity with desktop's local-first rule).
|
|
375
|
+
const inbox = folders.find(f => f.specialUse === "inbox");
|
|
376
|
+
const others = folders.filter(f => f.specialUse !== "inbox");
|
|
377
|
+
if (inbox) {
|
|
378
|
+
try { await this.syncManager.syncFolder(accountId, inbox.id); }
|
|
379
|
+
catch (e: any) { console.error(` Skipping INBOX ${inbox.path}: ${e.message}`); }
|
|
380
|
+
}
|
|
381
|
+
// Background fan-out: don't await; errors log. UI already has INBOX.
|
|
382
|
+
(async () => {
|
|
383
|
+
for (const folder of others) {
|
|
384
|
+
try { await this.syncManager.syncFolder(accountId, folder.id); }
|
|
385
|
+
catch (e: any) { console.error(` Skipping folder ${folder.path}: ${e.message}`); }
|
|
386
|
+
}
|
|
387
|
+
})().catch(() => { /* top-level already logged */ });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async reauthenticate(accountId: string): Promise<boolean> {
|
|
391
|
+
return this.syncManager.reauthenticate(accountId);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ── Send ──
|
|
395
|
+
|
|
396
|
+
async send(msg: any): Promise<void> {
|
|
397
|
+
const settings = await loadSettings();
|
|
398
|
+
const account = settings.accounts.find(a => a.id === msg.from);
|
|
399
|
+
if (!account) throw new Error(`Unknown account: ${msg.from}`);
|
|
400
|
+
|
|
401
|
+
const fromHeader = msg.fromAddress || `${account.name} <${account.email}>`;
|
|
402
|
+
const to = msg.to.map((a: any) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
|
|
403
|
+
const cc = msg.cc?.map((a: any) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
|
|
404
|
+
const bcc = msg.bcc?.map((a: any) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
|
|
405
|
+
// HTML-bodied send gets a multipart/alternative wrapper with a
|
|
406
|
+
// text/plain derived from the HTML — matches desktop's path; spam
|
|
407
|
+
// filters score HTML-only mail harshly without it.
|
|
408
|
+
const hasHtml = !!msg.bodyHtml;
|
|
409
|
+
const htmlBody = msg.bodyHtml || "";
|
|
410
|
+
const textBody = msg.bodyText || (hasHtml ? htmlToPlainText(htmlBody) : "");
|
|
411
|
+
const htmlEncoded = hasHtml ? encodeQuotedPrintable(htmlBody) : "";
|
|
412
|
+
const textEncoded = encodeQuotedPrintable(textBody);
|
|
413
|
+
|
|
414
|
+
const domain = account.email.split("@")[1] || "mailx.local";
|
|
415
|
+
const messageId = `<${Date.now()}.${Math.random().toString(36).slice(2)}@${domain}>`;
|
|
416
|
+
|
|
417
|
+
const envelope = [
|
|
418
|
+
`From: ${fromHeader}`, `To: ${to}`,
|
|
419
|
+
cc ? `Cc: ${cc}` : null, bcc ? `Bcc: ${bcc}` : null,
|
|
420
|
+
`Subject: ${msg.subject}`, `Date: ${new Date().toUTCString()}`,
|
|
421
|
+
`Message-ID: ${messageId}`,
|
|
422
|
+
msg.inReplyTo ? `In-Reply-To: ${msg.inReplyTo}` : null,
|
|
423
|
+
msg.references?.length ? `References: ${msg.references.join(" ")}` : null,
|
|
424
|
+
`MIME-Version: 1.0`,
|
|
425
|
+
].filter(h => h !== null);
|
|
426
|
+
|
|
427
|
+
let rawMessage: string;
|
|
428
|
+
if (hasHtml) {
|
|
429
|
+
const altBoundary = `mailx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
430
|
+
const body =
|
|
431
|
+
`--${altBoundary}\r\n` +
|
|
432
|
+
`Content-Type: text/plain; charset=UTF-8\r\n` +
|
|
433
|
+
`Content-Transfer-Encoding: quoted-printable\r\n\r\n` +
|
|
434
|
+
`${textEncoded}\r\n` +
|
|
435
|
+
`--${altBoundary}\r\n` +
|
|
436
|
+
`Content-Type: text/html; charset=UTF-8\r\n` +
|
|
437
|
+
`Content-Transfer-Encoding: quoted-printable\r\n\r\n` +
|
|
438
|
+
`${htmlEncoded}\r\n` +
|
|
439
|
+
`--${altBoundary}--\r\n`;
|
|
440
|
+
const headers = [
|
|
441
|
+
...envelope,
|
|
442
|
+
`Content-Type: multipart/alternative; boundary="${altBoundary}"`,
|
|
443
|
+
].join("\r\n");
|
|
444
|
+
rawMessage = `${headers}\r\n\r\n${body}`;
|
|
445
|
+
} else {
|
|
446
|
+
const headers = [
|
|
447
|
+
...envelope,
|
|
448
|
+
`Content-Type: text/plain; charset=UTF-8`,
|
|
449
|
+
`Content-Transfer-Encoding: quoted-printable`,
|
|
450
|
+
].join("\r\n");
|
|
451
|
+
rawMessage = `${headers}\r\n\r\n${textEncoded}`;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// queueOutgoingLocal on the Android bridge is async (it flushes
|
|
455
|
+
// sql.js → IndexedDB before returning so a tab-close in the
|
|
456
|
+
// debounce window can't lose the row). On the web-worker
|
|
457
|
+
// SyncManager it's synchronous and returns void; awaiting an
|
|
458
|
+
// undefined value is benign, so this works for both.
|
|
459
|
+
await this.syncManager.queueOutgoingLocal(account.id, rawMessage);
|
|
460
|
+
|
|
461
|
+
for (const addr of msg.to) this.db.recordSentAddress(addr.name, addr.address);
|
|
462
|
+
if (msg.cc) for (const addr of msg.cc) this.db.recordSentAddress(addr.name, addr.address);
|
|
463
|
+
if (msg.bcc) for (const addr of msg.bcc) this.db.recordSentAddress(addr.name, addr.address);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ── Delete / Move ──
|
|
467
|
+
|
|
468
|
+
async deleteMessage(accountId: string, uid: number): Promise<void> {
|
|
469
|
+
const envelope = this.db.getMessageByUid(accountId, uid);
|
|
470
|
+
if (!envelope) throw new Error("Message not found");
|
|
471
|
+
await this.syncManager.trashMessage(accountId, envelope.folderId, envelope.uid);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async deleteMessages(accountId: string, uids: number[]): Promise<void> {
|
|
475
|
+
const messages = uids.map(uid => {
|
|
476
|
+
const env = this.db.getMessageByUid(accountId, uid);
|
|
477
|
+
if (!env) return null;
|
|
478
|
+
return { uid: env.uid, folderId: env.folderId };
|
|
479
|
+
}).filter(m => m !== null);
|
|
480
|
+
await this.syncManager.trashMessages(accountId, messages);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async moveMessage(accountId: string, uid: number, targetFolderId: number, targetAccountId?: string): Promise<void> {
|
|
484
|
+
const envelope = this.db.getMessageByUid(accountId, uid);
|
|
485
|
+
if (!envelope) throw new Error("Message not found");
|
|
486
|
+
if (targetAccountId && targetAccountId !== accountId) {
|
|
487
|
+
await this.syncManager.moveMessageCrossAccount(accountId, envelope.uid, envelope.folderId, targetAccountId, targetFolderId);
|
|
488
|
+
} else {
|
|
489
|
+
await this.syncManager.moveMessage(accountId, envelope.uid, envelope.folderId, targetFolderId);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async moveMessages(accountId: string, uids: number[], targetFolderId: number): Promise<void> {
|
|
494
|
+
const messages = uids.map(uid => {
|
|
495
|
+
const env = this.db.getMessageByUid(accountId, uid);
|
|
496
|
+
if (!env) return null;
|
|
497
|
+
return { uid: env.uid, folderId: env.folderId };
|
|
498
|
+
}).filter(m => m !== null);
|
|
499
|
+
await this.syncManager.moveMessages(accountId, messages, targetFolderId);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async undeleteMessage(accountId: string, uid: number, folderId: number): Promise<void> {
|
|
503
|
+
await this.syncManager.undeleteMessage(accountId, uid, folderId);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ── Drafts ──
|
|
507
|
+
|
|
508
|
+
async saveDraft(accountId: string, subject: string, bodyHtml: string, bodyText: string, to?: string, cc?: string, previousDraftUid?: number, draftId?: string): Promise<{ uid: number | null; draftId: string }> {
|
|
509
|
+
const settings = await loadSettings();
|
|
510
|
+
const account = settings.accounts.find(a => a.id === accountId);
|
|
511
|
+
if (!account) throw new Error(`Unknown account: ${accountId}`);
|
|
512
|
+
|
|
513
|
+
const id = draftId || `mailx-draft-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
514
|
+
const body = bodyHtml || bodyText || "";
|
|
515
|
+
const bodyEncoded = encodeQuotedPrintable(body);
|
|
516
|
+
|
|
517
|
+
const headers = [
|
|
518
|
+
`From: ${account.name} <${account.email}>`,
|
|
519
|
+
to ? `To: ${to}` : null, cc ? `Cc: ${cc}` : null,
|
|
520
|
+
`Subject: ${subject || "(no subject)"}`, `Date: ${new Date().toUTCString()}`,
|
|
521
|
+
`X-Mailx-Draft-ID: ${id}`,
|
|
522
|
+
`MIME-Version: 1.0`, `Content-Type: text/html; charset=UTF-8`, `Content-Transfer-Encoding: quoted-printable`,
|
|
523
|
+
].filter(h => h !== null).join("\r\n");
|
|
524
|
+
const raw = `${headers}\r\n\r\n${bodyEncoded}`;
|
|
525
|
+
const uid = await this.syncManager.saveDraft(accountId, raw, previousDraftUid, id);
|
|
526
|
+
return { uid, draftId: id };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async deleteDraft(accountId: string, draftUid: number): Promise<void> {
|
|
530
|
+
await this.syncManager.deleteDraft(accountId, draftUid);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// ── Contacts ──
|
|
534
|
+
|
|
535
|
+
searchContacts(query: string): any[] {
|
|
536
|
+
query = (query || "").trim();
|
|
537
|
+
if (query.length < 1) return [];
|
|
538
|
+
return this.db.searchContacts(query);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** Address-book listing — paginated, filterable. Mirrors mailx-service's
|
|
542
|
+
* signature so the same client-side address-book modal works on Android
|
|
543
|
+
* without an "ipc(...).listContacts is not a function" crash. */
|
|
544
|
+
listContacts(query: string, page = 1, pageSize = 100) {
|
|
545
|
+
return this.db.listContacts(query || "", page, pageSize);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/** Manual upsert from the address-book UI. The desktop path queues a
|
|
549
|
+
* Google People sync; Android relies on the desktop pushing changes
|
|
550
|
+
* back, so this is local-only for now. */
|
|
551
|
+
upsertContact(name: string, email: string): { ok: true } {
|
|
552
|
+
this.db.upsertContact(name || "", email);
|
|
553
|
+
return { ok: true };
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
deleteContact(email: string): { ok: true } {
|
|
557
|
+
this.db.deleteContactLocal(email);
|
|
558
|
+
return { ok: true };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
addContact(name: string, email: string): boolean {
|
|
562
|
+
if (!email || !/^[^\s<>@]+@[^\s<>@]+\.[^\s<>@]+$/.test(email)) return false;
|
|
563
|
+
this.db.recordSentAddress(name || "", email);
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** Q49 heuristic mirror: true if the user has ever sent a message to
|
|
568
|
+
* `recipientEmail` that had a non-empty Cc field. Compose uses this to
|
|
569
|
+
* decide whether to auto-expand the Cc row on reply. */
|
|
570
|
+
hasCcHistoryTo(recipientEmail: string): boolean {
|
|
571
|
+
return (this.db as any).hasCcHistoryTo?.(recipientEmail) ?? false;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// ── Settings ──
|
|
575
|
+
|
|
576
|
+
async getSettings(): Promise<any> {
|
|
577
|
+
return loadSettings();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async saveSettingsData(settings: any): Promise<void> {
|
|
581
|
+
await saveSettings(settings);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
getStorageInfo(): { provider: string; mode: string } {
|
|
585
|
+
return getStorageInfo();
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ── Folder management (limited on mobile — read-only) ──
|
|
589
|
+
|
|
590
|
+
markFolderRead(folderId: number): void {
|
|
591
|
+
this.db.markFolderRead(folderId);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ── Autocomplete ──
|
|
595
|
+
|
|
596
|
+
async getAutocompleteSettings(): Promise<AutocompleteSettings> {
|
|
597
|
+
return loadAutocomplete();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async saveAutocompleteSettings(settings: AutocompleteSettings): Promise<void> {
|
|
601
|
+
await saveAutocomplete(settings);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async autocomplete(_req: any): Promise<{ suggestion: string }> {
|
|
605
|
+
// Autocomplete disabled on mobile by default
|
|
606
|
+
return { suggestion: "" };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// ── Reset ──
|
|
610
|
+
|
|
611
|
+
async resetStore(): Promise<void> {
|
|
612
|
+
await this.db.resetStore();
|
|
613
|
+
await this.bodyStore.clear();
|
|
614
|
+
console.log("[service] Store reset complete");
|
|
615
|
+
}
|
|
616
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-settings.d.ts","sourceRoot":"","sources":["web-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AA2DpG,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAE5E;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAExD;AAmKD,QAAA,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;CAuBxB,CAAC;AAEF,QAAA,MAAM,iBAAiB;aACJ,MAAM,EAAE;aACR,MAAM,EAAE;gBACL,MAAM,EAAE;CAC7B,CAAC;AAqDF,8DAA8D;AAC9D,wBAAsB,YAAY,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAuB7D;AAED,gEAAgE;AAChE,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAWtE;AAED,4CAA4C;AAC5C,wBAAsB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI3E;AAED,uBAAuB;AACvB,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,mBAAmB,CAAC,CA0B3E;AAED,uBAAuB;AACvB,wBAAsB,eAAe,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/D;AAED,2DAA2D;AAC3D,wBAAsB,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC,CAa3D;AAED,yBAAyB;AACzB,wBAAsB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAGzE;AAED,qBAAqB;AACrB,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,iBAAiB,CAAC,CAWvE;AAED,qBAAqB;AACrB,wBAAsB,aAAa,CAAC,IAAI,EAAE,OAAO,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF;AAED,iCAAiC;AACjC,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAGtE;AAED,iCAAiC;AACjC,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpF;AAED,+CAA+C;AAC/C,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAGtD;AAED,2BAA2B;AAC3B,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAGpD;AAED,uBAAuB;AACvB,wBAAgB,cAAc,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEnE;AAED,yDAAyD;AACzD,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAInD;AAMD,qEAAqE;AACrE,wBAAgB,WAAW,IAAI,MAAM,CAOpC;AAED,8EAA8E;AAC9E,wBAAsB,eAAe,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/D;AAED,oCAAoC;AACpC,wBAAsB,eAAe,IAAI,OAAO,CAAC,GAAG,CAAC,CAapD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-settings.js","sourceRoot":"","sources":["web-settings.ts"],"names":[],"mappings":"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"}
|