@bobfrankston/mailx 1.0.256 → 1.0.260

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.
Files changed (28) hide show
  1. package/bin/mailx.js +112 -0
  2. package/client/.msger-window.json +1 -1
  3. package/client/components/message-viewer.js +82 -7
  4. package/package.json +7 -6
  5. package/packages/mailx-imap/index.d.ts +6 -0
  6. package/packages/mailx-imap/index.js +100 -33
  7. package/packages/mailx-imap/package.json +2 -1
  8. package/packages/mailx-imap/providers/gmail-api.d.ts +5 -40
  9. package/packages/mailx-imap/providers/gmail-api.js +5 -336
  10. package/packages/mailx-imap/providers/types.d.ts +6 -59
  11. package/packages/mailx-imap/providers/types.js +5 -2
  12. package/packages/mailx-service/index.js +16 -2
  13. package/packages/mailx-store-web/android-bootstrap.js +8 -6
  14. package/packages/mailx-store-web/gmail-api-web.d.ts +7 -37
  15. package/packages/mailx-store-web/gmail-api-web.js +7 -298
  16. package/packages/mailx-store-web/imap-web-provider.d.ts +1 -1
  17. package/packages/mailx-store-web/imap-web-provider.js +2 -2
  18. package/packages/mailx-store-web/main-thread-host.d.ts +15 -0
  19. package/packages/mailx-store-web/main-thread-host.js +287 -0
  20. package/packages/mailx-store-web/package.json +2 -1
  21. package/packages/mailx-store-web/provider-types.d.ts +4 -47
  22. package/packages/mailx-store-web/provider-types.js +3 -3
  23. package/packages/mailx-store-web/sync-manager.d.ts +61 -0
  24. package/packages/mailx-store-web/sync-manager.js +422 -0
  25. package/packages/mailx-store-web/worker-entry.d.ts +8 -0
  26. package/packages/mailx-store-web/worker-entry.js +187 -0
  27. package/packages/mailx-store-web/worker-tcp-transport.d.ts +28 -0
  28. package/packages/mailx-store-web/worker-tcp-transport.js +98 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Worker entry point — runs the entire mailx service layer off the main thread.
3
+ *
4
+ * Hosts: wa-sqlite DB, sync manager, providers, service, settings.
5
+ * Main thread only handles UI rendering and proxies TCP/native bridge calls.
6
+ */
7
+ import { initWorkerTcp, handleTcpMessage, WorkerTcpTransport } from "./worker-tcp-transport.js";
8
+ import { WebMailxDB } from "./db.js";
9
+ import { WebMessageStore } from "./web-message-store.js";
10
+ import { WebMailxService } from "./web-service.js";
11
+ import { SyncManager } from "./sync-manager.js";
12
+ import { dispatch } from "./web-jsonrpc.js";
13
+ import { loadAccounts, loadAccountsFromCloud, setGDriveTokenProvider, setGDriveFolderId } from "./web-settings.js";
14
+ // ── Globals ──
15
+ let db;
16
+ let bodyStore;
17
+ let syncManager;
18
+ let service;
19
+ const post = (msg) => self.postMessage(msg);
20
+ // ── Event emission → main thread ──
21
+ function emitEvent(event) {
22
+ post({ type: "event", event });
23
+ }
24
+ function vlog(msg) {
25
+ // Route through main thread for logit (fetch works in workers but keep consistent)
26
+ try {
27
+ fetch(`https://rmf39.aaz.lt/logit/${encodeURIComponent("V/" + msg.substring(0, 800))}?log=mailx-android&silent=true`).catch(() => { });
28
+ }
29
+ catch { /* ignore */ }
30
+ }
31
+ // ── Native bridge proxy ──
32
+ let nativeReqCounter = 0;
33
+ const pendingNative = new Map();
34
+ function nativeRequest(op, args) {
35
+ const reqId = ++nativeReqCounter;
36
+ return new Promise((resolve, reject) => {
37
+ pendingNative.set(reqId, { resolve, reject });
38
+ post({ type: "native", reqId, op, args });
39
+ setTimeout(() => {
40
+ if (pendingNative.has(reqId)) {
41
+ pendingNative.delete(reqId);
42
+ reject(new Error(`Native ${op} timeout`));
43
+ }
44
+ }, 60000);
45
+ });
46
+ }
47
+ function handleNativeResponse(msg) {
48
+ const pending = pendingNative.get(msg.reqId);
49
+ if (pending) {
50
+ pendingNative.delete(msg.reqId);
51
+ if (msg.error)
52
+ pending.reject(new Error(msg.error));
53
+ else
54
+ pending.resolve(msg.result);
55
+ }
56
+ }
57
+ // ── Token provider (proxied through main thread → C# OAuth) ──
58
+ function createTokenProvider(email) {
59
+ return async () => {
60
+ console.log(`[oauth] Refreshing token for ${email}`);
61
+ return nativeRequest("refreshToken", [email]);
62
+ };
63
+ }
64
+ // ── GDrive folder lookup ──
65
+ async function findGDriveMailxFolder(tokenProvider) {
66
+ const token = await tokenProvider();
67
+ const r = await fetch(`https://www.googleapis.com/drive/v3/files?q=name='mailx' and mimeType='application/vnd.google-apps.folder' and trashed=false&fields=files(id,name)`, { headers: { "Authorization": `Bearer ${token}` } });
68
+ if (!r.ok)
69
+ return null;
70
+ const data = await r.json();
71
+ return data.files?.[0]?.id || null;
72
+ }
73
+ // ── Message handler ──
74
+ self.onmessage = async (e) => {
75
+ const msg = e.data;
76
+ if (msg.type === "tcp-response" || msg.type === "tcp-data" ||
77
+ msg.type === "tcp-close" || msg.type === "tcp-error") {
78
+ handleTcpMessage(msg);
79
+ return;
80
+ }
81
+ if (msg.type === "native-response") {
82
+ handleNativeResponse(msg);
83
+ return;
84
+ }
85
+ if (msg.type === "rpc") {
86
+ try {
87
+ const result = await dispatch(service, {
88
+ _action: msg.action,
89
+ _cbid: String(msg.id),
90
+ ...msg.params
91
+ });
92
+ post({ type: "rpc-response", id: msg.id, result: result.result, error: result.error });
93
+ }
94
+ catch (e) {
95
+ post({ type: "rpc-response", id: msg.id, error: e.message });
96
+ }
97
+ return;
98
+ }
99
+ if (msg.type === "init") {
100
+ try {
101
+ await initialize();
102
+ post({ type: "init-complete" });
103
+ }
104
+ catch (e) {
105
+ console.error(`[worker] Init failed: ${e.message}\n${e.stack}`);
106
+ post({ type: "init-error", error: e.message });
107
+ }
108
+ return;
109
+ }
110
+ };
111
+ // ── Initialization ──
112
+ async function initialize() {
113
+ console.log("[worker] Initializing service layer...");
114
+ // Wire up TCP proxy for IMAP/SMTP
115
+ initWorkerTcp(post);
116
+ // Initialize DB (wa-sqlite runs in this Worker thread — no main thread blocking)
117
+ db = new WebMailxDB("mailx");
118
+ await db.waitReady();
119
+ bodyStore = new WebMessageStore();
120
+ // Create sync manager with injected deps
121
+ syncManager = new SyncManager(db, bodyStore, {
122
+ emitEvent,
123
+ vlog,
124
+ createTcpTransport: () => new WorkerTcpTransport(),
125
+ });
126
+ service = new WebMailxService(db, bodyStore, syncManager);
127
+ // Load accounts
128
+ let accounts = await loadAccounts();
129
+ console.log(`[worker] ${accounts.length} account(s) found`);
130
+ let gmailTokenProvider = null;
131
+ for (const account of accounts) {
132
+ if (!account.enabled)
133
+ continue;
134
+ const domain = account.email?.split("@")[1]?.toLowerCase() || "";
135
+ if (domain === "gmail.com" || domain === "googlemail.com") {
136
+ const tp = createTokenProvider(account.email);
137
+ syncManager.setTokenProvider(account.id, tp);
138
+ if (!gmailTokenProvider)
139
+ gmailTokenProvider = tp;
140
+ }
141
+ await syncManager.addAccount(account);
142
+ }
143
+ // GDrive setup — read shared accounts from cloud
144
+ if (gmailTokenProvider) {
145
+ setGDriveTokenProvider(gmailTokenProvider);
146
+ try {
147
+ console.log("[worker] Looking up GDrive mailx folder...");
148
+ const folderId = await findGDriveMailxFolder(gmailTokenProvider);
149
+ if (folderId) {
150
+ setGDriveFolderId(folderId);
151
+ console.log(`[worker] GDrive mailx folder: ${folderId}`);
152
+ // Load accounts from cloud
153
+ const cloudAccounts = await loadAccountsFromCloud();
154
+ if (cloudAccounts && cloudAccounts.length > 0) {
155
+ console.log(`[worker] GDrive returned ${cloudAccounts.length} accounts`);
156
+ for (const account of cloudAccounts) {
157
+ if (!account.enabled)
158
+ continue;
159
+ const domain = account.email?.split("@")[1]?.toLowerCase() || "";
160
+ if (domain === "gmail.com" || domain === "googlemail.com") {
161
+ const tp = createTokenProvider(account.email);
162
+ syncManager.setTokenProvider(account.id, tp);
163
+ if (!gmailTokenProvider)
164
+ gmailTokenProvider = tp;
165
+ }
166
+ await syncManager.addAccount(account);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ catch (e) {
172
+ console.error(`[worker] GDrive setup error: ${e.message}`);
173
+ }
174
+ }
175
+ // Periodic sync (runs in Worker — never blocks UI)
176
+ setInterval(() => {
177
+ console.log("[sync] periodic poll");
178
+ syncManager.syncAll().catch((e) => console.error(`[worker] Periodic sync error: ${e.message}`));
179
+ }, 2 * 60 * 1000);
180
+ // Initial sync after 1 second
181
+ setTimeout(() => {
182
+ syncManager.syncAll().catch((e) => console.error(`[worker] Initial sync error: ${e.message}`));
183
+ }, 1000);
184
+ console.log("[worker] Service layer ready");
185
+ emitEvent({ type: "connected" });
186
+ }
187
+ //# sourceMappingURL=worker-entry.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * WorkerTcpTransport — TcpTransport implementation for Web Workers.
3
+ * Proxies TCP operations to the main thread via postMessage, where the
4
+ * main thread relays them to msgapi.tcp (the C# native bridge).
5
+ *
6
+ * Same interface as BridgeTcpTransport, but works in a Worker context
7
+ * where window/msgapi aren't available.
8
+ */
9
+ /** Call once from worker-entry to wire up the postMessage channel. */
10
+ export declare function initWorkerTcp(post: (msg: any) => void): void;
11
+ /** Route incoming TCP messages from the main thread to the right transport instance. */
12
+ export declare function handleTcpMessage(msg: any): void;
13
+ export declare class WorkerTcpTransport {
14
+ streamId: number | null;
15
+ dataHandler: ((data: string) => void) | null;
16
+ closeHandler: ((hadError: boolean) => void) | null;
17
+ errorHandler: ((err: Error) => void) | null;
18
+ _connected: boolean;
19
+ get connected(): boolean;
20
+ connect(host: string, port: number, tls: boolean, _servername?: string): Promise<void>;
21
+ upgradeTLS(servername?: string): Promise<void>;
22
+ write(data: string | Uint8Array): Promise<void>;
23
+ onData(handler: (data: string) => void): void;
24
+ onClose(handler: (hadError: boolean) => void): void;
25
+ onError(handler: (err: Error) => void): void;
26
+ close(): void;
27
+ }
28
+ //# sourceMappingURL=worker-tcp-transport.d.ts.map
@@ -0,0 +1,98 @@
1
+ /**
2
+ * WorkerTcpTransport — TcpTransport implementation for Web Workers.
3
+ * Proxies TCP operations to the main thread via postMessage, where the
4
+ * main thread relays them to msgapi.tcp (the C# native bridge).
5
+ *
6
+ * Same interface as BridgeTcpTransport, but works in a Worker context
7
+ * where window/msgapi aren't available.
8
+ */
9
+ /** Function to send messages to the main thread. Set during worker init. */
10
+ let postToMain = () => { throw new Error("Worker TCP transport not initialized"); };
11
+ let reqCounter = 0;
12
+ const pendingRequests = new Map();
13
+ const instances = new Map();
14
+ /** Call once from worker-entry to wire up the postMessage channel. */
15
+ export function initWorkerTcp(post) {
16
+ postToMain = post;
17
+ }
18
+ /** Route incoming TCP messages from the main thread to the right transport instance. */
19
+ export function handleTcpMessage(msg) {
20
+ if (msg.type === "tcp-response") {
21
+ const pending = pendingRequests.get(msg.reqId);
22
+ if (pending) {
23
+ pendingRequests.delete(msg.reqId);
24
+ if (msg.error)
25
+ pending.reject(new Error(msg.error));
26
+ else
27
+ pending.resolve(msg.result);
28
+ }
29
+ }
30
+ else if (msg.type === "tcp-data") {
31
+ instances.get(msg.streamId)?.dataHandler?.(msg.data);
32
+ }
33
+ else if (msg.type === "tcp-close") {
34
+ const t = instances.get(msg.streamId);
35
+ if (t) {
36
+ t._connected = false;
37
+ instances.delete(msg.streamId);
38
+ t.closeHandler?.(msg.hadError);
39
+ }
40
+ }
41
+ else if (msg.type === "tcp-error") {
42
+ const t = instances.get(msg.streamId);
43
+ if (t) {
44
+ t.errorHandler?.(new Error(msg.message));
45
+ }
46
+ }
47
+ }
48
+ function tcpRequest(op, params) {
49
+ const reqId = ++reqCounter;
50
+ return new Promise((resolve, reject) => {
51
+ pendingRequests.set(reqId, { resolve, reject });
52
+ postToMain({ type: "tcp", op, reqId, ...params });
53
+ // 30s timeout
54
+ setTimeout(() => {
55
+ if (pendingRequests.has(reqId)) {
56
+ pendingRequests.delete(reqId);
57
+ reject(new Error(`TCP ${op} timeout`));
58
+ }
59
+ }, 30000);
60
+ });
61
+ }
62
+ export class WorkerTcpTransport {
63
+ streamId = null;
64
+ dataHandler = null;
65
+ closeHandler = null;
66
+ errorHandler = null;
67
+ _connected = false;
68
+ get connected() { return this._connected; }
69
+ async connect(host, port, tls, _servername) {
70
+ const streamId = await tcpRequest("connect", { host, port, tls });
71
+ this.streamId = Number(streamId);
72
+ this._connected = true;
73
+ instances.set(this.streamId, this);
74
+ }
75
+ async upgradeTLS(servername) {
76
+ if (this.streamId == null)
77
+ throw new Error("Not connected");
78
+ await tcpRequest("upgradeTLS", { streamId: this.streamId, servername: servername || "" });
79
+ }
80
+ async write(data) {
81
+ if (this.streamId == null)
82
+ throw new Error("Not connected");
83
+ const s = typeof data === "string" ? data : new TextDecoder().decode(data);
84
+ await tcpRequest("write", { streamId: this.streamId, data: s });
85
+ }
86
+ onData(handler) { this.dataHandler = handler; }
87
+ onClose(handler) { this.closeHandler = handler; }
88
+ onError(handler) { this.errorHandler = handler; }
89
+ close() {
90
+ if (this.streamId != null) {
91
+ postToMain({ type: "tcp", op: "close", streamId: this.streamId });
92
+ instances.delete(this.streamId);
93
+ this.streamId = null;
94
+ this._connected = false;
95
+ }
96
+ }
97
+ }
98
+ //# sourceMappingURL=worker-tcp-transport.js.map