@bobfrankston/mailx 1.0.9 → 1.0.13
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 +47 -29
- package/bin/postinstall.js +41 -0
- package/client/app.js +93 -13
- package/client/components/folder-tree.js +84 -3
- package/client/components/message-list.js +134 -8
- package/client/components/message-viewer.js +130 -13
- package/client/compose/compose.html +4 -4
- package/client/compose/compose.js +53 -34
- package/client/index.html +33 -9
- package/client/lib/api-client.js +102 -30
- package/client/lib/mailxapi.js +123 -0
- package/client/package.json +5 -1
- package/client/styles/components.css +188 -15
- package/client/styles/layout.css +2 -1
- package/killmail.cmd +6 -0
- package/launch.ps1 +48 -3
- package/launcher/bin/mailx-app.exe +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Breadcrumbs +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/metadata +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/settings.dat +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/throttle_store.dat +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/CrashpadMetrics-active.pma +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/BrowsingTopicsSiteData +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Cache/No_Vary_Search/journal.baj +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DIPS +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DashTrackerDatabase +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/EdgeJourneys/EdgeJourneys.db +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityComp +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityEdge +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Favicons +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History-journal +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data For Account +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Cookies +0 -0
- NEL +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Trust Tokens +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network Action Predictor +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Preferences +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Safe Browsing Network/Safe Browsing Cookies +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ServerCertificate +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shared Dictionary/db +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/SharedStorage +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shortcuts +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Top Sites +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Vpn Tokens +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data-journal +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager-journal +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/heavy_ad_intervention_opt_out.db +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/DeferredBrowserMetrics/BrowserMetrics-69CAD063-BE24.pma +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Local State +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Variations +1 -0
- package/launcher/bin/mailx-app.old.exe +0 -0
- package/launcher/mailx.ico +0 -0
- package/npmg.bat +6 -0
- package/package.json +9 -3
- package/packages/mailx-api/index.js +79 -26
- package/packages/mailx-api/package.json +5 -1
- package/packages/mailx-compose/package.json +5 -1
- package/packages/mailx-core/index.d.ts +129 -0
- package/packages/mailx-core/index.js +323 -0
- package/packages/mailx-core/ipc.d.ts +13 -0
- package/packages/mailx-core/ipc.js +56 -0
- package/packages/mailx-core/package.json +18 -0
- package/packages/mailx-imap/index.d.ts +5 -1
- package/packages/mailx-imap/index.js +76 -14
- package/packages/mailx-imap/package.json +5 -1
- package/packages/mailx-send/package.json +12 -2
- package/packages/mailx-server/index.js +42 -31
- package/packages/mailx-server/package.json +5 -2
- package/packages/mailx-settings/index.d.ts +1 -1
- package/packages/mailx-settings/index.js +21 -12
- package/packages/mailx-settings/package.json +5 -1
- package/packages/mailx-store/db.d.ts +5 -1
- package/packages/mailx-store/db.js +64 -12
- package/packages/mailx-store/file-store.d.ts +2 -8
- package/packages/mailx-store/file-store.js +7 -31
- package/packages/mailx-store/package.json +5 -1
- package/packages/mailx-types/index.d.ts +3 -1
- package/packages/mailx-types/package.json +6 -2
- package/.tswalk.json +0 -3366
- package/mailx.json +0 -9
- package/packages/mailx-api/node_modules/nodemailer/.ncurc.js +0 -9
- package/packages/mailx-api/node_modules/nodemailer/.prettierignore +0 -8
- package/packages/mailx-api/node_modules/nodemailer/.prettierrc +0 -12
- package/packages/mailx-api/node_modules/nodemailer/.prettierrc.js +0 -10
- package/packages/mailx-api/node_modules/nodemailer/.release-please-config.json +0 -9
- package/packages/mailx-api/node_modules/nodemailer/LICENSE +0 -16
- package/packages/mailx-api/node_modules/nodemailer/README.md +0 -86
- package/packages/mailx-api/node_modules/nodemailer/SECURITY.txt +0 -22
- package/packages/mailx-api/node_modules/nodemailer/eslint.config.js +0 -88
- package/packages/mailx-api/node_modules/nodemailer/lib/addressparser/index.js +0 -383
- package/packages/mailx-api/node_modules/nodemailer/lib/base64/index.js +0 -139
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/index.js +0 -253
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/sign.js +0 -117
- package/packages/mailx-api/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
- package/packages/mailx-api/node_modules/nodemailer/lib/fetch/index.js +0 -280
- package/packages/mailx-api/node_modules/nodemailer/lib/json-transport/index.js +0 -82
- package/packages/mailx-api/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
- package/packages/mailx-api/node_modules/nodemailer/lib/mailer/index.js +0 -441
- package/packages/mailx-api/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
- package/packages/mailx-api/node_modules/nodemailer/lib/nodemailer.js +0 -157
- package/packages/mailx-api/node_modules/nodemailer/lib/punycode/index.js +0 -460
- package/packages/mailx-api/node_modules/nodemailer/lib/qp/index.js +0 -227
- package/packages/mailx-api/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
- package/packages/mailx-api/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
- package/packages/mailx-api/node_modules/nodemailer/lib/shared/index.js +0 -754
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
- package/packages/mailx-api/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
- package/packages/mailx-api/node_modules/nodemailer/lib/well-known/index.js +0 -47
- package/packages/mailx-api/node_modules/nodemailer/lib/well-known/services.json +0 -611
- package/packages/mailx-api/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
- package/packages/mailx-api/node_modules/nodemailer/package.json +0 -47
- package/packages/mailx-imap/node_modules/nodemailer/.ncurc.js +0 -9
- package/packages/mailx-imap/node_modules/nodemailer/.prettierignore +0 -8
- package/packages/mailx-imap/node_modules/nodemailer/.prettierrc +0 -12
- package/packages/mailx-imap/node_modules/nodemailer/.prettierrc.js +0 -10
- package/packages/mailx-imap/node_modules/nodemailer/.release-please-config.json +0 -9
- package/packages/mailx-imap/node_modules/nodemailer/LICENSE +0 -16
- package/packages/mailx-imap/node_modules/nodemailer/README.md +0 -86
- package/packages/mailx-imap/node_modules/nodemailer/SECURITY.txt +0 -22
- package/packages/mailx-imap/node_modules/nodemailer/eslint.config.js +0 -88
- package/packages/mailx-imap/node_modules/nodemailer/lib/addressparser/index.js +0 -383
- package/packages/mailx-imap/node_modules/nodemailer/lib/base64/index.js +0 -139
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/index.js +0 -253
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/sign.js +0 -117
- package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
- package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/index.js +0 -280
- package/packages/mailx-imap/node_modules/nodemailer/lib/json-transport/index.js +0 -82
- package/packages/mailx-imap/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
- package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/index.js +0 -441
- package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
- package/packages/mailx-imap/node_modules/nodemailer/lib/nodemailer.js +0 -157
- package/packages/mailx-imap/node_modules/nodemailer/lib/punycode/index.js +0 -460
- package/packages/mailx-imap/node_modules/nodemailer/lib/qp/index.js +0 -227
- package/packages/mailx-imap/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
- package/packages/mailx-imap/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
- package/packages/mailx-imap/node_modules/nodemailer/lib/shared/index.js +0 -754
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
- package/packages/mailx-imap/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
- package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/index.js +0 -47
- package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/services.json +0 -611
- package/packages/mailx-imap/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
- package/packages/mailx-imap/node_modules/nodemailer/package.json +0 -47
- package/packages/mailx-send/node_modules/nodemailer/.ncurc.js +0 -9
- package/packages/mailx-send/node_modules/nodemailer/.prettierignore +0 -8
- package/packages/mailx-send/node_modules/nodemailer/.prettierrc +0 -12
- package/packages/mailx-send/node_modules/nodemailer/.prettierrc.js +0 -10
- package/packages/mailx-send/node_modules/nodemailer/.release-please-config.json +0 -9
- package/packages/mailx-send/node_modules/nodemailer/LICENSE +0 -16
- package/packages/mailx-send/node_modules/nodemailer/README.md +0 -86
- package/packages/mailx-send/node_modules/nodemailer/SECURITY.txt +0 -22
- package/packages/mailx-send/node_modules/nodemailer/eslint.config.js +0 -88
- package/packages/mailx-send/node_modules/nodemailer/lib/addressparser/index.js +0 -383
- package/packages/mailx-send/node_modules/nodemailer/lib/base64/index.js +0 -139
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/index.js +0 -253
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/sign.js +0 -117
- package/packages/mailx-send/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
- package/packages/mailx-send/node_modules/nodemailer/lib/fetch/index.js +0 -280
- package/packages/mailx-send/node_modules/nodemailer/lib/json-transport/index.js +0 -82
- package/packages/mailx-send/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
- package/packages/mailx-send/node_modules/nodemailer/lib/mailer/index.js +0 -441
- package/packages/mailx-send/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
- package/packages/mailx-send/node_modules/nodemailer/lib/nodemailer.js +0 -157
- package/packages/mailx-send/node_modules/nodemailer/lib/punycode/index.js +0 -460
- package/packages/mailx-send/node_modules/nodemailer/lib/qp/index.js +0 -227
- package/packages/mailx-send/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
- package/packages/mailx-send/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
- package/packages/mailx-send/node_modules/nodemailer/lib/shared/index.js +0 -754
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
- package/packages/mailx-send/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
- package/packages/mailx-send/node_modules/nodemailer/lib/well-known/index.js +0 -47
- package/packages/mailx-send/node_modules/nodemailer/lib/well-known/services.json +0 -611
- package/packages/mailx-send/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
- package/packages/mailx-send/node_modules/nodemailer/package.json +0 -47
|
@@ -55,6 +55,13 @@ export class ImapManager extends EventEmitter {
|
|
|
55
55
|
const storePath = getStorePath();
|
|
56
56
|
this.bodyStore = new FileMessageStore(storePath);
|
|
57
57
|
}
|
|
58
|
+
/** Get OAuth access token for an account (for SMTP auth) */
|
|
59
|
+
async getOAuthToken(accountId) {
|
|
60
|
+
const config = this.configs.get(accountId);
|
|
61
|
+
if (!config || !config.tokenProvider)
|
|
62
|
+
return null;
|
|
63
|
+
return config.tokenProvider();
|
|
64
|
+
}
|
|
58
65
|
/** Create a fresh ImapClient for an account (disposable, single-use) */
|
|
59
66
|
createClient(accountId) {
|
|
60
67
|
const config = this.configs.get(accountId);
|
|
@@ -106,12 +113,7 @@ export class ImapManager extends EventEmitter {
|
|
|
106
113
|
this.db.upsertFolder(accountId, folder.path, folder.name || folder.path.split(folder.delimiter || "/").pop() || folder.path, specialUse, folder.delimiter || "/");
|
|
107
114
|
}
|
|
108
115
|
this.emit("syncProgress", accountId, "folders", 100);
|
|
109
|
-
|
|
110
|
-
// Register folder names for human-readable store paths
|
|
111
|
-
for (const f of dbFolders) {
|
|
112
|
-
this.bodyStore.registerFolder(f.id, f.path);
|
|
113
|
-
}
|
|
114
|
-
return dbFolders;
|
|
116
|
+
return this.db.getFolders(accountId);
|
|
115
117
|
}
|
|
116
118
|
/** Sync messages for a specific folder */
|
|
117
119
|
async syncFolder(accountId, folderId, client) {
|
|
@@ -271,8 +273,6 @@ export class ImapManager extends EventEmitter {
|
|
|
271
273
|
const folders = await this.syncFolders(accountId, client);
|
|
272
274
|
await client.logout();
|
|
273
275
|
client = null;
|
|
274
|
-
// Fresh client for message sync (getFolderList corrupts imapflow state)
|
|
275
|
-
client = this.createClient(accountId);
|
|
276
276
|
// INBOX first so it's available fastest
|
|
277
277
|
folders.sort((a, b) => {
|
|
278
278
|
if (a.specialUse === "inbox")
|
|
@@ -281,11 +281,22 @@ export class ImapManager extends EventEmitter {
|
|
|
281
281
|
return 1;
|
|
282
282
|
return 0;
|
|
283
283
|
});
|
|
284
|
+
// Fresh client per folder — IMAP connections drop mid-sync on large accounts
|
|
284
285
|
for (const folder of folders) {
|
|
285
286
|
try {
|
|
287
|
+
client = this.createClient(accountId);
|
|
286
288
|
await this.syncFolder(accountId, folder.id, client);
|
|
289
|
+
await client.logout();
|
|
290
|
+
client = null;
|
|
287
291
|
}
|
|
288
292
|
catch (e) {
|
|
293
|
+
if (client) {
|
|
294
|
+
try {
|
|
295
|
+
await client.logout();
|
|
296
|
+
}
|
|
297
|
+
catch { /* ignore */ }
|
|
298
|
+
client = null;
|
|
299
|
+
}
|
|
289
300
|
if (e.responseText?.includes("doesn't exist")) {
|
|
290
301
|
console.log(` Removing non-existent folder: ${folder.path}`);
|
|
291
302
|
this.db.deleteFolder(folder.id);
|
|
@@ -295,8 +306,6 @@ export class ImapManager extends EventEmitter {
|
|
|
295
306
|
}
|
|
296
307
|
}
|
|
297
308
|
}
|
|
298
|
-
await client.logout();
|
|
299
|
-
client = null;
|
|
300
309
|
this.emit("syncComplete", accountId);
|
|
301
310
|
}
|
|
302
311
|
catch (e) {
|
|
@@ -493,6 +502,38 @@ export class ImapManager extends EventEmitter {
|
|
|
493
502
|
// Try immediate sync
|
|
494
503
|
this.processSyncActions(accountId).catch(() => { });
|
|
495
504
|
}
|
|
505
|
+
/** Move message across accounts using iflow's moveMessageToServer */
|
|
506
|
+
async moveMessageCrossAccount(fromAccountId, uid, fromFolderId, toAccountId, toFolderId) {
|
|
507
|
+
const fromFolders = this.db.getFolders(fromAccountId);
|
|
508
|
+
const fromFolder = fromFolders.find(f => f.id === fromFolderId);
|
|
509
|
+
if (!fromFolder)
|
|
510
|
+
throw new Error(`Source folder ${fromFolderId} not found`);
|
|
511
|
+
const toFolders = this.db.getFolders(toAccountId);
|
|
512
|
+
const toFolder = toFolders.find(f => f.id === toFolderId);
|
|
513
|
+
if (!toFolder)
|
|
514
|
+
throw new Error(`Target folder ${toFolderId} not found`);
|
|
515
|
+
const sourceClient = this.createClient(fromAccountId);
|
|
516
|
+
const targetClient = this.createClient(toAccountId);
|
|
517
|
+
try {
|
|
518
|
+
const msg = await sourceClient.fetchMessageByUid(fromFolder.path, uid, { source: true });
|
|
519
|
+
if (!msg)
|
|
520
|
+
throw new Error(`Message UID ${uid} not found in ${fromFolder.path}`);
|
|
521
|
+
await sourceClient.moveMessageToServer(msg, fromFolder.path, targetClient, toFolder.path);
|
|
522
|
+
// Remove from local DB
|
|
523
|
+
this.db.deleteMessage(fromAccountId, uid);
|
|
524
|
+
console.log(` Cross-account move: ${fromAccountId}/${fromFolder.path} UID ${uid} → ${toAccountId}/${toFolder.path}`);
|
|
525
|
+
}
|
|
526
|
+
finally {
|
|
527
|
+
try {
|
|
528
|
+
await sourceClient.logout();
|
|
529
|
+
}
|
|
530
|
+
catch { /* ignore */ }
|
|
531
|
+
try {
|
|
532
|
+
await targetClient.logout();
|
|
533
|
+
}
|
|
534
|
+
catch { /* ignore */ }
|
|
535
|
+
}
|
|
536
|
+
}
|
|
496
537
|
/** Undelete — move from Trash back to original folder */
|
|
497
538
|
async undeleteMessage(accountId, uid, originalFolderId) {
|
|
498
539
|
const trash = this.findFolder(accountId, "trash");
|
|
@@ -817,13 +858,21 @@ export class ImapManager extends EventEmitter {
|
|
|
817
858
|
}
|
|
818
859
|
// Send via SMTP
|
|
819
860
|
try {
|
|
861
|
+
let smtpAuth;
|
|
862
|
+
if (account.smtp.auth === "password") {
|
|
863
|
+
smtpAuth = { user: account.smtp.user, pass: account.smtp.password };
|
|
864
|
+
}
|
|
865
|
+
else if (account.smtp.auth === "oauth2") {
|
|
866
|
+
const accessToken = await this.getOAuthToken(accountId);
|
|
867
|
+
if (!accessToken)
|
|
868
|
+
throw new Error("OAuth token not available — re-authenticate");
|
|
869
|
+
smtpAuth = { type: "OAuth2", user: account.smtp.user, accessToken };
|
|
870
|
+
}
|
|
820
871
|
const transport = createTransport({
|
|
821
872
|
host: account.smtp.host,
|
|
822
873
|
port: account.smtp.port,
|
|
823
874
|
secure: account.smtp.port === 465,
|
|
824
|
-
auth:
|
|
825
|
-
? { user: account.smtp.user, pass: account.smtp.password }
|
|
826
|
-
: undefined,
|
|
875
|
+
auth: smtpAuth,
|
|
827
876
|
tls: { rejectUnauthorized: false },
|
|
828
877
|
});
|
|
829
878
|
// Parse recipients from raw message headers for SMTP envelope
|
|
@@ -874,10 +923,23 @@ export class ImapManager extends EventEmitter {
|
|
|
874
923
|
catch { /* ignore */ }
|
|
875
924
|
}
|
|
876
925
|
}
|
|
877
|
-
/** Start background Outbox worker —
|
|
926
|
+
/** Start background Outbox worker — runs immediately then every 10 seconds */
|
|
878
927
|
startOutboxWorker() {
|
|
879
928
|
if (this.outboxInterval)
|
|
880
929
|
return;
|
|
930
|
+
// Run once immediately on startup
|
|
931
|
+
const processAll = async () => {
|
|
932
|
+
for (const [accountId] of this.configs) {
|
|
933
|
+
try {
|
|
934
|
+
await this.processLocalQueue(accountId);
|
|
935
|
+
await this.processOutbox(accountId);
|
|
936
|
+
}
|
|
937
|
+
catch (e) {
|
|
938
|
+
console.error(` [outbox] Error for ${accountId}: ${e.message}`);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
setTimeout(() => processAll(), 3000); // 3s after startup (let connections settle)
|
|
881
943
|
this.outboxInterval = setInterval(async () => {
|
|
882
944
|
for (const [accountId] of this.configs) {
|
|
883
945
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -15,5 +15,9 @@
|
|
|
15
15
|
"@bobfrankston/iflow": "file:../../../MailApps/iflow",
|
|
16
16
|
"@bobfrankston/oauthsupport": "file:../../../../projects/oauth/oauthsupport",
|
|
17
17
|
"nodemailer": "^7.0.0"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/BobFrankston/mailx-imap.git"
|
|
18
22
|
}
|
|
19
23
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-send",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Queue-based mail sender with SMTP and OAuth2 support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
"build": "tsc",
|
|
14
14
|
"release": "npmglobalize"
|
|
15
15
|
},
|
|
16
|
-
"keywords": [
|
|
16
|
+
"keywords": [
|
|
17
|
+
"email",
|
|
18
|
+
"smtp",
|
|
19
|
+
"queue",
|
|
20
|
+
"oauth2",
|
|
21
|
+
"gmail"
|
|
22
|
+
],
|
|
17
23
|
"author": "Bob Frankston",
|
|
18
24
|
"license": "MIT",
|
|
19
25
|
"dependencies": {
|
|
@@ -23,5 +29,9 @@
|
|
|
23
29
|
"devDependencies": {
|
|
24
30
|
"@types/nodemailer": "^6.4.0",
|
|
25
31
|
"@types/node": "^22.0.0"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/BobFrankston/mailx-send.git"
|
|
26
36
|
}
|
|
27
37
|
}
|
|
@@ -11,10 +11,10 @@ import { ImapManager } from "@bobfrankston/mailx-imap";
|
|
|
11
11
|
import { createApiRouter } from "@bobfrankston/mailx-api";
|
|
12
12
|
import { loadSettings, getConfigDir, initLocalConfig } from "@bobfrankston/mailx-settings";
|
|
13
13
|
import { ports } from "@bobfrankston/miscinfo";
|
|
14
|
-
import {
|
|
14
|
+
import { createServer } from "node:http";
|
|
15
15
|
const PORT = ports.mailx;
|
|
16
16
|
// ── File logging ──
|
|
17
|
-
const logDir = path.join(process.env.USERPROFILE || process.env.HOME || ".", ".mailx");
|
|
17
|
+
const logDir = path.join(process.env.USERPROFILE || process.env.HOME || ".", ".mailx", "logs");
|
|
18
18
|
fs.mkdirSync(logDir, { recursive: true });
|
|
19
19
|
// Rotate: delete logs older than 7 days
|
|
20
20
|
try {
|
|
@@ -53,7 +53,19 @@ const db = new MailxDB(dbDir);
|
|
|
53
53
|
const imapManager = new ImapManager(db);
|
|
54
54
|
// ── Express App ──
|
|
55
55
|
const app = express();
|
|
56
|
-
app.use(express.json());
|
|
56
|
+
app.use(express.json({ limit: "Infinity" }));
|
|
57
|
+
// Request logging
|
|
58
|
+
app.use((req, res, next) => {
|
|
59
|
+
const start = Date.now();
|
|
60
|
+
res.on("finish", () => {
|
|
61
|
+
const ms = Date.now() - start;
|
|
62
|
+
// Skip noisy polling endpoints
|
|
63
|
+
if (req.path === "/api/sync/pending")
|
|
64
|
+
return;
|
|
65
|
+
console.log(` ${req.method} ${req.path} ${res.statusCode} ${ms}ms`);
|
|
66
|
+
});
|
|
67
|
+
next();
|
|
68
|
+
});
|
|
57
69
|
// Serve client static files
|
|
58
70
|
const clientDir = path.join(import.meta.dirname, "..", "..", "client");
|
|
59
71
|
const rootDir = path.join(import.meta.dirname, "..", "..");
|
|
@@ -97,15 +109,14 @@ ${accountInfo.map((a) => `<tr><td>${a.name}</td><td>${a.folders}</td><td>${a.inb
|
|
|
97
109
|
<p style="margin-top:2rem;font-size:0.8rem"><a href="/">Open mailx</a> | Auto-refreshes every 10s</p>
|
|
98
110
|
</body></html>`);
|
|
99
111
|
});
|
|
100
|
-
//
|
|
112
|
+
// Restart server + reload clients
|
|
101
113
|
app.post("/api/restart", (req, res) => {
|
|
102
114
|
res.json({ ok: true });
|
|
103
115
|
broadcast({ type: "reload" });
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
fs.utimesSync(serverFile, now, now);
|
|
116
|
+
// Graceful shutdown — node --watch will auto-restart
|
|
117
|
+
setTimeout(async () => {
|
|
118
|
+
console.log(" Restart requested via API");
|
|
119
|
+
await shutdown();
|
|
109
120
|
}, 500);
|
|
110
121
|
});
|
|
111
122
|
// SPA fallback
|
|
@@ -113,8 +124,13 @@ app.get("*", (req, res) => {
|
|
|
113
124
|
if (!req.path.startsWith("/api"))
|
|
114
125
|
res.sendFile(path.join(clientDir, "index.html"));
|
|
115
126
|
});
|
|
116
|
-
//
|
|
117
|
-
|
|
127
|
+
// JSON error handler — all errors return JSON, never HTML
|
|
128
|
+
app.use((err, _req, res, _next) => {
|
|
129
|
+
console.error(`ERROR ${err.message}`);
|
|
130
|
+
const status = err.status || err.statusCode || 500;
|
|
131
|
+
res.status(status).json({ error: err.message || "Internal server error" });
|
|
132
|
+
});
|
|
133
|
+
// ── HTTP Server ──
|
|
118
134
|
let server;
|
|
119
135
|
let wss;
|
|
120
136
|
const clients = new Set();
|
|
@@ -147,19 +163,19 @@ async function start() {
|
|
|
147
163
|
const seeded = db.seedContactsFromMessages();
|
|
148
164
|
if (seeded > 0)
|
|
149
165
|
console.log(` Seeded ${seeded} contacts`);
|
|
150
|
-
// Search index —
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
// Search index — rebuild in background after server starts (non-blocking)
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
let ftsCount = 0;
|
|
169
|
+
try {
|
|
170
|
+
ftsCount = db.db.prepare("SELECT COUNT(*) as cnt FROM messages_fts").get()?.cnt || 0;
|
|
171
|
+
}
|
|
172
|
+
catch { /* */ }
|
|
173
|
+
if (ftsCount === 0) {
|
|
154
174
|
const indexed = db.rebuildSearchIndex();
|
|
155
|
-
|
|
175
|
+
if (indexed > 0)
|
|
176
|
+
console.log(` Search index: ${indexed} messages`);
|
|
156
177
|
}
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
// FTS table might not exist yet
|
|
160
|
-
const indexed = db.rebuildSearchIndex();
|
|
161
|
-
console.log(` Search index built: ${indexed} messages`);
|
|
162
|
-
}
|
|
178
|
+
}, 5000);
|
|
163
179
|
// Add configured accounts
|
|
164
180
|
for (const account of settings.accounts) {
|
|
165
181
|
if (!account.enabled)
|
|
@@ -191,17 +207,12 @@ async function start() {
|
|
|
191
207
|
imapManager.startOutboxWorker();
|
|
192
208
|
// Start server — localhost only by default, --external for network access
|
|
193
209
|
const externalAccess = process.argv.includes("--external");
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
hostname: externalAccess ? undefined : "127.0.0.1",
|
|
197
|
-
app,
|
|
198
|
-
certspath,
|
|
199
|
-
msger: (msg) => console.log(` [cert] ${msg}`),
|
|
200
|
-
});
|
|
201
|
-
server = await InitServerAsync(opts);
|
|
210
|
+
const hostname = externalAccess ? "0.0.0.0" : "127.0.0.1";
|
|
211
|
+
server = createServer(app);
|
|
202
212
|
wss = new WebSocketServer({ server });
|
|
203
213
|
wireWebSocket();
|
|
204
|
-
|
|
214
|
+
await new Promise((resolve) => server.listen(PORT, hostname, resolve));
|
|
215
|
+
console.log(`mailx server running on http://${hostname}:${PORT}`);
|
|
205
216
|
}
|
|
206
217
|
// ── Graceful Shutdown ──
|
|
207
218
|
async function shutdown() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -14,12 +14,15 @@
|
|
|
14
14
|
"@bobfrankston/mailx-imap": "file:../mailx-imap",
|
|
15
15
|
"@bobfrankston/mailx-api": "file:../mailx-api",
|
|
16
16
|
"@bobfrankston/mailx-settings": "file:../mailx-settings",
|
|
17
|
-
"@bobfrankston/certsupport": "file:../../../../projects/nodejs/certsupport",
|
|
18
17
|
"express": "^4.21.0",
|
|
19
18
|
"ws": "^8.18.0"
|
|
20
19
|
},
|
|
21
20
|
"devDependencies": {
|
|
22
21
|
"@types/express": "^5.0.0",
|
|
23
22
|
"@types/ws": "^8.5.13"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/BobFrankston/mailx-server.git"
|
|
24
27
|
}
|
|
25
28
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* allowlist.jsonc — remote content allow-list
|
|
9
9
|
*
|
|
10
10
|
* Local overrides (~/.mailx/):
|
|
11
|
-
* config.
|
|
11
|
+
* config.jsonc — pointer to shared dir + local-only settings (storePath, historyDays)
|
|
12
12
|
* accounts.jsonc — cached copy, fallback when shared unavailable
|
|
13
13
|
* preferences.jsonc — local overrides merged on top of shared
|
|
14
14
|
* allowlist.jsonc — cached copy
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* allowlist.jsonc — remote content allow-list
|
|
9
9
|
*
|
|
10
10
|
* Local overrides (~/.mailx/):
|
|
11
|
-
* config.
|
|
11
|
+
* config.jsonc — pointer to shared dir + local-only settings (storePath, historyDays)
|
|
12
12
|
* accounts.jsonc — cached copy, fallback when shared unavailable
|
|
13
13
|
* preferences.jsonc — local overrides merged on top of shared
|
|
14
14
|
* allowlist.jsonc — cached copy
|
|
@@ -20,17 +20,17 @@ import * as path from "node:path";
|
|
|
20
20
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
21
21
|
// ── Paths ──
|
|
22
22
|
const LOCAL_DIR = path.join(process.env.USERPROFILE || process.env.HOME || ".", ".mailx");
|
|
23
|
-
const LOCAL_CONFIG_PATH = path.join(LOCAL_DIR, "config.
|
|
23
|
+
const LOCAL_CONFIG_PATH = path.join(LOCAL_DIR, "config.jsonc");
|
|
24
|
+
const LEGACY_CONFIG_PATH = path.join(LOCAL_DIR, "config.json");
|
|
24
25
|
const DEFAULT_STORE_PATH = path.join(LOCAL_DIR, "mailxstore");
|
|
25
26
|
function readLocalConfig() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return JSON.parse(fs.readFileSync(LOCAL_CONFIG_PATH, "utf-8"));
|
|
27
|
+
// Migrate config.json → config.jsonc
|
|
28
|
+
if (!fs.existsSync(LOCAL_CONFIG_PATH) && fs.existsSync(LEGACY_CONFIG_PATH)) {
|
|
29
|
+
fs.renameSync(LEGACY_CONFIG_PATH, LOCAL_CONFIG_PATH);
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
if (!fs.existsSync(LOCAL_CONFIG_PATH))
|
|
32
32
|
return {};
|
|
33
|
-
}
|
|
33
|
+
return readJsonc(LOCAL_CONFIG_PATH) || {};
|
|
34
34
|
}
|
|
35
35
|
function getSharedDir() {
|
|
36
36
|
const config = readLocalConfig();
|
|
@@ -42,14 +42,23 @@ function getSharedDir() {
|
|
|
42
42
|
return LOCAL_DIR;
|
|
43
43
|
}
|
|
44
44
|
// ── File helpers ──
|
|
45
|
+
/** Read JSON or JSONC file. If exact path not found, tries .json/.jsonc variant. */
|
|
45
46
|
function readJsonc(filePath) {
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
let actual = filePath;
|
|
48
|
+
if (!fs.existsSync(actual)) {
|
|
49
|
+
// Try alternate extension
|
|
50
|
+
if (actual.endsWith(".jsonc"))
|
|
51
|
+
actual = actual.replace(/\.jsonc$/, ".json");
|
|
52
|
+
else if (actual.endsWith(".json"))
|
|
53
|
+
actual = actual.replace(/\.json$/, ".jsonc");
|
|
54
|
+
if (!fs.existsSync(actual))
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
48
57
|
try {
|
|
49
|
-
return parseJsonc(fs.readFileSync(
|
|
58
|
+
return parseJsonc(fs.readFileSync(actual, "utf-8"));
|
|
50
59
|
}
|
|
51
60
|
catch (e) {
|
|
52
|
-
console.error(`Failed to read ${
|
|
61
|
+
console.error(`Failed to read ${actual}: ${e.message}`);
|
|
53
62
|
return null;
|
|
54
63
|
}
|
|
55
64
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-settings",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -12,5 +12,9 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@bobfrankston/mailx-types": "file:../mailx-types",
|
|
14
14
|
"jsonc-parser": "^3.3.1"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/BobFrankston/mailx-settings.git"
|
|
15
19
|
}
|
|
16
20
|
}
|
|
@@ -44,7 +44,9 @@ export declare class MailxDB {
|
|
|
44
44
|
bodyPath: string;
|
|
45
45
|
}): number;
|
|
46
46
|
getMessages(query: MessageQuery): PagedResult<MessageEnvelope>;
|
|
47
|
-
|
|
47
|
+
/** Unified inbox: all inbox folders across accounts, sorted by date, paginated in SQL */
|
|
48
|
+
getUnifiedInbox(page?: number, pageSize?: number): PagedResult<MessageEnvelope>;
|
|
49
|
+
getMessageByUid(accountId: string, uid: number, folderId?: number): MessageEnvelope;
|
|
48
50
|
getMessageBodyPath(accountId: string, uid: number): string;
|
|
49
51
|
updateMessageFlags(accountId: string, uid: number, flags: string[]): void;
|
|
50
52
|
getHighestUid(accountId: string, folderId: number): number;
|
|
@@ -52,6 +54,8 @@ export declare class MailxDB {
|
|
|
52
54
|
getUidsForFolder(accountId: string, folderId: number): number[];
|
|
53
55
|
/** Delete a message by account + UID */
|
|
54
56
|
deleteMessage(accountId: string, uid: number): void;
|
|
57
|
+
/** Recalculate folder total/unread counts from actual messages */
|
|
58
|
+
recalcFolderCounts(folderId: number): void;
|
|
55
59
|
/** Bulk insert within a transaction for sync performance */
|
|
56
60
|
beginTransaction(): void;
|
|
57
61
|
commitTransaction(): void;
|
|
@@ -241,8 +241,43 @@ export class MailxDB {
|
|
|
241
241
|
}));
|
|
242
242
|
return { items, total, page, pageSize };
|
|
243
243
|
}
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
/** Unified inbox: all inbox folders across accounts, sorted by date, paginated in SQL */
|
|
245
|
+
getUnifiedInbox(page = 1, pageSize = 50) {
|
|
246
|
+
const offset = (page - 1) * pageSize;
|
|
247
|
+
// Find all inbox folder IDs
|
|
248
|
+
const inboxRows = this.db.prepare("SELECT id FROM folders WHERE special_use = 'inbox'").all();
|
|
249
|
+
if (inboxRows.length === 0)
|
|
250
|
+
return { items: [], total: 0, page, pageSize };
|
|
251
|
+
const placeholders = inboxRows.map(() => "?").join(",");
|
|
252
|
+
const folderIds = inboxRows.map((r) => r.id);
|
|
253
|
+
const total = this.db.prepare(`SELECT COUNT(*) as cnt FROM messages WHERE folder_id IN (${placeholders})`).get(...folderIds).cnt;
|
|
254
|
+
const rows = this.db.prepare(`SELECT * FROM messages WHERE folder_id IN (${placeholders}) ORDER BY date DESC LIMIT ? OFFSET ?`).all(...folderIds, pageSize, offset);
|
|
255
|
+
const items = rows.map(r => ({
|
|
256
|
+
id: r.id,
|
|
257
|
+
accountId: r.account_id,
|
|
258
|
+
folderId: r.folder_id,
|
|
259
|
+
uid: r.uid,
|
|
260
|
+
messageId: r.message_id || "",
|
|
261
|
+
inReplyTo: r.in_reply_to || "",
|
|
262
|
+
references: JSON.parse(r.refs || "[]"),
|
|
263
|
+
date: r.date,
|
|
264
|
+
subject: r.subject,
|
|
265
|
+
from: { name: r.from_name, address: r.from_address },
|
|
266
|
+
to: JSON.parse(r.to_json),
|
|
267
|
+
cc: JSON.parse(r.cc_json),
|
|
268
|
+
flags: JSON.parse(r.flags_json),
|
|
269
|
+
size: r.size,
|
|
270
|
+
hasAttachments: !!r.has_attachments,
|
|
271
|
+
preview: r.preview
|
|
272
|
+
}));
|
|
273
|
+
return { items, total, page, pageSize };
|
|
274
|
+
}
|
|
275
|
+
getMessageByUid(accountId, uid, folderId) {
|
|
276
|
+
const sql = folderId != null
|
|
277
|
+
? "SELECT * FROM messages WHERE account_id = ? AND uid = ? AND folder_id = ?"
|
|
278
|
+
: "SELECT * FROM messages WHERE account_id = ? AND uid = ?";
|
|
279
|
+
const params = folderId != null ? [accountId, uid, folderId] : [accountId, uid];
|
|
280
|
+
const r = this.db.prepare(sql).get(...params);
|
|
246
281
|
if (!r)
|
|
247
282
|
return null;
|
|
248
283
|
return {
|
|
@@ -282,7 +317,19 @@ export class MailxDB {
|
|
|
282
317
|
}
|
|
283
318
|
/** Delete a message by account + UID */
|
|
284
319
|
deleteMessage(accountId, uid) {
|
|
320
|
+
// Get folderId before deleting so we can update counts
|
|
321
|
+
const msg = this.db.prepare("SELECT folder_id FROM messages WHERE account_id = ? AND uid = ?").get(accountId, uid);
|
|
285
322
|
this.db.prepare("DELETE FROM messages WHERE account_id = ? AND uid = ?").run(accountId, uid);
|
|
323
|
+
// Refresh folder counts
|
|
324
|
+
if (msg)
|
|
325
|
+
this.recalcFolderCounts(msg.folder_id);
|
|
326
|
+
}
|
|
327
|
+
/** Recalculate folder total/unread counts from actual messages */
|
|
328
|
+
recalcFolderCounts(folderId) {
|
|
329
|
+
const counts = this.db.prepare(`SELECT COUNT(*) as total,
|
|
330
|
+
SUM(CASE WHEN flags_json NOT LIKE '%\\\\Seen%' THEN 1 ELSE 0 END) as unread
|
|
331
|
+
FROM messages WHERE folder_id = ?`).get(folderId);
|
|
332
|
+
this.updateFolderCounts(folderId, counts?.total || 0, counts?.unread || 0);
|
|
286
333
|
}
|
|
287
334
|
/** Bulk insert within a transaction for sync performance */
|
|
288
335
|
beginTransaction() { this.db.exec("BEGIN"); }
|
|
@@ -401,19 +448,24 @@ export class MailxDB {
|
|
|
401
448
|
subject, from_name, from_address, to_text, cc_text, body_text,
|
|
402
449
|
content=messages, content_rowid=id
|
|
403
450
|
)`);
|
|
451
|
+
// Use a single transaction + prepared statement for speed (~50x faster than individual inserts)
|
|
452
|
+
const insert = this.db.prepare("INSERT INTO messages_fts (rowid, subject, from_name, from_address, to_text, cc_text, body_text) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
|
404
453
|
const rows = this.db.prepare("SELECT id, subject, from_name, from_address, to_json, cc_json, preview FROM messages").all();
|
|
405
454
|
let count = 0;
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
455
|
+
const batchInsert = this.db.transaction(() => {
|
|
456
|
+
for (const r of rows) {
|
|
457
|
+
const to = JSON.parse(r.to_json || "[]");
|
|
458
|
+
const cc = JSON.parse(r.cc_json || "[]");
|
|
459
|
+
const toText = to.map((a) => `${a.name} ${a.address}`).join(" ");
|
|
460
|
+
const ccText = cc.map((a) => `${a.name} ${a.address}`).join(" ");
|
|
461
|
+
try {
|
|
462
|
+
insert.run(r.id, r.subject, r.from_name, r.from_address, toText, ccText, r.preview);
|
|
463
|
+
count++;
|
|
464
|
+
}
|
|
465
|
+
catch { /* skip duplicates */ }
|
|
414
466
|
}
|
|
415
|
-
|
|
416
|
-
|
|
467
|
+
});
|
|
468
|
+
batchInsert();
|
|
417
469
|
return count;
|
|
418
470
|
}
|
|
419
471
|
// ── Sync Actions ──
|
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File-per-message body storage backend.
|
|
3
|
-
* Messages stored as: {basePath}/{accountId}/{
|
|
4
|
-
*
|
|
3
|
+
* Messages stored as: {basePath}/{accountId}/{folderId}/{uid}.eml
|
|
4
|
+
* Folder IDs are numeric (from SQLite), not names.
|
|
5
5
|
*/
|
|
6
6
|
import type { MessageStore } from "@bobfrankston/mailx-types";
|
|
7
7
|
export declare class FileMessageStore implements MessageStore {
|
|
8
8
|
private basePath;
|
|
9
|
-
private folderNames;
|
|
10
9
|
constructor(basePath: string);
|
|
11
|
-
/** Register folder ID → path mapping for human-readable directory names */
|
|
12
|
-
registerFolder(folderId: number, folderPath: string): void;
|
|
13
|
-
private folderDir;
|
|
14
10
|
private messagePath;
|
|
15
|
-
/** Check legacy path (numeric folder ID) */
|
|
16
|
-
private legacyPath;
|
|
17
11
|
putMessage(accountId: string, folderId: number, uid: number, raw: Buffer): Promise<string>;
|
|
18
12
|
getMessage(accountId: string, folderId: number, uid: number): Promise<Buffer>;
|
|
19
13
|
deleteMessage(accountId: string, folderId: number, uid: number): Promise<void>;
|
|
@@ -1,34 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File-per-message body storage backend.
|
|
3
|
-
* Messages stored as: {basePath}/{accountId}/{
|
|
4
|
-
*
|
|
3
|
+
* Messages stored as: {basePath}/{accountId}/{folderId}/{uid}.eml
|
|
4
|
+
* Folder IDs are numeric (from SQLite), not names.
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
/** Sanitize folder path for use as directory name — replace delimiters with _ */
|
|
9
|
-
function sanitizeFolderName(folderPath) {
|
|
10
|
-
return folderPath.replace(/[\/\\.:]/g, "_");
|
|
11
|
-
}
|
|
12
8
|
export class FileMessageStore {
|
|
13
9
|
basePath;
|
|
14
|
-
folderNames = new Map();
|
|
15
10
|
constructor(basePath) {
|
|
16
11
|
this.basePath = basePath;
|
|
17
12
|
fs.mkdirSync(basePath, { recursive: true });
|
|
18
13
|
}
|
|
19
|
-
/** Register folder ID → path mapping for human-readable directory names */
|
|
20
|
-
registerFolder(folderId, folderPath) {
|
|
21
|
-
this.folderNames.set(folderId, sanitizeFolderName(folderPath));
|
|
22
|
-
}
|
|
23
|
-
folderDir(accountId, folderId) {
|
|
24
|
-
const name = this.folderNames.get(folderId) || String(folderId);
|
|
25
|
-
return path.join(this.basePath, accountId, name);
|
|
26
|
-
}
|
|
27
14
|
messagePath(accountId, folderId, uid) {
|
|
28
|
-
return path.join(this.folderDir(accountId, folderId), `${uid}.eml`);
|
|
29
|
-
}
|
|
30
|
-
/** Check legacy path (numeric folder ID) */
|
|
31
|
-
legacyPath(accountId, folderId, uid) {
|
|
32
15
|
return path.join(this.basePath, accountId, String(folderId), `${uid}.eml`);
|
|
33
16
|
}
|
|
34
17
|
async putMessage(accountId, folderId, uid, raw) {
|
|
@@ -38,22 +21,15 @@ export class FileMessageStore {
|
|
|
38
21
|
return filePath;
|
|
39
22
|
}
|
|
40
23
|
async getMessage(accountId, folderId, uid) {
|
|
41
|
-
|
|
42
|
-
if (fs.existsSync(filePath))
|
|
43
|
-
return fs.readFileSync(filePath);
|
|
44
|
-
// Fallback to legacy path
|
|
45
|
-
const legacy = this.legacyPath(accountId, folderId, uid);
|
|
46
|
-
return fs.readFileSync(legacy);
|
|
24
|
+
return fs.readFileSync(this.messagePath(accountId, folderId, uid));
|
|
47
25
|
}
|
|
48
26
|
async deleteMessage(accountId, folderId, uid) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
27
|
+
const filePath = this.messagePath(accountId, folderId, uid);
|
|
28
|
+
if (fs.existsSync(filePath))
|
|
29
|
+
fs.unlinkSync(filePath);
|
|
53
30
|
}
|
|
54
31
|
async hasMessage(accountId, folderId, uid) {
|
|
55
|
-
return fs.existsSync(this.messagePath(accountId, folderId, uid))
|
|
56
|
-
fs.existsSync(this.legacyPath(accountId, folderId, uid));
|
|
32
|
+
return fs.existsSync(this.messagePath(accountId, folderId, uid));
|
|
57
33
|
}
|
|
58
34
|
}
|
|
59
35
|
//# sourceMappingURL=file-store.js.map
|