@bobfrankston/rmfmail 1.0.599 → 1.0.605
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/TODO.md +7 -0
- package/client/components/alarms.js +45 -24
- package/client/components/alarms.js.map +1 -1
- package/client/components/alarms.ts +40 -18
- package/client/components/message-viewer.js +32 -1
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +29 -1
- package/docs/push-relay.md +141 -0
- package/package.json +35 -34
- package/packages/mailx-imap/index.d.ts +12 -1
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +63 -9
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +64 -9
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +17 -5
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +15 -5
- package/packages/mailx-store/db.d.ts +22 -0
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +67 -0
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +75 -0
- package/packages/mailx-store/package.json +1 -1
|
@@ -352,6 +352,28 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
|
|
|
352
352
|
const bodyEl = document.getElementById("mv-body") as HTMLElement;
|
|
353
353
|
const attEl = document.getElementById("mv-attachments") as HTMLElement;
|
|
354
354
|
|
|
355
|
+
// Same-message re-show guard. The list rebuilds on folderCountsChanged
|
|
356
|
+
// (sync events, IDLE, flag updates) and `restoreSelection()` then calls
|
|
357
|
+
// `focusRow()` → `viewerShow()` on the row whose uid matches. If we
|
|
358
|
+
// re-paint here, `bodyEl.innerHTML = …` later in this function destroys
|
|
359
|
+
// the iframe and resets the user's scroll position to top. Skip that
|
|
360
|
+
// entirely when the message identity hasn't changed and we've got a
|
|
361
|
+
// live iframe to keep. Header refresh is harmless (no scroll inside)
|
|
362
|
+
// and keeps flag/read state visually current. Retry path bypasses the
|
|
363
|
+
// guard so transient body fetches still get their delayed paint.
|
|
364
|
+
if (!isRetry
|
|
365
|
+
&& currentMessage
|
|
366
|
+
&& currentAccountId === accountId
|
|
367
|
+
&& currentMessage.uid === uid
|
|
368
|
+
&& (folderId === undefined || currentMessage.folderId === folderId)
|
|
369
|
+
&& bodyEl.querySelector("iframe")) {
|
|
370
|
+
if (envelope) {
|
|
371
|
+
lastEnvelope = envelope;
|
|
372
|
+
try { renderHeaderFromEnvelope(headerEl, envelope); headerEl.hidden = false; } catch { /* */ }
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
355
377
|
// Clear any "current message" identity at the START of a new render —
|
|
356
378
|
// before a single byte is fetched. Action buttons (Source, Edit Draft,
|
|
357
379
|
// attachments) read from `currentMessage` and must NOT serve the
|
|
@@ -859,8 +881,14 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
|
|
|
859
881
|
: `Unwatched ${type}: ${value}`;
|
|
860
882
|
// Re-render this message so the banner picks up the new
|
|
861
883
|
// flagged state without the user having to reselect.
|
|
884
|
+
// `isRetry=true` bypasses the same-message guard at the
|
|
885
|
+
// top of showMessage — we genuinely want a fresh paint
|
|
886
|
+
// here, even though scroll position will reset (the
|
|
887
|
+
// user just clicked a control, so scroll loss is
|
|
888
|
+
// expected, not surprising).
|
|
862
889
|
if (currentMessage) {
|
|
863
|
-
|
|
890
|
+
invalidateParsedCache(currentAccountId, currentMessage.uid);
|
|
891
|
+
showMessage(currentAccountId, currentMessage.uid, currentMessage.folderId, specialUse, true).catch(() => { /* */ });
|
|
864
892
|
}
|
|
865
893
|
} catch (e: any) {
|
|
866
894
|
const status = document.getElementById("status-sync");
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Push Relay (C127, optional)
|
|
2
|
+
|
|
3
|
+
Status: design only — not yet built. Relevant when polling latency feels too slow OR when battery / quota matters more than now.
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
mailx polls Google Calendar / Gmail / Outlook for changes. Polling is cheap, simple, and works on every network — the right default. But it has two costs:
|
|
8
|
+
|
|
9
|
+
1. **Latency floor.** Even at a 5 s syncToken cadence, "the user just rescheduled this on their phone" takes ~5 s to surface in mailx. For most workflows that's invisible; for "alarm fires for an event that was moved an hour ago" it isn't.
|
|
10
|
+
2. **Battery / quota on mobile.** The Android shell polls the same way the desktop does; on a phone that's measurable wakeups.
|
|
11
|
+
|
|
12
|
+
Push notifications (Google's `events.watch`, Microsoft Graph webhook subscriptions, Gmail's `users.watch`) fix both, but only deliver to a public HTTPS URL. The desktop client doesn't have one. So we need a relay.
|
|
13
|
+
|
|
14
|
+
## Shape
|
|
15
|
+
|
|
16
|
+
A standalone Node service in `MailApps/relayer/`, deployed wherever the operator runs other small services (alerter-style: tiny Express app, one config file, one long-running process). One relay process serves multiple users; per-user state is keyed by user-issued tokens.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌──────────────┐ watch ┌─────────┐ webhook ┌───────────┐ WS/SSE ┌────────┐
|
|
20
|
+
│ Google / MS │ ──register── │ relayer │ ◀──────── │ relayer │ ◀────────│ rmfmail│
|
|
21
|
+
│ Calendar API │ │ /sub │ │ /hook/:id │ │ client │
|
|
22
|
+
└──────────────┘ └─────────┘ └───────────┘ └────────┘
|
|
23
|
+
▲ │ ▲
|
|
24
|
+
│ events.watch( │ per-subscription state: │
|
|
25
|
+
│ address: relayer/hook │ upstream channel id, expiry, owner-token, │
|
|
26
|
+
│ token: per-sub secret │ fan-out endpoints │
|
|
27
|
+
│ ) │ │
|
|
28
|
+
└──── done by relayer on │ │
|
|
29
|
+
client request, with │ │
|
|
30
|
+
client-supplied OAuth │ │
|
|
31
|
+
access token (kept │ │
|
|
32
|
+
short-lived; client │ │
|
|
33
|
+
re-presents on │ │
|
|
34
|
+
renewal) │ │
|
|
35
|
+
▼
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Three roles, separable:
|
|
39
|
+
- **Subscriber API** — clients POST to register what they want watched.
|
|
40
|
+
- **Hook receiver** — public endpoint(s) the upstream services post to.
|
|
41
|
+
- **Fan-out** — per-subscription WS or SSE that holds open client connections and pushes payloads.
|
|
42
|
+
|
|
43
|
+
## API (relayer side)
|
|
44
|
+
|
|
45
|
+
Generic relay shape — caller specifies upstream service + the params that service needs to register a watch. The relay knows enough about each supported upstream to issue the watch-registration call and decode the inbound webhook envelope; it doesn't introspect payload content.
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
POST /subscribe
|
|
49
|
+
body: {
|
|
50
|
+
upstream: "google-calendar" | "google-gmail" | "ms-graph-events" | …
|
|
51
|
+
params: { …upstream-specific watch args, e.g. calendarId, accessToken, expiry preference }
|
|
52
|
+
deliver: "sse" | "ws"
|
|
53
|
+
}
|
|
54
|
+
→ 200 {
|
|
55
|
+
subscriptionId: "<uuid>",
|
|
56
|
+
deliverUrl: "/sub/<uuid>", // open SSE / WS here
|
|
57
|
+
authToken: "<random-secret>", // present on connect
|
|
58
|
+
expiresAt: <ms> // when relay's upstream channel needs renewal
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
POST /subscribe/<id>/renew
|
|
62
|
+
body: { params: { accessToken: "<fresh OAuth>" } }
|
|
63
|
+
→ 200 { expiresAt: <ms> }
|
|
64
|
+
|
|
65
|
+
DELETE /subscribe/<id>
|
|
66
|
+
→ 204 (also calls upstream stop-watch)
|
|
67
|
+
|
|
68
|
+
GET /subscribe/<id>/status
|
|
69
|
+
→ 200 { upstream, expiresAt, lastEventAt, queueDepth }
|
|
70
|
+
|
|
71
|
+
GET /sub/<id> (SSE) — emits `data: {payload}\n\n` per upstream event
|
|
72
|
+
(or WS upgrade) — same payload shape as text frames
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Auth: `Authorization: Bearer <authToken>` on every call after `/subscribe`. The token issued at subscribe time scopes the subscription; one client manages one or more subscriptions, each with its own token.
|
|
76
|
+
|
|
77
|
+
## API (client side)
|
|
78
|
+
|
|
79
|
+
Two settings the user fills in:
|
|
80
|
+
- `pushRelayUrl` — e.g. `https://mail1.example.com:NNNN`
|
|
81
|
+
- `pushRelayCredentials` — issued by the operator at first contact (one-time, opaque blob; relay verifies)
|
|
82
|
+
|
|
83
|
+
When `pushRelayUrl` is non-empty, the client at startup:
|
|
84
|
+
1. Reads its previous subscription IDs from local config.
|
|
85
|
+
2. For each, opens the SSE / WS connection.
|
|
86
|
+
3. If the connection refuses with "unknown subscription" (relay restart, expiry), re-subscribes from scratch with the current OAuth token and stores the new id.
|
|
87
|
+
4. Hooks the inbound event stream into the same `mailxapi.onEvent` channel the daemon already uses, so the alarm subsystem / message-list / folder-tree all consume push events the same way they consume the daemon's IDLE / sync events.
|
|
88
|
+
|
|
89
|
+
The client owns OAuth tokens; the relay receives short-lived access tokens at register / renew time, doesn't store refresh tokens.
|
|
90
|
+
|
|
91
|
+
## Reliability
|
|
92
|
+
|
|
93
|
+
- **Replay queue per subscription** — last N events (in-memory ring, e.g. N=100) so a brief disconnect doesn't lose events. Client passes `Last-Event-ID` (SSE) or a sequence cursor (WS) on reconnect.
|
|
94
|
+
- **Channel renewal** — relay tracks each upstream channel's expiry, schedules renewal at 80% of TTL, calls back to the client (via the WS/SSE itself) for a fresh OAuth token if needed.
|
|
95
|
+
- **Fail-open** — if the relay is unreachable, the client's existing syncToken polling continues. Push is "freshness boost," never load-bearing for correctness.
|
|
96
|
+
|
|
97
|
+
## Generic relay vs per-upstream code
|
|
98
|
+
|
|
99
|
+
The relay needs per-upstream code for:
|
|
100
|
+
- How to register a watch (the API call shape varies).
|
|
101
|
+
- How to decode the inbound webhook envelope (Google sends headers in `X-Goog-*`, Graph sends a JSON body, etc.).
|
|
102
|
+
- How to renew or stop a watch.
|
|
103
|
+
|
|
104
|
+
Per-upstream is a small adapter (~50–100 lines each). Adding "Outlook Tasks" or "iCloud Calendar" later is a new file in `relayer/upstreams/`. The fan-out, subscription store, WS/SSE plumbing, replay buffer — all generic, written once.
|
|
105
|
+
|
|
106
|
+
## Operator footprint
|
|
107
|
+
|
|
108
|
+
- One Node process, one port, one HTTPS cert (or behind a reverse proxy that handles TLS).
|
|
109
|
+
- Storage: SQLite or JSON for the subscription map (small — one row per active subscription).
|
|
110
|
+
- Logs to a file the operator already tails.
|
|
111
|
+
- No queue infrastructure, no message broker, no DB cluster.
|
|
112
|
+
|
|
113
|
+
This is the same operational shape as alerter — drop into `MailApps/`, deploy alongside other small services, treat as one more daemon.
|
|
114
|
+
|
|
115
|
+
## When to build this
|
|
116
|
+
|
|
117
|
+
Only when polling demonstrably isn't fast enough, OR when mobile battery becomes a real concern (push lets the Android shell wake on event instead of polling on a timer). Polling at 5–10 s syncToken cadence is functionally instant for the "user rescheduled an event" case and quota-cheap. Push is a freshness optimization, not a correctness fix.
|
|
118
|
+
|
|
119
|
+
## Alternatives considered
|
|
120
|
+
|
|
121
|
+
- **Cloudflare Workers + Durable Objects** — cleaner per-user-isolation story but requires each user to deploy their own Worker (or one shared operator-run Worker, with a different trust shape). Higher onboarding friction; users without a CF account can't use it. Pro: free tier, edge latency. Con: vendor lock-in, two-click onboarding minimum (account + token).
|
|
122
|
+
- **Cloudflare Tunnel / ngrok to the local daemon** — no relay needed, the daemon exposes its own webhook receiver. Catch: only works while the daemon is running; events while offline are lost (recovered by the next syncToken poll, which already exists). Per-instance tunnel.
|
|
123
|
+
- **Status quo (poll only)** — what mailx does today. Right default; this doc is for when "default isn't enough."
|
|
124
|
+
|
|
125
|
+
## Files (when built)
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
MailApps/relayer/
|
|
129
|
+
package.json
|
|
130
|
+
tsconfig.json
|
|
131
|
+
index.ts — Express server + WS upgrade + SSE
|
|
132
|
+
upstreams/
|
|
133
|
+
google-calendar.ts
|
|
134
|
+
google-gmail.ts
|
|
135
|
+
ms-graph-events.ts
|
|
136
|
+
subs.ts — subscription store (SQLite)
|
|
137
|
+
fanout.ts — per-sub connection pool + replay buffer
|
|
138
|
+
readme.md — operator doc (deploy + config)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Client side: extend `mailx-service` with a `pushRelayClient.ts` that opens / re-opens / re-subscribes; surface its status in the existing sync-status banner.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/rmfmail",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.605",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -28,36 +28,37 @@
|
|
|
28
28
|
"start": "node --watch packages/mailx-server/index.js",
|
|
29
29
|
"start:prod": "node packages/mailx-server/index.js",
|
|
30
30
|
"release": "npmglobalize",
|
|
31
|
-
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
|
32
|
-
"test:fast": "node --experimental-strip-types --test tests/dedup.test.ts tests/dedup-newest.test.ts tests/dupcount.test.ts tests/search.test.ts tests/sync-coalesce.test.ts",
|
|
31
|
+
"test": "node --experimental-strip-types --test tests/*.test.ts tests/dom/*.test.ts",
|
|
32
|
+
"test:fast": "node --experimental-strip-types --test tests/dedup.test.ts tests/dedup-newest.test.ts tests/dupcount.test.ts tests/search.test.ts tests/sync-coalesce.test.ts tests/undelete.test.ts tests/dom/_smoke.test.ts tests/dom/viewer-scroll.test.ts tests/dom/viewer-rerender-guard.test.ts",
|
|
33
33
|
"postinstall": "node bin/postinstall.js"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@bobfrankston/iflow-direct": "^0.1.39",
|
|
37
|
+
"@bobfrankston/mailx-host": "^0.1.11",
|
|
38
|
+
"@bobfrankston/mailx-imap": "^0.1.32",
|
|
39
|
+
"@bobfrankston/mailx-sync": "^0.1.16",
|
|
37
40
|
"@bobfrankston/miscinfo": "^1.0.10",
|
|
38
|
-
"@bobfrankston/oauthsupport": "^1.0.26",
|
|
39
41
|
"@bobfrankston/msger": "^0.1.378",
|
|
40
|
-
"@bobfrankston/
|
|
42
|
+
"@bobfrankston/node-tcp-transport": "^0.1.8",
|
|
43
|
+
"@bobfrankston/oauthsupport": "^1.0.26",
|
|
44
|
+
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
45
|
+
"@bobfrankston/tcp-transport": "^0.1.6",
|
|
41
46
|
"@capacitor/android": "^8.3.0",
|
|
42
47
|
"@capacitor/cli": "^8.3.0",
|
|
43
48
|
"@capacitor/core": "^8.3.0",
|
|
44
49
|
"express": "^4.21.0",
|
|
50
|
+
"html-to-docx": "^1.8.0",
|
|
45
51
|
"jsonc-parser": "^3.3.1",
|
|
46
52
|
"mailparser": "^3.7.2",
|
|
53
|
+
"mammoth": "^1.12.0",
|
|
47
54
|
"nodemailer": "^7.0.0",
|
|
48
55
|
"quill": "^2.0.3",
|
|
49
|
-
"ws": "^8.18.0",
|
|
50
56
|
"sql.js": "^1.14.1",
|
|
51
|
-
"
|
|
52
|
-
"@bobfrankston/node-tcp-transport": "^0.1.8",
|
|
53
|
-
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
54
|
-
"@bobfrankston/mailx-sync": "^0.1.16",
|
|
55
|
-
"@bobfrankston/mailx-imap": "^0.1.32",
|
|
56
|
-
"html-to-docx": "^1.8.0",
|
|
57
|
-
"mammoth": "^1.12.0"
|
|
57
|
+
"ws": "^8.18.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@types/mailparser": "^3.4.6"
|
|
60
|
+
"@types/mailparser": "^3.4.6",
|
|
61
|
+
"jsdom": "^29.1.1"
|
|
61
62
|
},
|
|
62
63
|
"keywords": [
|
|
63
64
|
"email",
|
|
@@ -77,52 +78,52 @@
|
|
|
77
78
|
},
|
|
78
79
|
".dependencies": {
|
|
79
80
|
"@bobfrankston/iflow-direct": "file:../../MailApps/iflow-direct",
|
|
81
|
+
"@bobfrankston/mailx-host": "file:packages/mailx-host",
|
|
82
|
+
"@bobfrankston/mailx-imap": "file:packages/mailx-imap",
|
|
83
|
+
"@bobfrankston/mailx-sync": "file:../../MailApps/mailx-sync",
|
|
80
84
|
"@bobfrankston/miscinfo": "file:../../../projects/npm/miscinfo",
|
|
81
|
-
"@bobfrankston/oauthsupport": "file:../../../projects/oauth/oauthsupport",
|
|
82
85
|
"@bobfrankston/msger": "file:../../../utils/msgx/msger",
|
|
83
|
-
"@bobfrankston/
|
|
86
|
+
"@bobfrankston/node-tcp-transport": "file:../../MailApps/node-tcp-transport",
|
|
87
|
+
"@bobfrankston/oauthsupport": "file:../../../projects/oauth/oauthsupport",
|
|
88
|
+
"@bobfrankston/smtp-direct": "file:../../MailApps/smtp-direct",
|
|
89
|
+
"@bobfrankston/tcp-transport": "file:../../MailApps/tcp-transport",
|
|
84
90
|
"@capacitor/android": "^8.3.0",
|
|
85
91
|
"@capacitor/cli": "^8.3.0",
|
|
86
92
|
"@capacitor/core": "^8.3.0",
|
|
87
93
|
"express": "^4.21.0",
|
|
94
|
+
"html-to-docx": "^1.8.0",
|
|
88
95
|
"jsonc-parser": "^3.3.1",
|
|
89
96
|
"mailparser": "^3.7.2",
|
|
97
|
+
"mammoth": "^1.12.0",
|
|
90
98
|
"nodemailer": "^7.0.0",
|
|
91
99
|
"quill": "^2.0.3",
|
|
92
|
-
"ws": "^8.18.0",
|
|
93
100
|
"sql.js": "^1.14.1",
|
|
94
|
-
"
|
|
95
|
-
"@bobfrankston/node-tcp-transport": "file:../../MailApps/node-tcp-transport",
|
|
96
|
-
"@bobfrankston/smtp-direct": "file:../../MailApps/smtp-direct",
|
|
97
|
-
"@bobfrankston/mailx-sync": "file:../../MailApps/mailx-sync",
|
|
98
|
-
"@bobfrankston/mailx-imap": "file:packages/mailx-imap",
|
|
99
|
-
"html-to-docx": "^1.8.0",
|
|
100
|
-
"mammoth": "^1.12.0"
|
|
101
|
+
"ws": "^8.18.0"
|
|
101
102
|
},
|
|
102
103
|
".transformedSnapshot": {
|
|
103
104
|
"dependencies": {
|
|
104
105
|
"@bobfrankston/iflow-direct": "^0.1.39",
|
|
106
|
+
"@bobfrankston/mailx-host": "^0.1.11",
|
|
107
|
+
"@bobfrankston/mailx-imap": "^0.1.32",
|
|
108
|
+
"@bobfrankston/mailx-sync": "^0.1.16",
|
|
105
109
|
"@bobfrankston/miscinfo": "^1.0.10",
|
|
106
|
-
"@bobfrankston/oauthsupport": "^1.0.26",
|
|
107
110
|
"@bobfrankston/msger": "^0.1.378",
|
|
108
|
-
"@bobfrankston/
|
|
111
|
+
"@bobfrankston/node-tcp-transport": "^0.1.8",
|
|
112
|
+
"@bobfrankston/oauthsupport": "^1.0.26",
|
|
113
|
+
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
114
|
+
"@bobfrankston/tcp-transport": "^0.1.6",
|
|
109
115
|
"@capacitor/android": "^8.3.0",
|
|
110
116
|
"@capacitor/cli": "^8.3.0",
|
|
111
117
|
"@capacitor/core": "^8.3.0",
|
|
112
118
|
"express": "^4.21.0",
|
|
119
|
+
"html-to-docx": "^1.8.0",
|
|
113
120
|
"jsonc-parser": "^3.3.1",
|
|
114
121
|
"mailparser": "^3.7.2",
|
|
122
|
+
"mammoth": "^1.12.0",
|
|
115
123
|
"nodemailer": "^7.0.0",
|
|
116
124
|
"quill": "^2.0.3",
|
|
117
|
-
"ws": "^8.18.0",
|
|
118
125
|
"sql.js": "^1.14.1",
|
|
119
|
-
"
|
|
120
|
-
"@bobfrankston/node-tcp-transport": "^0.1.8",
|
|
121
|
-
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
122
|
-
"@bobfrankston/mailx-sync": "^0.1.16",
|
|
123
|
-
"@bobfrankston/mailx-imap": "^0.1.32",
|
|
124
|
-
"html-to-docx": "^1.8.0",
|
|
125
|
-
"mammoth": "^1.12.0"
|
|
126
|
+
"ws": "^8.18.0"
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
}
|
|
@@ -398,7 +398,18 @@ export declare class ImapManager extends EventEmitter {
|
|
|
398
398
|
moveMessage(accountId: string, uid: number, fromFolderId: number, toFolderId: number): Promise<void>;
|
|
399
399
|
/** Move message across accounts using iflow's moveMessageToServer */
|
|
400
400
|
moveMessageCrossAccount(fromAccountId: string, uid: number, fromFolderId: number, toAccountId: string, toFolderId: number): Promise<void>;
|
|
401
|
-
/** Undelete — move from Trash back to original folder
|
|
401
|
+
/** Undelete — move from Trash back to original folder. Local-first:
|
|
402
|
+
* the row was moved (not deleted) on trash, so we just move it back
|
|
403
|
+
* in the local DB and reconcile the IMAP queue. Two cases:
|
|
404
|
+
* (a) the to-trash MOVE is still pending — cancel it; the server
|
|
405
|
+
* never saw the delete, so no counter-action is needed.
|
|
406
|
+
* (b) the to-trash MOVE drained — the message is now in Trash on
|
|
407
|
+
* the server with a new uid. Queue a counter-move from
|
|
408
|
+
* trash → original. The IMAP processor's fetchByUid in trash
|
|
409
|
+
* will use the local membership uid (which the reconciler
|
|
410
|
+
* rebound to the server's new trash uid via Message-ID match).
|
|
411
|
+
* If reconcile hasn't run yet (unlikely race), action retries
|
|
412
|
+
* until it does. */
|
|
402
413
|
undeleteMessage(accountId: string, uid: number, originalFolderId: number): Promise<void>;
|
|
403
414
|
/** Update flags — local-first, queues IMAP sync */
|
|
404
415
|
updateFlagsLocal(accountId: string, uid: number, folderId: number, flags: string[]): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
|
@@ -2696,12 +2696,24 @@ export class ImapManager extends EventEmitter {
|
|
|
2696
2696
|
if (messages.length === 0)
|
|
2697
2697
|
return;
|
|
2698
2698
|
const trash = this.findFolder(accountId, "trash");
|
|
2699
|
-
// Local first —
|
|
2699
|
+
// Local first — move to trash folder locally so the row stays
|
|
2700
|
+
// visible in Trash and Ctrl+Z can restore it. Body file stays in
|
|
2701
|
+
// its original folder dir; the next sync rebinds path on
|
|
2702
|
+
// membership uid change. Old behavior was `db.deleteMessage` +
|
|
2703
|
+
// `unlinkBodyFile` which made undelete impossible (no row to
|
|
2704
|
+
// restore, no body to read). For folders that ARE the trash
|
|
2705
|
+
// already, fall through to hard delete (the action will EXPUNGE
|
|
2706
|
+
// and reconciliation cleans up).
|
|
2700
2707
|
for (const msg of messages) {
|
|
2701
|
-
|
|
2702
|
-
|
|
2708
|
+
if (trash && trash.id !== msg.folderId) {
|
|
2709
|
+
this.db.moveMessageLocal(accountId, msg.uid, msg.folderId, trash.id);
|
|
2710
|
+
}
|
|
2711
|
+
else {
|
|
2712
|
+
this.unlinkBodyFile(accountId, msg.uid, msg.folderId).catch(() => { });
|
|
2713
|
+
this.db.deleteMessage(accountId, msg.uid, "user-initiated trash (already in trash → expunge)", "mailx-imap trashMessages");
|
|
2714
|
+
}
|
|
2703
2715
|
}
|
|
2704
|
-
console.log(`
|
|
2716
|
+
console.log(` Trashed ${messages.length} messages locally (moved to trash folder, body files retained)`);
|
|
2705
2717
|
// Queue IMAP actions
|
|
2706
2718
|
for (const msg of messages) {
|
|
2707
2719
|
if (trash && trash.id !== msg.folderId) {
|
|
@@ -2750,9 +2762,17 @@ export class ImapManager extends EventEmitter {
|
|
|
2750
2762
|
/** Move a message to Trash (delete) — local-first, queues IMAP sync */
|
|
2751
2763
|
async trashMessage(accountId, folderId, uid) {
|
|
2752
2764
|
const trash = this.findFolder(accountId, "trash");
|
|
2753
|
-
// Local first —
|
|
2754
|
-
|
|
2755
|
-
|
|
2765
|
+
// Local first — move to trash folder so the row stays visible in
|
|
2766
|
+
// Trash and Ctrl+Z can restore. Body file retained for undelete.
|
|
2767
|
+
// If we're already in trash (or no trash configured), fall through
|
|
2768
|
+
// to hard delete + EXPUNGE.
|
|
2769
|
+
if (trash && trash.id !== folderId) {
|
|
2770
|
+
this.db.moveMessageLocal(accountId, uid, folderId, trash.id);
|
|
2771
|
+
}
|
|
2772
|
+
else {
|
|
2773
|
+
this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
|
|
2774
|
+
this.db.deleteMessage(accountId, uid, "user-initiated trash (already in trash → expunge)", "mailx-imap trashMessage");
|
|
2775
|
+
}
|
|
2756
2776
|
// Queue IMAP action + log the resolution so "I deleted a message and
|
|
2757
2777
|
// now it's in neither trash nor deleted" is diagnosable from the log.
|
|
2758
2778
|
if (trash && trash.id !== folderId) {
|
|
@@ -2764,6 +2784,12 @@ export class ImapManager extends EventEmitter {
|
|
|
2764
2784
|
this.db.queueSyncAction(accountId, "delete", uid, folderId);
|
|
2765
2785
|
console.log(` [trash] ${accountId} UID ${uid}: queued EXPUNGE in folder ${folderId} (already in trash or no trash configured)`);
|
|
2766
2786
|
}
|
|
2787
|
+
// Folder counts moved — refresh both source and trash so the
|
|
2788
|
+
// tree badges update immediately, not at the next sync.
|
|
2789
|
+
this.db.recalcFolderCounts(folderId);
|
|
2790
|
+
if (trash && trash.id !== folderId)
|
|
2791
|
+
this.db.recalcFolderCounts(trash.id);
|
|
2792
|
+
this.emit("folderCountsChanged", accountId, {});
|
|
2767
2793
|
// Debounced sync — batches multiple deletes into one IMAP session
|
|
2768
2794
|
this.debounceSyncActions(accountId);
|
|
2769
2795
|
}
|
|
@@ -2799,12 +2825,40 @@ export class ImapManager extends EventEmitter {
|
|
|
2799
2825
|
});
|
|
2800
2826
|
});
|
|
2801
2827
|
}
|
|
2802
|
-
/** Undelete — move from Trash back to original folder
|
|
2828
|
+
/** Undelete — move from Trash back to original folder. Local-first:
|
|
2829
|
+
* the row was moved (not deleted) on trash, so we just move it back
|
|
2830
|
+
* in the local DB and reconcile the IMAP queue. Two cases:
|
|
2831
|
+
* (a) the to-trash MOVE is still pending — cancel it; the server
|
|
2832
|
+
* never saw the delete, so no counter-action is needed.
|
|
2833
|
+
* (b) the to-trash MOVE drained — the message is now in Trash on
|
|
2834
|
+
* the server with a new uid. Queue a counter-move from
|
|
2835
|
+
* trash → original. The IMAP processor's fetchByUid in trash
|
|
2836
|
+
* will use the local membership uid (which the reconciler
|
|
2837
|
+
* rebound to the server's new trash uid via Message-ID match).
|
|
2838
|
+
* If reconcile hasn't run yet (unlikely race), action retries
|
|
2839
|
+
* until it does. */
|
|
2803
2840
|
async undeleteMessage(accountId, uid, originalFolderId) {
|
|
2804
2841
|
const trash = this.findFolder(accountId, "trash");
|
|
2805
2842
|
if (!trash)
|
|
2806
2843
|
throw new Error("No Trash folder found");
|
|
2807
|
-
|
|
2844
|
+
// Move locally back to the original folder.
|
|
2845
|
+
const moved = this.db.moveMessageLocal(accountId, uid, trash.id, originalFolderId);
|
|
2846
|
+
if (!moved) {
|
|
2847
|
+
console.log(` [undelete] ${accountId} UID ${uid}: no row in trash — nothing to restore locally (sync may have already pruned)`);
|
|
2848
|
+
}
|
|
2849
|
+
// (a) cancel still-pending to-trash action.
|
|
2850
|
+
const pending = this.db.findPendingSyncAction(accountId, "move", uid, originalFolderId, trash.id);
|
|
2851
|
+
if (pending) {
|
|
2852
|
+
this.db.completeSyncAction(pending.id);
|
|
2853
|
+
console.log(` [undelete] ${accountId} UID ${uid}: cancelled pending MOVE to trash (server never saw delete)`);
|
|
2854
|
+
this.emit("folderCountsChanged", accountId, {});
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
// (b) queue counter-move from trash → original.
|
|
2858
|
+
this.db.queueSyncAction(accountId, "move", uid, trash.id, { targetFolderId: originalFolderId });
|
|
2859
|
+
console.log(` [undelete] ${accountId} UID ${uid}: queued counter-MOVE trash → folder ${originalFolderId}`);
|
|
2860
|
+
this.debounceSyncActions(accountId);
|
|
2861
|
+
this.emit("folderCountsChanged", accountId, {});
|
|
2808
2862
|
}
|
|
2809
2863
|
/** Update flags — local-first, queues IMAP sync */
|
|
2810
2864
|
async updateFlagsLocal(accountId, uid, folderId, flags) {
|