@bobfrankston/rmfmail 1.1.225 → 1.1.228

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 (44) hide show
  1. package/client/compose/compose.bundle.js +22 -10
  2. package/client/compose/compose.bundle.js.map +2 -2
  3. package/client/lib/rmf-tiny.js +35 -10
  4. package/client/lib/tinymce/CHANGELOG.md +12 -0
  5. package/client/lib/tinymce/composer.json +1 -1
  6. package/client/lib/tinymce/models/dom/model.js +1 -1
  7. package/client/lib/tinymce/package.json +1 -1
  8. package/client/lib/tinymce/plugins/accordion/plugin.js +1 -1
  9. package/client/lib/tinymce/plugins/advlist/plugin.js +1 -1
  10. package/client/lib/tinymce/plugins/anchor/plugin.js +1 -1
  11. package/client/lib/tinymce/plugins/autolink/plugin.js +1 -1
  12. package/client/lib/tinymce/plugins/autoresize/plugin.js +1 -1
  13. package/client/lib/tinymce/plugins/autosave/plugin.js +1 -1
  14. package/client/lib/tinymce/plugins/charmap/plugin.js +1 -1
  15. package/client/lib/tinymce/plugins/code/plugin.js +1 -1
  16. package/client/lib/tinymce/plugins/codesample/plugin.js +1 -1
  17. package/client/lib/tinymce/plugins/directionality/plugin.js +1 -1
  18. package/client/lib/tinymce/plugins/emoticons/plugin.js +1 -1
  19. package/client/lib/tinymce/plugins/fullscreen/plugin.js +1 -1
  20. package/client/lib/tinymce/plugins/help/plugin.js +1 -1
  21. package/client/lib/tinymce/plugins/image/plugin.js +1 -1
  22. package/client/lib/tinymce/plugins/importcss/plugin.js +1 -1
  23. package/client/lib/tinymce/plugins/insertdatetime/plugin.js +1 -1
  24. package/client/lib/tinymce/plugins/link/plugin.js +1 -1
  25. package/client/lib/tinymce/plugins/lists/plugin.js +1 -1
  26. package/client/lib/tinymce/plugins/media/plugin.js +66 -41
  27. package/client/lib/tinymce/plugins/media/plugin.min.js +1 -1
  28. package/client/lib/tinymce/plugins/nonbreaking/plugin.js +1 -1
  29. package/client/lib/tinymce/plugins/pagebreak/plugin.js +1 -1
  30. package/client/lib/tinymce/plugins/preview/plugin.js +1 -1
  31. package/client/lib/tinymce/plugins/quickbars/plugin.js +1 -1
  32. package/client/lib/tinymce/plugins/save/plugin.js +1 -1
  33. package/client/lib/tinymce/plugins/searchreplace/plugin.js +1 -1
  34. package/client/lib/tinymce/plugins/table/plugin.js +1 -1
  35. package/client/lib/tinymce/plugins/visualblocks/plugin.js +1 -1
  36. package/client/lib/tinymce/plugins/visualchars/plugin.js +1 -1
  37. package/client/lib/tinymce/plugins/wordcount/plugin.js +1 -1
  38. package/client/lib/tinymce/themes/silver/theme.js +345 -155
  39. package/client/lib/tinymce/themes/silver/theme.min.js +3 -1
  40. package/client/lib/tinymce/tinymce.js +393 -171
  41. package/client/lib/tinymce/tinymce.min.js +4 -2
  42. package/docs/outlook.md +215 -0
  43. package/package.json +3 -3
  44. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-62732 → node_modules.npmglobalize-stash-126048}/.package-lock.json +0 -0
@@ -0,0 +1,215 @@
1
+ # Outlook.com / Microsoft 365 Support
2
+
3
+ Status (2026-06-05): **scaffolding present, not usable.** An account auto-detects
4
+ the right hosts but cannot authenticate — there is no Microsoft OAuth wired, and
5
+ Microsoft has disabled basic-auth (password) for outlook.com / office365, so the
6
+ IMAP path can't fall back to a password either. Gmail is the only fully-wired
7
+ OAuth provider today.
8
+
9
+ This doc scopes what it takes to make Outlook a first-class account.
10
+
11
+ ---
12
+
13
+ ## What already exists
14
+
15
+ | Piece | Where | State |
16
+ |---|---|---|
17
+ | Graph API provider | `@bobfrankston/mailx-sync/outlook.ts` (re-exported `packages/mailx-imap/providers/outlook-api.ts`) | **Read-only**: `listFolders`, `fetchSince/ByDate/ByUids/One`, `getUids`. No send, no write-back, no drafts/attachments. |
18
+ | Host auto-config | `packages/mailx-settings/index.ts:503` (`outlook.com`, `hotmail.com` → `outlook.office365.com:993` / `smtp.office365.com:587`, `auth: "oauth2"`) | Done |
19
+ | MX-based detection | `bin/mailx.ts:1307` (`*.outlook.com` / `*.protection.outlook.com` MX → Microsoft 365) | Done |
20
+ | Generic OAuth flow | `@bobfrankston/oauthsupport` `OAuthTokenManager.ts:369` — validates `client_id/client_secret/auth_uri/token_uri` from the creds file; localhost loopback auth-code flow | **Provider-agnostic** — not Google-specific |
21
+ | OAuth SMTP send | `packages/mailx-imap/index.ts:1533` builds `{type:"OAuth2", user, accessToken}` from `config.tokenProvider()` | Works for any provider with a tokenProvider |
22
+
23
+ The single thing that makes Gmail work and Outlook not: the **tokenProvider** at
24
+ `packages/mailx-imap/index.ts:798-848` is built only for Google. It hardcodes the
25
+ Google credentials file (`~/.mailx/google-credentials.json`) and the all-Google
26
+ scope string. Nothing constructs a Microsoft tokenProvider, so
27
+ `OutlookApiProvider` is never instantiated and the office365 IMAP host has no
28
+ token to present.
29
+
30
+ ---
31
+
32
+ ## Decision: Graph API vs IMAP/XOAUTH2
33
+
34
+ Both need the same Azure app registration + Microsoft OAuth. The difference is
35
+ the sync transport once you have a token.
36
+
37
+ **Recommended: Graph API.** It mirrors the Gmail-API model the codebase already
38
+ follows (`isGmailAccount()` branch → REST provider, no IMAP client). Wins: no
39
+ connection-limit/timeout class of bugs (the entire `inactivityTimeout` /
40
+ `greetingTimeout` / chunk-size tuning at `index.ts:862` is IMAP-only pain);
41
+ provider is already half-written; same `MailProvider` interface so Android reuses
42
+ it verbatim. Cost: the provider is read-only — send + flag/move/delete + drafts
43
+ must be added (Graph endpoints, ~a day). Sharp edge: `idToUid()`
44
+ (`outlook.ts` ~135) hashes Graph's long string IDs to 48-bit ints, and
45
+ `fetchByUids`/`fetchOne` re-list the **whole** folder then filter — O(folder) per
46
+ fetch with collision risk. Real sync needs a UID↔providerId map (we already keep
47
+ `provider_id` in the DB) and direct `GET /messages/{id}` instead of list-and-scan.
48
+
49
+ **Alternative: IMAP + XOAUTH2.** Reuses the existing iflow-direct sync path
50
+ unchanged — just feed it a Microsoft token instead of a Google one. Wins: zero
51
+ new sync/send code; flag/move/delete/append already work over IMAP. Cost:
52
+ inherits every IMAP fragility we've spent months hardening for Dovecot/Gmail, now
53
+ against Exchange Online's quirks. Sharp edge: Microsoft is steering third-party
54
+ clients toward Graph and away from consumer IMAP; betting the integration on
55
+ office365 IMAP is the less future-proof path.
56
+
57
+ Pragmatic call: **IMAP/XOAUTH2 first** as the fast path to a working account (it's
58
+ mostly OAuth plumbing), then migrate sync to Graph for parity with Gmail. The
59
+ OAuth work below is shared, so it's not wasted either way.
60
+
61
+ ---
62
+
63
+ ## Work breakdown
64
+
65
+ ### 1. Azure app registration [S, one-time, external]
66
+ Register an app in Entra/Azure AD:
67
+ - Account type: "personal Microsoft accounts" (consumer outlook.com) and/or
68
+ "any org directory" (Microsoft 365). For both, use the `/common` authority.
69
+ - Redirect URI: `http://localhost` (loopback) — matches oauthsupport's flow
70
+ (`OAuthTokenManager.ts:463`, parses the loopback callback).
71
+ - API permissions (delegated):
72
+ - Graph path: `Mail.ReadWrite`, `Mail.Send`, `offline_access`, `openid`,
73
+ `profile`.
74
+ - IMAP path: `https://outlook.office.com/IMAP.AccessAsUser.All`,
75
+ `https://outlook.office.com/SMTP.Send`, `offline_access`.
76
+ - Produce a `microsoft-credentials.json` shaped like the Google one, with
77
+ `auth_uri: https://login.microsoftonline.com/common/oauth2/v2.0/authorize`,
78
+ `token_uri: https://login.microsoftonline.com/common/oauth2/v2.0/token`,
79
+ `redirect_uris: ["http://localhost"]`, plus `client_id` / `client_secret`.
80
+
81
+ Store at `~/.mailx/microsoft-credentials.json` (parallel to
82
+ `google-credentials.json`).
83
+
84
+ ### 2. Microsoft tokenProvider [M]
85
+ Generalize `index.ts:798-848` so the provider isn't Google-only:
86
+ - Pick the creds file + scope by account kind (gmail vs outlook), keyed off the
87
+ same signal `account.imap.auth === "oauth2"` plus host/email domain.
88
+ - For Outlook, point `authenticateOAuth` at `microsoft-credentials.json` with the
89
+ Microsoft scope set. The flow itself is unchanged — oauthsupport already reads
90
+ the endpoints from the creds file, so no oauthsupport changes are required
91
+ (confirm consumer-account PKCE: Microsoft may require `code_challenge` for the
92
+ loopback client; if so, add PKCE support in `OAuthTokenManager.getToken`).
93
+ - Token cache dir reuses the existing `tokens/<user>/` convention (`index.ts:814`).
94
+
95
+ This single change makes the **IMAP/XOAUTH2 path work end-to-end** — the office365
96
+ host config already exists, and SMTP OAuth (`index.ts:1533`) consumes the same
97
+ tokenProvider. Verify the token is minted with Outlook IMAP/SMTP scopes, not Graph
98
+ scopes (they're different audiences).
99
+
100
+ ### 3. Provider selection [S]
101
+ Add an `isOutlookAccount()` sibling to `isGmailAccount()` (`index.ts:898`) and an
102
+ `getOutlookProvider()` mirroring `getGmailProvider()` (`index.ts:906`) that does
103
+ `new OutlookApiProvider(config.tokenProvider)`. Then extend each
104
+ `isGmailAccount()` branch (sync, periodic STATUS, IDLE-skip, etc. — ~10 sites) to
105
+ a three-way provider switch. *(Only needed for the Graph path; the IMAP path
106
+ needs none of this — it flows through the normal IMAP code.)*
107
+
108
+ ### 4. Graph write-back + send [M] *(Graph path only)*
109
+ Add to `OutlookApiProvider`:
110
+ - `sendMail`: `POST /sendMail` with the MIME or the Graph message object.
111
+ - mark read/flag: `PATCH /messages/{id}` (`isRead`, `flag`).
112
+ - move: `POST /messages/{id}/move`.
113
+ - delete: `DELETE /messages/{id}` (or move to deleteditems).
114
+ - draft create/update: `POST /messages` / `PATCH`.
115
+ - attachments: `GET /messages/{id}/attachments`.
116
+ Replace list-and-scan in `fetchByUids`/`fetchOne` with direct
117
+ `GET /messages/{providerId}` using the DB's `provider_id` mapping.
118
+
119
+ ### 5. Setup UX [S]
120
+ `bin/mailx.ts` `knownOAuth` list (~line 1320) already includes `outlook.com` /
121
+ `hotmail.com`. Once the tokenProvider exists, first-run setup triggers the
122
+ Microsoft consent screen the same way Gmail does — no extra UI. Add an error
123
+ banner case for "Microsoft credentials missing" pointing at
124
+ `microsoft-credentials.json`.
125
+
126
+ ---
127
+
128
+ ## Step-by-step (with URLs)
129
+
130
+ ### A. Register the Azure app — the part only you can do
131
+ 1. Sign in to the Entra admin center → **App registrations**:
132
+ https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
133
+ (equivalently Azure portal → "Microsoft Entra ID" → "App registrations":
134
+ https://portal.azure.com)
135
+ 2. **New registration** (guide:
136
+ https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app):
137
+ - Name: `rmfmail`.
138
+ - Supported account types: **"Accounts in any organizational directory and
139
+ personal Microsoft accounts"** (covers both outlook.com consumer and M365).
140
+ - Redirect URI: platform **"Mobile and desktop applications"**, value
141
+ **`http://localhost`** (loopback — what oauthsupport's flow listens on).
142
+ Desktop/loopback registration:
143
+ https://learn.microsoft.com/en-us/entra/identity-platform/scenario-desktop-app-registration
144
+ 3. Copy the **Application (client) ID** from the app's Overview page.
145
+ 4. **Certificates & secrets** → **New client secret** → copy the secret *value*
146
+ (not the ID). (Public-client/PKCE is the alternative if you'd rather not ship a
147
+ secret — see Sharp edges.)
148
+ 5. **API permissions** → **Add a permission** → **Microsoft Graph** → **Delegated**
149
+ (permissions reference:
150
+ https://learn.microsoft.com/en-us/graph/permissions-reference):
151
+ - Graph path: `Mail.ReadWrite`, `Mail.Send`, `offline_access`, `openid`,
152
+ `profile`.
153
+ - IMAP path instead/also: `IMAP.AccessAsUser.All`, `SMTP.Send`,
154
+ `offline_access` (these live under the *Office 365 Exchange Online* API, not
155
+ Graph). Microsoft's IMAP/SMTP-OAuth guide:
156
+ https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
157
+ - Click **Grant admin consent** if it's a work tenant (personal accounts
158
+ consent at first sign-in).
159
+
160
+ Reference for the endpoints used below:
161
+ - Authorize: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize`
162
+ - Token: `https://login.microsoftonline.com/common/oauth2/v2.0/token`
163
+ - Auth-code flow spec:
164
+ https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow
165
+ - Graph mail API overview:
166
+ https://learn.microsoft.com/en-us/graph/api/resources/mail-api-overview
167
+
168
+ ### B. Drop in the credentials file
169
+ Create `~/.mailx/microsoft-credentials.json` mirroring the Google one's shape so
170
+ oauthsupport's `OAuthTokenManager` (reads `client_id`/`client_secret`/`auth_uri`/
171
+ `token_uri`, `OAuthTokenManager.ts:369`) consumes it unchanged:
172
+
173
+ {
174
+ "installed": {
175
+ "client_id": "<Application (client) ID>",
176
+ "client_secret": "<secret value>",
177
+ "auth_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
178
+ "token_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
179
+ "redirect_uris": ["http://localhost"]
180
+ }
181
+ }
182
+
183
+ ### C. Wire the code (steps 2-3 from the work breakdown)
184
+ 1. In `packages/mailx-imap/index.ts` (~798), branch the tokenProvider on account
185
+ kind: Outlook accounts use `microsoft-credentials.json` + the Microsoft scope
186
+ string (IMAP scopes for the IMAP path, Graph scopes for the Graph path).
187
+ 2. For the **IMAP path** that's the whole change — the office365 host config
188
+ (`mailx-settings/index.ts:503`) and OAuth-SMTP (`index.ts:1533`) already
189
+ consume the tokenProvider. Test sign-in: first send triggers the loopback
190
+ consent at `http://localhost`.
191
+ 3. For the **Graph path**, add `isOutlookAccount()`/`getOutlookProvider()` next to
192
+ the Gmail equivalents (`index.ts:898/906`) and fill the provider's write-back
193
+ gaps (`sendMail`, flag/move/delete, drafts).
194
+
195
+ ## Sharp edges / open questions
196
+
197
+ - **Consumer vs M365 differ.** Personal outlook.com and Microsoft 365 work
198
+ accounts use different scope audiences and consent behavior. `/common` covers
199
+ both at the authority level, but test both — a token good for Graph isn't valid
200
+ for IMAP and vice-versa.
201
+ - **PKCE.** Microsoft increasingly requires PKCE for loopback clients. If consent
202
+ fails with `invalid_request`, add `code_challenge`/`code_verifier` to
203
+ oauthsupport's auth-code flow (it's the one oauthsupport change that might be
204
+ needed).
205
+ - **Basic auth is gone.** Don't add a password fallback for office365 — it will
206
+ always fail. The error path should say "Outlook requires sign-in", not "wrong
207
+ password".
208
+ - **Drive/OneDrive is unrelated.** `cloud.ts` already sketches MS Graph
209
+ `Files.ReadWrite` scopes for OneDrive storage — that's a separate app/scope from
210
+ mail and shares none of this wiring (and OneDrive cloud storage was removed;
211
+ GDrive only).
212
+ - **`idToUid` collisions.** A 32-bit hash of Graph IDs across a 10-year mailbox
213
+ will eventually collide. The Graph path must key on `provider_id`
214
+ (string) as identity, with the integer UID as a display/sort convenience only —
215
+ same lesson as `imap_uid_not_identity`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/rmfmail",
3
- "version": "1.1.225",
3
+ "version": "1.1.228",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -45,7 +45,7 @@
45
45
  "@bobfrankston/msger": "^0.1.384",
46
46
  "@bobfrankston/node-tcp-transport": "^0.1.8",
47
47
  "@bobfrankston/oauthsupport": "^1.0.31",
48
- "@bobfrankston/rmf-tiny": "^0.1.28",
48
+ "@bobfrankston/rmf-tiny": "^0.1.29",
49
49
  "@bobfrankston/smtp-direct": "^0.1.8",
50
50
  "@bobfrankston/tcp-transport": "^0.1.6",
51
51
  "@capacitor/android": "^8.3.0",
@@ -125,7 +125,7 @@
125
125
  "@bobfrankston/msger": "^0.1.384",
126
126
  "@bobfrankston/node-tcp-transport": "^0.1.8",
127
127
  "@bobfrankston/oauthsupport": "^1.0.31",
128
- "@bobfrankston/rmf-tiny": "^0.1.28",
128
+ "@bobfrankston/rmf-tiny": "^0.1.29",
129
129
  "@bobfrankston/smtp-direct": "^0.1.8",
130
130
  "@bobfrankston/tcp-transport": "^0.1.6",
131
131
  "@capacitor/android": "^8.3.0",