@bobfrankston/mailx 1.0.1
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/.tswalk.json +3366 -0
- package/README.md +160 -0
- package/bin/mailx.js +58 -0
- package/client/app.js +431 -0
- package/client/components/folder-tree.js +286 -0
- package/client/components/message-list.js +230 -0
- package/client/components/message-viewer.js +161 -0
- package/client/compose/compose.css +197 -0
- package/client/compose/compose.html +45 -0
- package/client/compose/compose.js +335 -0
- package/client/favicon.svg +11 -0
- package/client/index.html +100 -0
- package/client/lib/api-client.js +67 -0
- package/client/package.json +9 -0
- package/client/styles/components.css +361 -0
- package/client/styles/layout.css +69 -0
- package/launch.ps1 +20 -0
- package/launcher/Cargo.lock +3167 -0
- package/launcher/Cargo.toml +13 -0
- package/launcher/build.cmd +4 -0
- package/launcher/build.rs +8 -0
- package/launcher/mailx.ico +0 -0
- package/launcher/release.cmd +4 -0
- package/launcher/src/main.rs +371 -0
- package/mailx.cmd +2 -0
- package/mailx.db +0 -0
- package/mailx.db-shm +0 -0
- package/mailx.db-wal +0 -0
- package/mailx.json +9 -0
- package/package.json +51 -0
- package/packages/mailx-api/index.d.ts +9 -0
- package/packages/mailx-api/index.js +375 -0
- package/packages/mailx-api/node_modules/nodemailer/.ncurc.js +9 -0
- package/packages/mailx-api/node_modules/nodemailer/.prettierignore +8 -0
- package/packages/mailx-api/node_modules/nodemailer/.prettierrc +12 -0
- package/packages/mailx-api/node_modules/nodemailer/.prettierrc.js +10 -0
- package/packages/mailx-api/node_modules/nodemailer/.release-please-config.json +9 -0
- package/packages/mailx-api/node_modules/nodemailer/LICENSE +16 -0
- package/packages/mailx-api/node_modules/nodemailer/README.md +86 -0
- package/packages/mailx-api/node_modules/nodemailer/SECURITY.txt +22 -0
- package/packages/mailx-api/node_modules/nodemailer/eslint.config.js +88 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/addressparser/index.js +383 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/base64/index.js +139 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/index.js +253 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/sign.js +117 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/fetch/index.js +280 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/json-transport/index.js +82 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/mail-composer/index.js +629 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/mailer/index.js +441 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/mailer/mail-message.js +316 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2113 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/index.js +1316 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/nodemailer.js +157 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/punycode/index.js +460 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/qp/index.js +227 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/ses-transport/index.js +234 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/shared/index.js +754 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/index.js +1870 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/index.js +652 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +259 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-transport/index.js +421 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/well-known/index.js +47 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/well-known/services.json +611 -0
- package/packages/mailx-api/node_modules/nodemailer/lib/xoauth2/index.js +427 -0
- package/packages/mailx-api/node_modules/nodemailer/package.json +47 -0
- package/packages/mailx-api/package.json +24 -0
- package/packages/mailx-api/tsconfig.tsbuildinfo +1 -0
- package/packages/mailx-compose/index.d.ts +21 -0
- package/packages/mailx-compose/index.js +61 -0
- package/packages/mailx-compose/package.json +14 -0
- package/packages/mailx-imap/index.d.ts +105 -0
- package/packages/mailx-imap/index.js +1026 -0
- package/packages/mailx-imap/node_modules/nodemailer/.ncurc.js +9 -0
- package/packages/mailx-imap/node_modules/nodemailer/.prettierignore +8 -0
- package/packages/mailx-imap/node_modules/nodemailer/.prettierrc +12 -0
- package/packages/mailx-imap/node_modules/nodemailer/.prettierrc.js +10 -0
- package/packages/mailx-imap/node_modules/nodemailer/.release-please-config.json +9 -0
- package/packages/mailx-imap/node_modules/nodemailer/LICENSE +16 -0
- package/packages/mailx-imap/node_modules/nodemailer/README.md +86 -0
- package/packages/mailx-imap/node_modules/nodemailer/SECURITY.txt +22 -0
- package/packages/mailx-imap/node_modules/nodemailer/eslint.config.js +88 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/addressparser/index.js +383 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/base64/index.js +139 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/index.js +253 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/sign.js +117 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/index.js +280 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/json-transport/index.js +82 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/mail-composer/index.js +629 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/index.js +441 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/mail-message.js +316 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2113 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/index.js +1316 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/nodemailer.js +157 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/punycode/index.js +460 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/qp/index.js +227 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/ses-transport/index.js +234 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/shared/index.js +754 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/index.js +1870 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/index.js +652 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +259 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-transport/index.js +421 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/index.js +47 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/services.json +611 -0
- package/packages/mailx-imap/node_modules/nodemailer/lib/xoauth2/index.js +427 -0
- package/packages/mailx-imap/node_modules/nodemailer/package.json +47 -0
- package/packages/mailx-imap/package.json +19 -0
- package/packages/mailx-imap/tsconfig.tsbuildinfo +1 -0
- package/packages/mailx-send/README.md +217 -0
- package/packages/mailx-send/cli-queue.d.ts +11 -0
- package/packages/mailx-send/cli-queue.js +59 -0
- package/packages/mailx-send/cli-send.d.ts +17 -0
- package/packages/mailx-send/cli-send.js +75 -0
- package/packages/mailx-send/cli.d.ts +22 -0
- package/packages/mailx-send/cli.js +115 -0
- package/packages/mailx-send/index.d.ts +77 -0
- package/packages/mailx-send/index.js +264 -0
- package/packages/mailx-send/mailsend/README.md +133 -0
- package/packages/mailx-send/mailsend/cli.d.ts +12 -0
- package/packages/mailx-send/mailsend/cli.js +70 -0
- package/packages/mailx-send/mailsend/index.d.ts +77 -0
- package/packages/mailx-send/mailsend/index.js +264 -0
- package/packages/mailx-send/mailsend/node_modules/.package-lock.json +49 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/LICENSE +21 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/README.md +15 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/assert/strict.d.ts +111 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/assert.d.ts +1078 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/async_hooks.d.ts +603 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/buffer.buffer.d.ts +472 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/buffer.d.ts +1934 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/child_process.d.ts +1476 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/cluster.d.ts +578 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/compatibility/disposable.d.ts +14 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/compatibility/index.d.ts +9 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/compatibility/indexable.d.ts +20 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/compatibility/iterators.d.ts +20 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/console.d.ts +452 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/constants.d.ts +21 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/crypto.d.ts +4545 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/dgram.d.ts +600 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/diagnostics_channel.d.ts +578 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/dns/promises.d.ts +503 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/dns.d.ts +923 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/domain.d.ts +170 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/events.d.ts +976 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/fs/promises.d.ts +1295 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/fs.d.ts +4461 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/globals.d.ts +172 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/globals.typedarray.d.ts +38 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/http.d.ts +2089 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/http2.d.ts +2644 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/https.d.ts +579 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/index.d.ts +97 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/inspector.d.ts +253 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/inspector.generated.d.ts +4052 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/module.d.ts +891 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/net.d.ts +1057 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/os.d.ts +506 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/package.json +145 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/path.d.ts +200 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/perf_hooks.d.ts +968 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/process.d.ts +2084 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/punycode.d.ts +117 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/querystring.d.ts +152 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/readline/promises.d.ts +161 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/readline.d.ts +594 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/repl.d.ts +428 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/sea.d.ts +153 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/sqlite.d.ts +721 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/stream/consumers.d.ts +38 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/stream/promises.d.ts +90 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/stream/web.d.ts +622 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/stream.d.ts +1664 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/string_decoder.d.ts +67 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/test.d.ts +2163 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/timers/promises.d.ts +108 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/timers.d.ts +287 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/tls.d.ts +1319 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/trace_events.d.ts +197 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +468 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +34 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/ts5.6/index.d.ts +97 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/tty.d.ts +208 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/url.d.ts +984 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/util.d.ts +2606 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/v8.d.ts +920 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/vm.d.ts +1000 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/wasi.d.ts +181 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/abortcontroller.d.ts +34 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/events.d.ts +97 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/fetch.d.ts +55 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/navigator.d.ts +22 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/storage.d.ts +24 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/worker_threads.d.ts +784 -0
- package/packages/mailx-send/mailsend/node_modules/@types/node/zlib.d.ts +747 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/LICENSE +21 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/README.md +15 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/index.d.ts +82 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/addressparser/index.d.ts +31 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/base64/index.d.ts +22 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/dkim/index.d.ts +45 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/dkim/message-parser.d.ts +75 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/dkim/relaxed-body.d.ts +75 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/dkim/sign.d.ts +21 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/fetch/cookies.d.ts +54 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/fetch/index.d.ts +38 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/json-transport/index.d.ts +53 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mail-composer/index.d.ts +25 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mailer/index.d.ts +283 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mailer/mail-message.d.ts +32 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mime-funcs/index.d.ts +87 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mime-funcs/mime-types.d.ts +2 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mime-node/index.d.ts +224 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mime-node/last-newline.d.ts +9 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/qp/index.d.ts +23 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/sendmail-transport/index.d.ts +53 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/sendmail-transport/le-unix.d.ts +7 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/sendmail-transport/le-windows.d.ts +7 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/ses-transport/index.d.ts +146 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/shared/index.d.ts +58 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-connection/data-stream.d.ts +11 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-connection/http-proxy-client.d.ts +16 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-connection/index.d.ts +270 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-pool/index.d.ts +93 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-pool/pool-resource.d.ts +66 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-transport/index.d.ts +115 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/stream-transport/index.d.ts +59 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/well-known/index.d.ts +6 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/xoauth2/index.d.ts +114 -0
- package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/package.json +38 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/.ncurc.js +9 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/.prettierignore +8 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/.prettierrc +12 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/.prettierrc.js +10 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/.release-please-config.json +9 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/LICENSE +16 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/README.md +86 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/SECURITY.txt +22 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/eslint.config.js +88 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/addressparser/index.js +383 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/base64/index.js +139 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/dkim/index.js +253 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/dkim/sign.js +117 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/fetch/index.js +280 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/json-transport/index.js +82 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mail-composer/index.js +629 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mailer/index.js +441 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mailer/mail-message.js +316 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2113 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-node/index.js +1316 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/nodemailer.js +157 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/punycode/index.js +460 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/qp/index.js +227 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/ses-transport/index.js +234 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/shared/index.js +754 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-connection/index.js +1870 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-pool/index.js +652 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +259 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-transport/index.js +421 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/well-known/index.js +47 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/well-known/services.json +611 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/xoauth2/index.js +427 -0
- package/packages/mailx-send/mailsend/node_modules/nodemailer/package.json +47 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/LICENSE +21 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/README.md +6 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/agent.d.ts +31 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/api.d.ts +43 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/balanced-pool.d.ts +29 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/cache.d.ts +36 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/client.d.ts +108 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/connector.d.ts +34 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/content-type.d.ts +21 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/cookies.d.ts +28 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/diagnostics-channel.d.ts +66 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/dispatcher.d.ts +256 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/env-http-proxy-agent.d.ts +21 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/errors.d.ts +149 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/eventsource.d.ts +61 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/fetch.d.ts +209 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/file.d.ts +39 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/filereader.d.ts +54 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/formdata.d.ts +108 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/global-dispatcher.d.ts +9 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/global-origin.d.ts +7 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/handlers.d.ts +15 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/header.d.ts +4 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/index.d.ts +71 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/interceptors.d.ts +17 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/mock-agent.d.ts +50 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/mock-client.d.ts +25 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/mock-errors.d.ts +12 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/mock-interceptor.d.ts +93 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/mock-pool.d.ts +25 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/package.json +55 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/patch.d.ts +33 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/pool-stats.d.ts +19 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/pool.d.ts +39 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/proxy-agent.d.ts +28 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/readable.d.ts +65 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/retry-agent.d.ts +8 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/retry-handler.d.ts +116 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/util.d.ts +18 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/webidl.d.ts +228 -0
- package/packages/mailx-send/mailsend/node_modules/undici-types/websocket.d.ts +150 -0
- package/packages/mailx-send/mailsend/package.json +26 -0
- package/packages/mailx-send/node_modules/.package-lock.json +49 -0
- package/packages/mailx-send/node_modules/@types/node/LICENSE +21 -0
- package/packages/mailx-send/node_modules/@types/node/README.md +15 -0
- package/packages/mailx-send/node_modules/@types/node/assert/strict.d.ts +111 -0
- package/packages/mailx-send/node_modules/@types/node/assert.d.ts +1078 -0
- package/packages/mailx-send/node_modules/@types/node/async_hooks.d.ts +603 -0
- package/packages/mailx-send/node_modules/@types/node/buffer.buffer.d.ts +472 -0
- package/packages/mailx-send/node_modules/@types/node/buffer.d.ts +1934 -0
- package/packages/mailx-send/node_modules/@types/node/child_process.d.ts +1476 -0
- package/packages/mailx-send/node_modules/@types/node/cluster.d.ts +578 -0
- package/packages/mailx-send/node_modules/@types/node/compatibility/disposable.d.ts +14 -0
- package/packages/mailx-send/node_modules/@types/node/compatibility/index.d.ts +9 -0
- package/packages/mailx-send/node_modules/@types/node/compatibility/indexable.d.ts +20 -0
- package/packages/mailx-send/node_modules/@types/node/compatibility/iterators.d.ts +20 -0
- package/packages/mailx-send/node_modules/@types/node/console.d.ts +452 -0
- package/packages/mailx-send/node_modules/@types/node/constants.d.ts +21 -0
- package/packages/mailx-send/node_modules/@types/node/crypto.d.ts +4545 -0
- package/packages/mailx-send/node_modules/@types/node/dgram.d.ts +600 -0
- package/packages/mailx-send/node_modules/@types/node/diagnostics_channel.d.ts +578 -0
- package/packages/mailx-send/node_modules/@types/node/dns/promises.d.ts +503 -0
- package/packages/mailx-send/node_modules/@types/node/dns.d.ts +923 -0
- package/packages/mailx-send/node_modules/@types/node/domain.d.ts +170 -0
- package/packages/mailx-send/node_modules/@types/node/events.d.ts +976 -0
- package/packages/mailx-send/node_modules/@types/node/fs/promises.d.ts +1295 -0
- package/packages/mailx-send/node_modules/@types/node/fs.d.ts +4461 -0
- package/packages/mailx-send/node_modules/@types/node/globals.d.ts +172 -0
- package/packages/mailx-send/node_modules/@types/node/globals.typedarray.d.ts +38 -0
- package/packages/mailx-send/node_modules/@types/node/http.d.ts +2089 -0
- package/packages/mailx-send/node_modules/@types/node/http2.d.ts +2644 -0
- package/packages/mailx-send/node_modules/@types/node/https.d.ts +579 -0
- package/packages/mailx-send/node_modules/@types/node/index.d.ts +97 -0
- package/packages/mailx-send/node_modules/@types/node/inspector.d.ts +253 -0
- package/packages/mailx-send/node_modules/@types/node/inspector.generated.d.ts +4052 -0
- package/packages/mailx-send/node_modules/@types/node/module.d.ts +891 -0
- package/packages/mailx-send/node_modules/@types/node/net.d.ts +1057 -0
- package/packages/mailx-send/node_modules/@types/node/os.d.ts +506 -0
- package/packages/mailx-send/node_modules/@types/node/package.json +145 -0
- package/packages/mailx-send/node_modules/@types/node/path.d.ts +200 -0
- package/packages/mailx-send/node_modules/@types/node/perf_hooks.d.ts +968 -0
- package/packages/mailx-send/node_modules/@types/node/process.d.ts +2084 -0
- package/packages/mailx-send/node_modules/@types/node/punycode.d.ts +117 -0
- package/packages/mailx-send/node_modules/@types/node/querystring.d.ts +152 -0
- package/packages/mailx-send/node_modules/@types/node/readline/promises.d.ts +161 -0
- package/packages/mailx-send/node_modules/@types/node/readline.d.ts +594 -0
- package/packages/mailx-send/node_modules/@types/node/repl.d.ts +428 -0
- package/packages/mailx-send/node_modules/@types/node/sea.d.ts +153 -0
- package/packages/mailx-send/node_modules/@types/node/sqlite.d.ts +721 -0
- package/packages/mailx-send/node_modules/@types/node/stream/consumers.d.ts +38 -0
- package/packages/mailx-send/node_modules/@types/node/stream/promises.d.ts +90 -0
- package/packages/mailx-send/node_modules/@types/node/stream/web.d.ts +622 -0
- package/packages/mailx-send/node_modules/@types/node/stream.d.ts +1664 -0
- package/packages/mailx-send/node_modules/@types/node/string_decoder.d.ts +67 -0
- package/packages/mailx-send/node_modules/@types/node/test.d.ts +2163 -0
- package/packages/mailx-send/node_modules/@types/node/timers/promises.d.ts +108 -0
- package/packages/mailx-send/node_modules/@types/node/timers.d.ts +287 -0
- package/packages/mailx-send/node_modules/@types/node/tls.d.ts +1319 -0
- package/packages/mailx-send/node_modules/@types/node/trace_events.d.ts +197 -0
- package/packages/mailx-send/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +468 -0
- package/packages/mailx-send/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +34 -0
- package/packages/mailx-send/node_modules/@types/node/ts5.6/index.d.ts +97 -0
- package/packages/mailx-send/node_modules/@types/node/tty.d.ts +208 -0
- package/packages/mailx-send/node_modules/@types/node/url.d.ts +984 -0
- package/packages/mailx-send/node_modules/@types/node/util.d.ts +2606 -0
- package/packages/mailx-send/node_modules/@types/node/v8.d.ts +920 -0
- package/packages/mailx-send/node_modules/@types/node/vm.d.ts +1000 -0
- package/packages/mailx-send/node_modules/@types/node/wasi.d.ts +181 -0
- package/packages/mailx-send/node_modules/@types/node/web-globals/abortcontroller.d.ts +34 -0
- package/packages/mailx-send/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
- package/packages/mailx-send/node_modules/@types/node/web-globals/events.d.ts +97 -0
- package/packages/mailx-send/node_modules/@types/node/web-globals/fetch.d.ts +55 -0
- package/packages/mailx-send/node_modules/@types/node/web-globals/navigator.d.ts +22 -0
- package/packages/mailx-send/node_modules/@types/node/web-globals/storage.d.ts +24 -0
- package/packages/mailx-send/node_modules/@types/node/worker_threads.d.ts +784 -0
- package/packages/mailx-send/node_modules/@types/node/zlib.d.ts +747 -0
- package/packages/mailx-send/node_modules/nodemailer/.ncurc.js +9 -0
- package/packages/mailx-send/node_modules/nodemailer/.prettierignore +8 -0
- package/packages/mailx-send/node_modules/nodemailer/.prettierrc +12 -0
- package/packages/mailx-send/node_modules/nodemailer/.prettierrc.js +10 -0
- package/packages/mailx-send/node_modules/nodemailer/.release-please-config.json +9 -0
- package/packages/mailx-send/node_modules/nodemailer/LICENSE +16 -0
- package/packages/mailx-send/node_modules/nodemailer/README.md +86 -0
- package/packages/mailx-send/node_modules/nodemailer/SECURITY.txt +22 -0
- package/packages/mailx-send/node_modules/nodemailer/eslint.config.js +88 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/addressparser/index.js +383 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/base64/index.js +139 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/index.js +253 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/sign.js +117 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/fetch/index.js +280 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/json-transport/index.js +82 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/mail-composer/index.js +629 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/mailer/index.js +441 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/mailer/mail-message.js +316 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2113 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/index.js +1316 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/nodemailer.js +157 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/punycode/index.js +460 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/qp/index.js +227 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/ses-transport/index.js +234 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/shared/index.js +754 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/index.js +1870 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/index.js +652 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +259 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-transport/index.js +421 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/well-known/index.js +47 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/well-known/services.json +611 -0
- package/packages/mailx-send/node_modules/nodemailer/lib/xoauth2/index.js +427 -0
- package/packages/mailx-send/node_modules/nodemailer/package.json +47 -0
- package/packages/mailx-send/node_modules/undici-types/LICENSE +21 -0
- package/packages/mailx-send/node_modules/undici-types/README.md +6 -0
- package/packages/mailx-send/node_modules/undici-types/agent.d.ts +31 -0
- package/packages/mailx-send/node_modules/undici-types/api.d.ts +43 -0
- package/packages/mailx-send/node_modules/undici-types/balanced-pool.d.ts +29 -0
- package/packages/mailx-send/node_modules/undici-types/cache.d.ts +36 -0
- package/packages/mailx-send/node_modules/undici-types/client.d.ts +108 -0
- package/packages/mailx-send/node_modules/undici-types/connector.d.ts +34 -0
- package/packages/mailx-send/node_modules/undici-types/content-type.d.ts +21 -0
- package/packages/mailx-send/node_modules/undici-types/cookies.d.ts +28 -0
- package/packages/mailx-send/node_modules/undici-types/diagnostics-channel.d.ts +66 -0
- package/packages/mailx-send/node_modules/undici-types/dispatcher.d.ts +256 -0
- package/packages/mailx-send/node_modules/undici-types/env-http-proxy-agent.d.ts +21 -0
- package/packages/mailx-send/node_modules/undici-types/errors.d.ts +149 -0
- package/packages/mailx-send/node_modules/undici-types/eventsource.d.ts +61 -0
- package/packages/mailx-send/node_modules/undici-types/fetch.d.ts +209 -0
- package/packages/mailx-send/node_modules/undici-types/file.d.ts +39 -0
- package/packages/mailx-send/node_modules/undici-types/filereader.d.ts +54 -0
- package/packages/mailx-send/node_modules/undici-types/formdata.d.ts +108 -0
- package/packages/mailx-send/node_modules/undici-types/global-dispatcher.d.ts +9 -0
- package/packages/mailx-send/node_modules/undici-types/global-origin.d.ts +7 -0
- package/packages/mailx-send/node_modules/undici-types/handlers.d.ts +15 -0
- package/packages/mailx-send/node_modules/undici-types/header.d.ts +4 -0
- package/packages/mailx-send/node_modules/undici-types/index.d.ts +71 -0
- package/packages/mailx-send/node_modules/undici-types/interceptors.d.ts +17 -0
- package/packages/mailx-send/node_modules/undici-types/mock-agent.d.ts +50 -0
- package/packages/mailx-send/node_modules/undici-types/mock-client.d.ts +25 -0
- package/packages/mailx-send/node_modules/undici-types/mock-errors.d.ts +12 -0
- package/packages/mailx-send/node_modules/undici-types/mock-interceptor.d.ts +93 -0
- package/packages/mailx-send/node_modules/undici-types/mock-pool.d.ts +25 -0
- package/packages/mailx-send/node_modules/undici-types/package.json +55 -0
- package/packages/mailx-send/node_modules/undici-types/patch.d.ts +33 -0
- package/packages/mailx-send/node_modules/undici-types/pool-stats.d.ts +19 -0
- package/packages/mailx-send/node_modules/undici-types/pool.d.ts +39 -0
- package/packages/mailx-send/node_modules/undici-types/proxy-agent.d.ts +28 -0
- package/packages/mailx-send/node_modules/undici-types/readable.d.ts +65 -0
- package/packages/mailx-send/node_modules/undici-types/retry-agent.d.ts +8 -0
- package/packages/mailx-send/node_modules/undici-types/retry-handler.d.ts +116 -0
- package/packages/mailx-send/node_modules/undici-types/util.d.ts +18 -0
- package/packages/mailx-send/node_modules/undici-types/webidl.d.ts +228 -0
- package/packages/mailx-send/node_modules/undici-types/websocket.d.ts +150 -0
- package/packages/mailx-send/package.json +27 -0
- package/packages/mailx-server/index.d.ts +8 -0
- package/packages/mailx-server/index.js +217 -0
- package/packages/mailx-server/package.json +25 -0
- package/packages/mailx-server/tsconfig.tsbuildinfo +1 -0
- package/packages/mailx-settings/index.d.ts +64 -0
- package/packages/mailx-settings/index.js +218 -0
- package/packages/mailx-settings/package.json +16 -0
- package/packages/mailx-settings/tsconfig.tsbuildinfo +1 -0
- package/packages/mailx-store/db.d.ts +100 -0
- package/packages/mailx-store/db.js +461 -0
- package/packages/mailx-store/file-store.d.ts +22 -0
- package/packages/mailx-store/file-store.js +59 -0
- package/packages/mailx-store/index.d.ts +7 -0
- package/packages/mailx-store/index.js +7 -0
- package/packages/mailx-store/package.json +19 -0
- package/packages/mailx-store/tsconfig.tsbuildinfo +1 -0
- package/packages/mailx-types/index.d.ts +195 -0
- package/packages/mailx-types/index.js +7 -0
- package/packages/mailx-types/package.json +12 -0
- package/packages/mailx-types/tsconfig.tsbuildinfo +1 -0
- package/tsconfig.base.json +28 -0
|
@@ -0,0 +1,1026 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bobfrankston/mailx-imap
|
|
3
|
+
* Multi-account IMAP management wrapping iflow.
|
|
4
|
+
* Syncs messages to local store, emits events for new mail.
|
|
5
|
+
*/
|
|
6
|
+
import { ImapClient, createAutoImapConfig } from "@bobfrankston/iflow";
|
|
7
|
+
import { authenticateOAuth } from "@bobfrankston/oauthsupport";
|
|
8
|
+
import { FileMessageStore } from "@bobfrankston/mailx-store";
|
|
9
|
+
import { loadSettings, getStorePath } from "@bobfrankston/mailx-settings";
|
|
10
|
+
import { EventEmitter } from "node:events";
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import { simpleParser } from "mailparser";
|
|
14
|
+
import { createTransport } from "nodemailer";
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
/** Convert iflow address objects to our EmailAddress */
|
|
17
|
+
function toEmailAddress(addr) {
|
|
18
|
+
return {
|
|
19
|
+
name: addr?.name || "",
|
|
20
|
+
address: addr?.address || ""
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Convert array of iflow addresses */
|
|
24
|
+
function toEmailAddresses(addrs) {
|
|
25
|
+
if (!addrs)
|
|
26
|
+
return [];
|
|
27
|
+
return addrs.map(toEmailAddress);
|
|
28
|
+
}
|
|
29
|
+
/** Extract a plain-text preview from message source */
|
|
30
|
+
async function extractPreview(source) {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = await simpleParser(source);
|
|
33
|
+
const bodyText = parsed.text || "";
|
|
34
|
+
const bodyHtml = parsed.html || "";
|
|
35
|
+
const preview = bodyText.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
36
|
+
const hasAttachments = (parsed.attachments?.length || 0) > 0;
|
|
37
|
+
return { bodyHtml, bodyText, preview, hasAttachments };
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { bodyHtml: "", bodyText: "", preview: "", hasAttachments: false };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class ImapManager extends EventEmitter {
|
|
44
|
+
configs = new Map();
|
|
45
|
+
watchers = new Map();
|
|
46
|
+
fetchClients = new Map();
|
|
47
|
+
db;
|
|
48
|
+
bodyStore;
|
|
49
|
+
syncIntervals = new Map();
|
|
50
|
+
syncing = false;
|
|
51
|
+
inboxSyncing = false;
|
|
52
|
+
constructor(db) {
|
|
53
|
+
super();
|
|
54
|
+
this.db = db;
|
|
55
|
+
const storePath = getStorePath();
|
|
56
|
+
this.bodyStore = new FileMessageStore(storePath);
|
|
57
|
+
}
|
|
58
|
+
/** Create a fresh ImapClient for an account (disposable, single-use) */
|
|
59
|
+
createClient(accountId) {
|
|
60
|
+
const config = this.configs.get(accountId);
|
|
61
|
+
if (!config)
|
|
62
|
+
throw new Error(`No config for account ${accountId}`);
|
|
63
|
+
return new ImapClient(config);
|
|
64
|
+
}
|
|
65
|
+
/** Register an account */
|
|
66
|
+
async addAccount(account) {
|
|
67
|
+
if (this.configs.has(account.id))
|
|
68
|
+
return;
|
|
69
|
+
// createAutoImapConfig auto-detects Gmail from server/username and sets up OAuth
|
|
70
|
+
const config = createAutoImapConfig({
|
|
71
|
+
server: account.imap.host,
|
|
72
|
+
port: account.imap.port,
|
|
73
|
+
username: account.imap.user,
|
|
74
|
+
password: account.imap.password
|
|
75
|
+
});
|
|
76
|
+
this.configs.set(account.id, config);
|
|
77
|
+
// Register account in DB
|
|
78
|
+
this.db.upsertAccount(account.id, account.name, account.email, JSON.stringify(account));
|
|
79
|
+
}
|
|
80
|
+
/** Sync folder list for an account */
|
|
81
|
+
async syncFolders(accountId, client) {
|
|
82
|
+
if (!client)
|
|
83
|
+
client = this.createClient(accountId);
|
|
84
|
+
this.emit("syncProgress", accountId, "folders", 0);
|
|
85
|
+
const folders = await client.getFolderList();
|
|
86
|
+
const specialFolders = client.getSpecialFolders(folders);
|
|
87
|
+
for (const folder of folders) {
|
|
88
|
+
// Skip non-selectable folders (virtual parents like "Added", "Added2")
|
|
89
|
+
const flags = folder.flags;
|
|
90
|
+
const flagArr = flags instanceof Set ? [...flags] : (flags || []);
|
|
91
|
+
if (flagArr.some((f) => f.toLowerCase() === "\\noselect" || f.toLowerCase() === "\\nonexistent"))
|
|
92
|
+
continue;
|
|
93
|
+
let specialUse = null;
|
|
94
|
+
if (specialFolders.inbox === folder.path)
|
|
95
|
+
specialUse = "inbox";
|
|
96
|
+
else if (specialFolders.sent === folder.path)
|
|
97
|
+
specialUse = "sent";
|
|
98
|
+
else if (specialFolders.trash === folder.path)
|
|
99
|
+
specialUse = "trash";
|
|
100
|
+
else if (specialFolders.drafts === folder.path)
|
|
101
|
+
specialUse = "drafts";
|
|
102
|
+
else if (specialFolders.spam === folder.path || specialFolders.junk === folder.path)
|
|
103
|
+
specialUse = "junk";
|
|
104
|
+
else if (specialFolders.archive === folder.path)
|
|
105
|
+
specialUse = "archive";
|
|
106
|
+
this.db.upsertFolder(accountId, folder.path, folder.name || folder.path.split(folder.delimiter || "/").pop() || folder.path, specialUse, folder.delimiter || "/");
|
|
107
|
+
}
|
|
108
|
+
this.emit("syncProgress", accountId, "folders", 100);
|
|
109
|
+
const dbFolders = this.db.getFolders(accountId);
|
|
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;
|
|
115
|
+
}
|
|
116
|
+
/** Sync messages for a specific folder */
|
|
117
|
+
async syncFolder(accountId, folderId, client) {
|
|
118
|
+
if (!client)
|
|
119
|
+
client = this.createClient(accountId);
|
|
120
|
+
const folders = this.db.getFolders(accountId);
|
|
121
|
+
const folder = folders.find(f => f.id === folderId);
|
|
122
|
+
if (!folder)
|
|
123
|
+
throw new Error(`Folder ${folderId} not found`);
|
|
124
|
+
this.emit("syncProgress", accountId, `sync:${folder.path}`, 0);
|
|
125
|
+
// Get the highest UID we already have for this folder
|
|
126
|
+
const highestUid = this.db.getHighestUid(accountId, folderId);
|
|
127
|
+
let messages;
|
|
128
|
+
if (highestUid > 0) {
|
|
129
|
+
// Incremental: only fetch messages newer than what we have
|
|
130
|
+
const fetched = await client.fetchMessagesSinceUid(folder.path, highestUid, { source: true });
|
|
131
|
+
// Filter out the last known message (IMAP * always returns at least one)
|
|
132
|
+
messages = fetched.filter(m => m.uid > highestUid);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// First sync: use date-based fetch with bodies for local-first
|
|
136
|
+
const settings = loadSettings();
|
|
137
|
+
const historyDays = settings.sync.historyDays || 0;
|
|
138
|
+
const startDate = historyDays > 0
|
|
139
|
+
? new Date(Date.now() - historyDays * 86400000)
|
|
140
|
+
: new Date(0);
|
|
141
|
+
messages = await client.fetchMessageByDate(folder.path, startDate, new Date(), { source: true });
|
|
142
|
+
}
|
|
143
|
+
if (messages.length > 0)
|
|
144
|
+
console.log(` ${folder.path}: ${messages.length} new messages`);
|
|
145
|
+
let newCount = 0;
|
|
146
|
+
const batchSize = 50;
|
|
147
|
+
this.db.beginTransaction();
|
|
148
|
+
try {
|
|
149
|
+
for (let i = 0; i < messages.length; i++) {
|
|
150
|
+
const msg = messages[i];
|
|
151
|
+
// Skip if we already have this UID
|
|
152
|
+
if (msg.uid <= highestUid) {
|
|
153
|
+
// But update flags in case they changed
|
|
154
|
+
const flags = [];
|
|
155
|
+
if (msg.seen)
|
|
156
|
+
flags.push("\\Seen");
|
|
157
|
+
if (msg.flagged)
|
|
158
|
+
flags.push("\\Flagged");
|
|
159
|
+
if (msg.answered)
|
|
160
|
+
flags.push("\\Answered");
|
|
161
|
+
if (msg.draft)
|
|
162
|
+
flags.push("\\Draft");
|
|
163
|
+
this.db.updateMessageFlags(accountId, msg.uid, flags);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
// Store body
|
|
167
|
+
const source = msg.source || "";
|
|
168
|
+
let bodyPath = "";
|
|
169
|
+
if (source) {
|
|
170
|
+
bodyPath = await this.bodyStore.putMessage(accountId, folderId, msg.uid, Buffer.from(source, "utf-8"));
|
|
171
|
+
}
|
|
172
|
+
// Parse for preview and attachment info
|
|
173
|
+
const parsed = await extractPreview(source);
|
|
174
|
+
// Build flags array
|
|
175
|
+
const flags = [];
|
|
176
|
+
if (msg.seen)
|
|
177
|
+
flags.push("\\Seen");
|
|
178
|
+
if (msg.flagged)
|
|
179
|
+
flags.push("\\Flagged");
|
|
180
|
+
if (msg.answered)
|
|
181
|
+
flags.push("\\Answered");
|
|
182
|
+
if (msg.draft)
|
|
183
|
+
flags.push("\\Draft");
|
|
184
|
+
// Store metadata
|
|
185
|
+
this.db.upsertMessage({
|
|
186
|
+
accountId,
|
|
187
|
+
folderId,
|
|
188
|
+
uid: msg.uid,
|
|
189
|
+
messageId: msg.messageId || "",
|
|
190
|
+
inReplyTo: "",
|
|
191
|
+
references: [],
|
|
192
|
+
date: msg.date instanceof Date ? msg.date.getTime() : (typeof msg.date === "number" ? msg.date : Date.now()),
|
|
193
|
+
subject: msg.subject || "",
|
|
194
|
+
from: toEmailAddress(msg.from?.[0] || {}),
|
|
195
|
+
to: toEmailAddresses(msg.to || []),
|
|
196
|
+
cc: toEmailAddresses(msg.cc || []),
|
|
197
|
+
flags,
|
|
198
|
+
size: msg.size || 0,
|
|
199
|
+
hasAttachments: parsed.hasAttachments,
|
|
200
|
+
preview: parsed.preview,
|
|
201
|
+
bodyPath
|
|
202
|
+
});
|
|
203
|
+
newCount++;
|
|
204
|
+
// Emit progress periodically
|
|
205
|
+
if (i % batchSize === 0) {
|
|
206
|
+
this.emit("syncProgress", accountId, `sync:${folder.path}`, Math.round((i / messages.length) * 100));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.db.commitTransaction();
|
|
210
|
+
if (newCount > 0)
|
|
211
|
+
console.log(` stored ${newCount} new messages`);
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
console.error(` transaction error: ${e.message}`);
|
|
215
|
+
this.db.rollbackTransaction();
|
|
216
|
+
throw e;
|
|
217
|
+
}
|
|
218
|
+
// Remove messages deleted on the server
|
|
219
|
+
let deletedCount = 0;
|
|
220
|
+
try {
|
|
221
|
+
const serverUids = new Set(await client.getUids(folder.path));
|
|
222
|
+
const localUids = this.db.getUidsForFolder(accountId, folderId);
|
|
223
|
+
for (const uid of localUids) {
|
|
224
|
+
if (!serverUids.has(uid)) {
|
|
225
|
+
this.db.deleteMessage(accountId, uid);
|
|
226
|
+
// Also remove cached body
|
|
227
|
+
this.bodyStore.deleteMessage(accountId, folderId, uid).catch(() => { });
|
|
228
|
+
deletedCount++;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (deletedCount > 0)
|
|
232
|
+
console.log(` removed ${deletedCount} deleted messages`);
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
console.error(` deletion sync error: ${e.message}`);
|
|
236
|
+
}
|
|
237
|
+
// Update folder counts from local DB (after deletions + additions)
|
|
238
|
+
const result = this.db.getMessages({ accountId, folderId, page: 1, pageSize: 1 });
|
|
239
|
+
const total = result.total;
|
|
240
|
+
const localMsgs = this.db.getMessages({ accountId, folderId, page: 1, pageSize: result.total });
|
|
241
|
+
const unread = localMsgs.items.filter((m) => !m.flags.includes("\\Seen")).length;
|
|
242
|
+
this.db.updateFolderCounts(folderId, total, unread);
|
|
243
|
+
this.emit("syncProgress", accountId, `sync:${folder.path}`, 100);
|
|
244
|
+
// Notify client to refresh if anything changed
|
|
245
|
+
if (newCount > 0 || deletedCount > 0) {
|
|
246
|
+
this.emit("folderCountsChanged", accountId, {
|
|
247
|
+
[folderId]: { total, unread }
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
this.db.updateLastSync(accountId, Date.now());
|
|
251
|
+
return newCount;
|
|
252
|
+
}
|
|
253
|
+
/** Sync all folders for all accounts */
|
|
254
|
+
async syncAll() {
|
|
255
|
+
if (this.syncing)
|
|
256
|
+
return; // Prevent concurrent syncs
|
|
257
|
+
this.syncing = true;
|
|
258
|
+
try {
|
|
259
|
+
await this._syncAll();
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
this.syncing = false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async _syncAll() {
|
|
266
|
+
for (const [accountId] of this.configs) {
|
|
267
|
+
let client = null;
|
|
268
|
+
try {
|
|
269
|
+
// Fresh client for folder list
|
|
270
|
+
client = this.createClient(accountId);
|
|
271
|
+
const folders = await this.syncFolders(accountId, client);
|
|
272
|
+
await client.logout();
|
|
273
|
+
client = null;
|
|
274
|
+
// Fresh client for message sync (getFolderList corrupts imapflow state)
|
|
275
|
+
client = this.createClient(accountId);
|
|
276
|
+
// INBOX first so it's available fastest
|
|
277
|
+
folders.sort((a, b) => {
|
|
278
|
+
if (a.specialUse === "inbox")
|
|
279
|
+
return -1;
|
|
280
|
+
if (b.specialUse === "inbox")
|
|
281
|
+
return 1;
|
|
282
|
+
return 0;
|
|
283
|
+
});
|
|
284
|
+
for (const folder of folders) {
|
|
285
|
+
try {
|
|
286
|
+
await this.syncFolder(accountId, folder.id, client);
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
if (e.responseText?.includes("doesn't exist")) {
|
|
290
|
+
console.log(` Removing non-existent folder: ${folder.path}`);
|
|
291
|
+
this.db.deleteFolder(folder.id);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
console.error(` Skipping folder ${folder.path}: ${e.message}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
await client.logout();
|
|
299
|
+
client = null;
|
|
300
|
+
this.emit("syncComplete", accountId);
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
this.emit("syncError", accountId, e.message);
|
|
304
|
+
console.error(`Sync error for ${accountId}: ${e.message}`);
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
if (client)
|
|
308
|
+
try {
|
|
309
|
+
await client.logout();
|
|
310
|
+
}
|
|
311
|
+
catch { /* ignore */ }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** Sync just INBOX for each account (fast check for new mail) */
|
|
316
|
+
async syncInbox() {
|
|
317
|
+
if (this.inboxSyncing)
|
|
318
|
+
return;
|
|
319
|
+
this.inboxSyncing = true;
|
|
320
|
+
try {
|
|
321
|
+
for (const [accountId] of this.configs) {
|
|
322
|
+
let client = null;
|
|
323
|
+
try {
|
|
324
|
+
const inbox = this.db.getFolders(accountId).find(f => f.specialUse === "inbox");
|
|
325
|
+
if (!inbox)
|
|
326
|
+
continue;
|
|
327
|
+
// Try up to 2 times with fresh clients
|
|
328
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
329
|
+
try {
|
|
330
|
+
client = this.createClient(accountId);
|
|
331
|
+
await this.syncFolder(accountId, inbox.id, client);
|
|
332
|
+
await client.logout();
|
|
333
|
+
client = null;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
catch (retryErr) {
|
|
337
|
+
if (client)
|
|
338
|
+
try {
|
|
339
|
+
await client.logout();
|
|
340
|
+
}
|
|
341
|
+
catch { /* ignore */ }
|
|
342
|
+
client = null;
|
|
343
|
+
if (attempt === 1)
|
|
344
|
+
throw retryErr;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
console.error(` [inbox] Sync error for ${accountId}: ${e.message}`);
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
if (client)
|
|
353
|
+
try {
|
|
354
|
+
await client.logout();
|
|
355
|
+
}
|
|
356
|
+
catch { /* ignore */ }
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
finally {
|
|
361
|
+
this.inboxSyncing = false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/** Start periodic sync */
|
|
365
|
+
startPeriodicSync(intervalMinutes) {
|
|
366
|
+
this.stopPeriodicSync();
|
|
367
|
+
// INBOX poll + sync actions (IDLE handles instant, this catches gaps)
|
|
368
|
+
const inboxInterval = setInterval(async () => {
|
|
369
|
+
// Process pending local→IMAP sync actions (sends + flags/deletes/moves)
|
|
370
|
+
for (const [accountId] of this.configs) {
|
|
371
|
+
this.processSendActions(accountId).catch(() => { });
|
|
372
|
+
this.processSyncActions(accountId).catch(() => { });
|
|
373
|
+
}
|
|
374
|
+
this.syncInbox().catch(e => console.error(` [inbox] error: ${e.message}`));
|
|
375
|
+
}, 30000);
|
|
376
|
+
this.syncIntervals.set("inbox", inboxInterval);
|
|
377
|
+
// Full sync (all folders + IDLE restart) at configured interval
|
|
378
|
+
const fullInterval = setInterval(async () => {
|
|
379
|
+
console.log(` [periodic] Full sync at ${new Date().toLocaleTimeString()}`);
|
|
380
|
+
await this.syncAll();
|
|
381
|
+
await this.stopWatching();
|
|
382
|
+
await this.startWatching();
|
|
383
|
+
}, intervalMinutes * 60000);
|
|
384
|
+
this.syncIntervals.set("all", fullInterval);
|
|
385
|
+
}
|
|
386
|
+
/** Stop periodic sync */
|
|
387
|
+
stopPeriodicSync() {
|
|
388
|
+
for (const [key, interval] of this.syncIntervals) {
|
|
389
|
+
clearInterval(interval);
|
|
390
|
+
}
|
|
391
|
+
this.syncIntervals.clear();
|
|
392
|
+
}
|
|
393
|
+
/** Start IMAP IDLE watchers for INBOX on each account */
|
|
394
|
+
async startWatching() {
|
|
395
|
+
for (const [accountId] of this.configs) {
|
|
396
|
+
if (this.watchers.has(accountId))
|
|
397
|
+
continue;
|
|
398
|
+
try {
|
|
399
|
+
const watchClient = this.createClient(accountId);
|
|
400
|
+
const stop = await watchClient.watchMailbox("INBOX", (newCount) => {
|
|
401
|
+
console.log(` [idle] ${accountId}: ${newCount} new message(s)`);
|
|
402
|
+
this.syncAll().catch(e => console.error(` [idle] sync error: ${e.message}`));
|
|
403
|
+
});
|
|
404
|
+
this.watchers.set(accountId, async () => {
|
|
405
|
+
await stop();
|
|
406
|
+
await watchClient.logout();
|
|
407
|
+
});
|
|
408
|
+
console.log(` [idle] Watching INBOX for ${accountId}`);
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
console.error(` [idle] Failed to watch ${accountId}: ${e.message}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/** Stop all IDLE watchers */
|
|
416
|
+
async stopWatching() {
|
|
417
|
+
for (const [id, stop] of this.watchers) {
|
|
418
|
+
try {
|
|
419
|
+
await stop();
|
|
420
|
+
}
|
|
421
|
+
catch { /* ignore */ }
|
|
422
|
+
}
|
|
423
|
+
this.watchers.clear();
|
|
424
|
+
}
|
|
425
|
+
/** Get or create a persistent client for body fetching */
|
|
426
|
+
getFetchClient(accountId) {
|
|
427
|
+
let client = this.fetchClients.get(accountId);
|
|
428
|
+
if (!client) {
|
|
429
|
+
client = this.createClient(accountId);
|
|
430
|
+
this.fetchClients.set(accountId, client);
|
|
431
|
+
}
|
|
432
|
+
return client;
|
|
433
|
+
}
|
|
434
|
+
/** Fetch a single message body on demand, caching in the store */
|
|
435
|
+
async fetchMessageBody(accountId, folderId, uid) {
|
|
436
|
+
// Already cached?
|
|
437
|
+
if (await this.bodyStore.hasMessage(accountId, folderId, uid)) {
|
|
438
|
+
return this.bodyStore.getMessage(accountId, folderId, uid);
|
|
439
|
+
}
|
|
440
|
+
if (!this.configs.has(accountId))
|
|
441
|
+
return null;
|
|
442
|
+
const folder = this.db.getFolders(accountId).find(f => f.id === folderId);
|
|
443
|
+
if (!folder)
|
|
444
|
+
return null;
|
|
445
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
446
|
+
try {
|
|
447
|
+
const client = this.getFetchClient(accountId);
|
|
448
|
+
const msg = await client.fetchMessageByUid(folder.path, uid, { source: true });
|
|
449
|
+
if (!msg?.source)
|
|
450
|
+
return null;
|
|
451
|
+
const raw = Buffer.from(msg.source, "utf-8");
|
|
452
|
+
await this.bodyStore.putMessage(accountId, folderId, uid, raw);
|
|
453
|
+
return raw;
|
|
454
|
+
}
|
|
455
|
+
catch (e) {
|
|
456
|
+
console.error(` Body fetch error (${accountId}/${uid}): ${e.message}`);
|
|
457
|
+
this.fetchClients.delete(accountId);
|
|
458
|
+
if (attempt === 1)
|
|
459
|
+
return null;
|
|
460
|
+
// Retry with fresh client
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
/** Get the body store for direct access */
|
|
466
|
+
getBodyStore() {
|
|
467
|
+
return this.bodyStore;
|
|
468
|
+
}
|
|
469
|
+
/** Move a message to Trash (delete) — local-first, queues IMAP sync */
|
|
470
|
+
async trashMessage(accountId, folderId, uid) {
|
|
471
|
+
const trash = this.findFolder(accountId, "trash");
|
|
472
|
+
// Local first — remove from DB immediately
|
|
473
|
+
this.db.deleteMessage(accountId, uid);
|
|
474
|
+
this.bodyStore.deleteMessage(accountId, folderId, uid).catch(() => { });
|
|
475
|
+
console.log(` Deleted message UID ${uid} locally`);
|
|
476
|
+
// Queue IMAP action
|
|
477
|
+
if (trash && trash.id !== folderId) {
|
|
478
|
+
this.db.queueSyncAction(accountId, "move", uid, folderId, { targetFolderId: trash.id });
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
this.db.queueSyncAction(accountId, "delete", uid, folderId);
|
|
482
|
+
}
|
|
483
|
+
// Try immediate sync
|
|
484
|
+
this.processSyncActions(accountId).catch(() => { });
|
|
485
|
+
}
|
|
486
|
+
/** Move a message between folders — local-first, queues IMAP sync */
|
|
487
|
+
async moveMessage(accountId, uid, fromFolderId, toFolderId) {
|
|
488
|
+
// Local first
|
|
489
|
+
this.db.deleteMessage(accountId, uid);
|
|
490
|
+
console.log(` Moved UID ${uid} locally (folder ${fromFolderId} → ${toFolderId})`);
|
|
491
|
+
// Queue IMAP action
|
|
492
|
+
this.db.queueSyncAction(accountId, "move", uid, fromFolderId, { targetFolderId: toFolderId });
|
|
493
|
+
// Try immediate sync
|
|
494
|
+
this.processSyncActions(accountId).catch(() => { });
|
|
495
|
+
}
|
|
496
|
+
/** Undelete — move from Trash back to original folder */
|
|
497
|
+
async undeleteMessage(accountId, uid, originalFolderId) {
|
|
498
|
+
const trash = this.findFolder(accountId, "trash");
|
|
499
|
+
if (!trash)
|
|
500
|
+
throw new Error("No Trash folder found");
|
|
501
|
+
await this.moveMessage(accountId, uid, trash.id, originalFolderId);
|
|
502
|
+
}
|
|
503
|
+
/** Update flags — local-first, queues IMAP sync */
|
|
504
|
+
async updateFlagsLocal(accountId, uid, folderId, flags) {
|
|
505
|
+
this.db.updateMessageFlags(accountId, uid, flags);
|
|
506
|
+
this.db.queueSyncAction(accountId, "flags", uid, folderId, { flags });
|
|
507
|
+
this.processSyncActions(accountId).catch(() => { });
|
|
508
|
+
}
|
|
509
|
+
/** Process pending sync actions for an account */
|
|
510
|
+
async processSyncActions(accountId) {
|
|
511
|
+
const actions = this.db.getPendingSyncActions(accountId);
|
|
512
|
+
if (actions.length === 0)
|
|
513
|
+
return;
|
|
514
|
+
const folders = this.db.getFolders(accountId);
|
|
515
|
+
const client = this.createClient(accountId);
|
|
516
|
+
try {
|
|
517
|
+
for (const action of actions) {
|
|
518
|
+
const folder = folders.find(f => f.id === action.folderId);
|
|
519
|
+
if (!folder) {
|
|
520
|
+
this.db.completeSyncAction(action.id);
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
switch (action.action) {
|
|
525
|
+
case "delete":
|
|
526
|
+
await client.deleteMessageByUid(folder.path, action.uid);
|
|
527
|
+
console.log(` [sync] Deleted UID ${action.uid} from ${folder.path}`);
|
|
528
|
+
break;
|
|
529
|
+
case "move": {
|
|
530
|
+
const target = folders.find(f => f.id === action.targetFolderId);
|
|
531
|
+
if (target) {
|
|
532
|
+
const msg = await client.fetchMessageByUid(folder.path, action.uid, { source: false });
|
|
533
|
+
if (msg) {
|
|
534
|
+
await client.moveMessage(msg, folder.path, target.path);
|
|
535
|
+
console.log(` [sync] Moved UID ${action.uid}: ${folder.path} → ${target.path}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
case "flags":
|
|
541
|
+
if (action.flags.length > 0) {
|
|
542
|
+
await client.addFlags(folder.path, action.uid, action.flags.filter(f => !f.startsWith("-")));
|
|
543
|
+
const toRemove = action.flags.filter(f => f.startsWith("-")).map(f => f.slice(1));
|
|
544
|
+
if (toRemove.length > 0) {
|
|
545
|
+
await client.removeFlags(folder.path, action.uid, toRemove);
|
|
546
|
+
}
|
|
547
|
+
console.log(` [sync] Updated flags UID ${action.uid}`);
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
550
|
+
case "append": {
|
|
551
|
+
if (action.rawMessage) {
|
|
552
|
+
await client.appendMessage(folder.path, action.rawMessage, action.flags);
|
|
553
|
+
console.log(` [sync] Appended to ${folder.path}`);
|
|
554
|
+
}
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
this.db.completeSyncAction(action.id);
|
|
559
|
+
}
|
|
560
|
+
catch (e) {
|
|
561
|
+
console.error(` [sync] Failed action ${action.action} UID ${action.uid}: ${e.message}`);
|
|
562
|
+
this.db.failSyncAction(action.id, e.message);
|
|
563
|
+
if (action.attempts >= 5) {
|
|
564
|
+
console.error(` [sync] Giving up on action ${action.id} after 5 attempts`);
|
|
565
|
+
this.db.completeSyncAction(action.id);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
finally {
|
|
571
|
+
try {
|
|
572
|
+
await client.logout();
|
|
573
|
+
}
|
|
574
|
+
catch { /* ignore */ }
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
/** Find a folder by specialUse, case-insensitive */
|
|
578
|
+
findFolder(accountId, specialUse) {
|
|
579
|
+
const folders = this.db.getFolders(accountId);
|
|
580
|
+
return folders.find(f => f.specialUse === specialUse ||
|
|
581
|
+
f.path.toLowerCase() === specialUse.toLowerCase()) || null;
|
|
582
|
+
}
|
|
583
|
+
/** Copy sent message to the Sent folder via IMAP APPEND */
|
|
584
|
+
async copyToSent(accountId, rawMessage) {
|
|
585
|
+
const sent = this.findFolder(accountId, "sent");
|
|
586
|
+
if (!sent) {
|
|
587
|
+
console.error(` [sent] No Sent folder found for ${accountId}`);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const client = this.createClient(accountId);
|
|
591
|
+
try {
|
|
592
|
+
await client.appendMessage(sent.path, rawMessage, ["\\Seen"]);
|
|
593
|
+
console.log(` [sent] Copied to ${sent.path}`);
|
|
594
|
+
}
|
|
595
|
+
finally {
|
|
596
|
+
try {
|
|
597
|
+
await client.logout();
|
|
598
|
+
}
|
|
599
|
+
catch { /* ignore */ }
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/** Save a draft to the Drafts folder via IMAP APPEND.
|
|
603
|
+
* Returns the UID of the saved draft (for replacing on next save). */
|
|
604
|
+
async saveDraft(accountId, rawMessage, previousDraftUid) {
|
|
605
|
+
const drafts = this.findFolder(accountId, "drafts");
|
|
606
|
+
if (!drafts) {
|
|
607
|
+
console.error(` [drafts] No Drafts folder found for ${accountId}`);
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
const client = this.createClient(accountId);
|
|
611
|
+
try {
|
|
612
|
+
// Delete previous draft if it exists
|
|
613
|
+
if (previousDraftUid) {
|
|
614
|
+
try {
|
|
615
|
+
await client.deleteMessageByUid(drafts.path, previousDraftUid);
|
|
616
|
+
}
|
|
617
|
+
catch { /* previous draft may already be gone */ }
|
|
618
|
+
}
|
|
619
|
+
// Append new draft
|
|
620
|
+
const result = await client.appendMessage(drafts.path, rawMessage, ["\\Draft", "\\Seen"]);
|
|
621
|
+
// imapflow append returns { destination, uid }
|
|
622
|
+
const uid = result?.uid || null;
|
|
623
|
+
return uid;
|
|
624
|
+
}
|
|
625
|
+
finally {
|
|
626
|
+
try {
|
|
627
|
+
await client.logout();
|
|
628
|
+
}
|
|
629
|
+
catch { /* ignore */ }
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
/** Delete a draft after successful send */
|
|
633
|
+
async deleteDraft(accountId, draftUid) {
|
|
634
|
+
const drafts = this.findFolder(accountId, "drafts");
|
|
635
|
+
if (!drafts || !draftUid)
|
|
636
|
+
return;
|
|
637
|
+
const client = this.createClient(accountId);
|
|
638
|
+
try {
|
|
639
|
+
await client.deleteMessageByUid(drafts.path, draftUid);
|
|
640
|
+
console.log(` [drafts] Deleted draft UID ${draftUid}`);
|
|
641
|
+
}
|
|
642
|
+
catch (e) {
|
|
643
|
+
console.error(` [drafts] Delete error: ${e.message}`);
|
|
644
|
+
}
|
|
645
|
+
finally {
|
|
646
|
+
try {
|
|
647
|
+
await client.logout();
|
|
648
|
+
}
|
|
649
|
+
catch { /* ignore */ }
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/** Queue outgoing message locally — never fails, worker handles IMAP+SMTP */
|
|
653
|
+
queueOutgoingLocal(accountId, rawMessage) {
|
|
654
|
+
// Use folderId=0 and uid=Date.now() as placeholder — the worker will handle the real IMAP append
|
|
655
|
+
this.db.queueSyncAction(accountId, "send", Date.now(), 0, { rawMessage });
|
|
656
|
+
console.log(` [outbox] Queued locally for ${accountId}`);
|
|
657
|
+
// Try immediate processing
|
|
658
|
+
this.processSendActions(accountId).catch(() => { });
|
|
659
|
+
}
|
|
660
|
+
/** Process local send actions — APPEND to Outbox, which the outbox worker then sends */
|
|
661
|
+
async processSendActions(accountId) {
|
|
662
|
+
const actions = this.db.getPendingSyncActions(accountId)
|
|
663
|
+
.filter(a => a.action === "send");
|
|
664
|
+
if (actions.length === 0)
|
|
665
|
+
return;
|
|
666
|
+
for (const action of actions) {
|
|
667
|
+
if (!action.rawMessage) {
|
|
668
|
+
this.db.completeSyncAction(action.id);
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
await this.queueOutgoing(accountId, action.rawMessage);
|
|
673
|
+
this.db.completeSyncAction(action.id);
|
|
674
|
+
}
|
|
675
|
+
catch (e) {
|
|
676
|
+
console.error(` [outbox] Local→IMAP failed: ${e.message}`);
|
|
677
|
+
this.db.failSyncAction(action.id, e.message);
|
|
678
|
+
// Don't give up — keep retrying sends
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
// ── Outbox ──
|
|
683
|
+
outboxInterval = null;
|
|
684
|
+
hostname = os.hostname();
|
|
685
|
+
/** Ensure Outbox folder exists, create if needed */
|
|
686
|
+
async ensureOutbox(accountId) {
|
|
687
|
+
let outbox = this.findFolder(accountId, "outbox");
|
|
688
|
+
if (outbox)
|
|
689
|
+
return outbox.path;
|
|
690
|
+
// Look for existing folder named Outbox (case-insensitive)
|
|
691
|
+
const folders = this.db.getFolders(accountId);
|
|
692
|
+
const existing = folders.find(f => f.path.toLowerCase() === "outbox");
|
|
693
|
+
if (existing)
|
|
694
|
+
return existing.path;
|
|
695
|
+
// Create it
|
|
696
|
+
const client = this.createClient(accountId);
|
|
697
|
+
try {
|
|
698
|
+
await client.createmailbox("Outbox");
|
|
699
|
+
// Sync folders to pick up the new one
|
|
700
|
+
await this.syncFolders(accountId, client);
|
|
701
|
+
await client.logout();
|
|
702
|
+
}
|
|
703
|
+
catch (e) {
|
|
704
|
+
try {
|
|
705
|
+
await client.logout();
|
|
706
|
+
}
|
|
707
|
+
catch { /* ignore */ }
|
|
708
|
+
// Might already exist
|
|
709
|
+
if (!e.message?.includes("already exists"))
|
|
710
|
+
throw e;
|
|
711
|
+
}
|
|
712
|
+
outbox = this.findFolder(accountId, "outbox");
|
|
713
|
+
return outbox?.path || "Outbox";
|
|
714
|
+
}
|
|
715
|
+
/** Queue a message for sending. Tries IMAP Outbox, falls back to local file. */
|
|
716
|
+
async queueOutgoing(accountId, rawMessage) {
|
|
717
|
+
try {
|
|
718
|
+
const outboxPath = await this.ensureOutbox(accountId);
|
|
719
|
+
const client = this.createClient(accountId);
|
|
720
|
+
try {
|
|
721
|
+
await client.appendMessage(outboxPath, rawMessage, ["\\Seen"]);
|
|
722
|
+
console.log(` [outbox] Queued message in ${outboxPath}`);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
finally {
|
|
726
|
+
try {
|
|
727
|
+
await client.logout();
|
|
728
|
+
}
|
|
729
|
+
catch { /* ignore */ }
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
catch (e) {
|
|
733
|
+
console.error(` [outbox] IMAP queue failed: ${e.message} — saving locally`);
|
|
734
|
+
}
|
|
735
|
+
// Fallback: save to local file queue
|
|
736
|
+
const localQueue = path.join(import.meta.dirname, "..", "..", "outbox", accountId);
|
|
737
|
+
fs.mkdirSync(localQueue, { recursive: true });
|
|
738
|
+
const now = new Date();
|
|
739
|
+
const pad2 = (n) => String(n).padStart(2, "0");
|
|
740
|
+
const filename = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}_${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}-${String(Math.floor(Math.random() * 10000)).padStart(4, "0")}.ltr`;
|
|
741
|
+
fs.writeFileSync(path.join(localQueue, filename), rawMessage);
|
|
742
|
+
console.log(` [outbox] Saved locally: ${filename}`);
|
|
743
|
+
}
|
|
744
|
+
/** Process local file queue — move to IMAP Outbox when server is reachable */
|
|
745
|
+
async processLocalQueue(accountId) {
|
|
746
|
+
const localQueue = path.join(import.meta.dirname, "..", "..", "outbox", accountId);
|
|
747
|
+
if (!fs.existsSync(localQueue))
|
|
748
|
+
return;
|
|
749
|
+
const files = fs.readdirSync(localQueue).filter(f => f.endsWith(".ltr"));
|
|
750
|
+
if (files.length === 0)
|
|
751
|
+
return;
|
|
752
|
+
try {
|
|
753
|
+
const outboxPath = await this.ensureOutbox(accountId);
|
|
754
|
+
const client = this.createClient(accountId);
|
|
755
|
+
try {
|
|
756
|
+
for (const file of files) {
|
|
757
|
+
const filePath = path.join(localQueue, file);
|
|
758
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
759
|
+
await client.appendMessage(outboxPath, raw, ["\\Seen"]);
|
|
760
|
+
fs.unlinkSync(filePath);
|
|
761
|
+
console.log(` [outbox] Moved local ${file} to IMAP Outbox`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
finally {
|
|
765
|
+
try {
|
|
766
|
+
await client.logout();
|
|
767
|
+
}
|
|
768
|
+
catch { /* ignore */ }
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
catch {
|
|
772
|
+
// IMAP still unreachable — leave files for next attempt
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
/** Process Outbox — send pending messages with flag-based interlock */
|
|
776
|
+
async processOutbox(accountId) {
|
|
777
|
+
const outboxFolder = this.findFolder(accountId, "outbox");
|
|
778
|
+
if (!outboxFolder)
|
|
779
|
+
return;
|
|
780
|
+
const settings = loadSettings();
|
|
781
|
+
const account = settings.accounts.find(a => a.id === accountId);
|
|
782
|
+
if (!account)
|
|
783
|
+
return;
|
|
784
|
+
const client = this.createClient(accountId);
|
|
785
|
+
try {
|
|
786
|
+
// Get all UIDs in Outbox
|
|
787
|
+
const uids = await client.getUids(outboxFolder.path);
|
|
788
|
+
if (uids.length === 0)
|
|
789
|
+
return;
|
|
790
|
+
const sendingFlag = `$Sending-${this.hostname}`;
|
|
791
|
+
for (const uid of uids) {
|
|
792
|
+
// Check flags — skip if already being sent or permanently failed
|
|
793
|
+
const flags = await client.getFlags(outboxFolder.path, uid);
|
|
794
|
+
if (flags.some(f => f.startsWith("$Sending")))
|
|
795
|
+
continue;
|
|
796
|
+
if (flags.includes("$PermanentFailure"))
|
|
797
|
+
continue;
|
|
798
|
+
if (flags.includes("$Failed")) {
|
|
799
|
+
// Retry: remove failed flag
|
|
800
|
+
await client.removeFlags(outboxFolder.path, uid, ["$Failed"]);
|
|
801
|
+
}
|
|
802
|
+
// Claim this message
|
|
803
|
+
await client.addFlags(outboxFolder.path, uid, [sendingFlag]);
|
|
804
|
+
// Re-check — did we win the race?
|
|
805
|
+
const flagsAfter = await client.getFlags(outboxFolder.path, uid);
|
|
806
|
+
const sendingFlags = flagsAfter.filter(f => f.startsWith("$Sending"));
|
|
807
|
+
if (sendingFlags.length > 1 || (sendingFlags.length === 1 && sendingFlags[0] !== sendingFlag)) {
|
|
808
|
+
// Another machine claimed it — back off
|
|
809
|
+
await client.removeFlags(outboxFolder.path, uid, [sendingFlag]);
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
// Fetch the raw message
|
|
813
|
+
const msg = await client.fetchMessageByUid(outboxFolder.path, uid, { source: true });
|
|
814
|
+
if (!msg?.source) {
|
|
815
|
+
await client.removeFlags(outboxFolder.path, uid, [sendingFlag]);
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
// Send via SMTP
|
|
819
|
+
try {
|
|
820
|
+
const transport = createTransport({
|
|
821
|
+
host: account.smtp.host,
|
|
822
|
+
port: account.smtp.port,
|
|
823
|
+
secure: account.smtp.port === 465,
|
|
824
|
+
auth: account.smtp.auth === "password"
|
|
825
|
+
? { user: account.smtp.user, pass: account.smtp.password }
|
|
826
|
+
: undefined,
|
|
827
|
+
tls: { rejectUnauthorized: false },
|
|
828
|
+
});
|
|
829
|
+
// Parse recipients from raw message headers for SMTP envelope
|
|
830
|
+
const toMatch = msg.source.match(/^To:\s*(.+)$/mi);
|
|
831
|
+
const ccMatch = msg.source.match(/^Cc:\s*(.+)$/mi);
|
|
832
|
+
const bccMatch = msg.source.match(/^Bcc:\s*(.+)$/mi);
|
|
833
|
+
const fromMatch = msg.source.match(/^From:\s*(.+)$/mi);
|
|
834
|
+
const parseAddrs = (s) => s.match(/[\w.+-]+@[\w.-]+/g) || [];
|
|
835
|
+
const recipients = [
|
|
836
|
+
...(toMatch ? parseAddrs(toMatch[1]) : []),
|
|
837
|
+
...(ccMatch ? parseAddrs(ccMatch[1]) : []),
|
|
838
|
+
...(bccMatch ? parseAddrs(bccMatch[1]) : []),
|
|
839
|
+
];
|
|
840
|
+
const sender = fromMatch ? (parseAddrs(fromMatch[1])[0] || account.email) : account.email;
|
|
841
|
+
if (recipients.length === 0) {
|
|
842
|
+
console.error(` [outbox] No recipients in UID ${uid} — permanent failure`);
|
|
843
|
+
await client.removeFlags(outboxFolder.path, uid, [sendingFlag]);
|
|
844
|
+
await client.addFlags(outboxFolder.path, uid, ["$PermanentFailure"]);
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
// Strip Bcc header from raw message before sending
|
|
848
|
+
const rawToSend = msg.source.replace(/^Bcc:.*\r?\n/mi, "");
|
|
849
|
+
await transport.sendMail({
|
|
850
|
+
raw: rawToSend,
|
|
851
|
+
envelope: { from: sender, to: recipients },
|
|
852
|
+
});
|
|
853
|
+
console.log(` [outbox] Sent UID ${uid} → ${recipients.join(", ")}`);
|
|
854
|
+
// Move to Sent
|
|
855
|
+
const sentFolder = this.findFolder(accountId, "sent");
|
|
856
|
+
if (sentFolder) {
|
|
857
|
+
await client.moveMessage(msg, outboxFolder.path, sentFolder.path);
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
await client.deleteMessageByUid(outboxFolder.path, uid);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
catch (e) {
|
|
864
|
+
console.error(` [outbox] Send failed UID ${uid}: ${e.message}`);
|
|
865
|
+
await client.removeFlags(outboxFolder.path, uid, [sendingFlag]);
|
|
866
|
+
await client.addFlags(outboxFolder.path, uid, ["$Failed"]);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
finally {
|
|
871
|
+
try {
|
|
872
|
+
await client.logout();
|
|
873
|
+
}
|
|
874
|
+
catch { /* ignore */ }
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
/** Start background Outbox worker — checks every 10 seconds */
|
|
878
|
+
startOutboxWorker() {
|
|
879
|
+
if (this.outboxInterval)
|
|
880
|
+
return;
|
|
881
|
+
this.outboxInterval = setInterval(async () => {
|
|
882
|
+
for (const [accountId] of this.configs) {
|
|
883
|
+
try {
|
|
884
|
+
// First move any local queued messages to IMAP
|
|
885
|
+
await this.processLocalQueue(accountId);
|
|
886
|
+
// Then process IMAP Outbox
|
|
887
|
+
await this.processOutbox(accountId);
|
|
888
|
+
}
|
|
889
|
+
catch (e) {
|
|
890
|
+
console.error(` [outbox] Error for ${accountId}: ${e.message}`);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}, 10000);
|
|
894
|
+
}
|
|
895
|
+
/** Stop Outbox worker */
|
|
896
|
+
stopOutboxWorker() {
|
|
897
|
+
if (this.outboxInterval) {
|
|
898
|
+
clearInterval(this.outboxInterval);
|
|
899
|
+
this.outboxInterval = null;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// ── Google Contacts Sync ──
|
|
903
|
+
contactsSyncToken = null;
|
|
904
|
+
/** Get an OAuth token for Google Contacts API */
|
|
905
|
+
async getContactsToken(accountId) {
|
|
906
|
+
const settings = loadSettings();
|
|
907
|
+
const account = settings.accounts.find(a => a.id === accountId);
|
|
908
|
+
if (!account || account.imap.auth !== "oauth2")
|
|
909
|
+
return null;
|
|
910
|
+
// Find credentials.json — same as iflow uses
|
|
911
|
+
const iflowDir = path.resolve(import.meta.dirname, "..", "..", "..", "MailApps", "iflow");
|
|
912
|
+
const credentialsPath = path.join(iflowDir, "credentials.json");
|
|
913
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
914
|
+
console.error(" [contacts] credentials.json not found at", credentialsPath);
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
const accountDir = account.imap.user.replace(/[@.]/g, "_");
|
|
918
|
+
const tokenDir = path.join(iflowDir, "tokens", accountDir);
|
|
919
|
+
const token = await authenticateOAuth(credentialsPath, {
|
|
920
|
+
scope: "https://www.googleapis.com/auth/contacts.readonly",
|
|
921
|
+
tokenDirectory: tokenDir,
|
|
922
|
+
tokenFileName: "contacts-token.json",
|
|
923
|
+
credentialsKey: "installed",
|
|
924
|
+
includeOfflineAccess: true,
|
|
925
|
+
loginHint: account.imap.user,
|
|
926
|
+
});
|
|
927
|
+
return token?.access_token || null;
|
|
928
|
+
}
|
|
929
|
+
/** Sync contacts from Google People API */
|
|
930
|
+
async syncGoogleContacts(accountId) {
|
|
931
|
+
const token = await this.getContactsToken(accountId);
|
|
932
|
+
if (!token)
|
|
933
|
+
return 0;
|
|
934
|
+
let added = 0;
|
|
935
|
+
let nextPageToken;
|
|
936
|
+
const now = Date.now();
|
|
937
|
+
try {
|
|
938
|
+
do {
|
|
939
|
+
const params = new URLSearchParams({
|
|
940
|
+
personFields: "names,emailAddresses,organizations,photos",
|
|
941
|
+
pageSize: "100",
|
|
942
|
+
});
|
|
943
|
+
if (nextPageToken)
|
|
944
|
+
params.set("pageToken", nextPageToken);
|
|
945
|
+
if (this.contactsSyncToken)
|
|
946
|
+
params.set("syncToken", this.contactsSyncToken);
|
|
947
|
+
const url = `https://people.googleapis.com/v1/people/me/connections?${params}`;
|
|
948
|
+
const res = await fetch(url, {
|
|
949
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
950
|
+
});
|
|
951
|
+
if (res.status === 410) {
|
|
952
|
+
// Sync token expired — do full sync
|
|
953
|
+
this.contactsSyncToken = null;
|
|
954
|
+
return this.syncGoogleContacts(accountId);
|
|
955
|
+
}
|
|
956
|
+
if (!res.ok) {
|
|
957
|
+
const err = await res.text();
|
|
958
|
+
console.error(` [contacts] API error: ${res.status} ${err}`);
|
|
959
|
+
return added;
|
|
960
|
+
}
|
|
961
|
+
const data = await res.json();
|
|
962
|
+
if (data.connections) {
|
|
963
|
+
for (const person of data.connections) {
|
|
964
|
+
const emails = person.emailAddresses || [];
|
|
965
|
+
const names = person.names || [];
|
|
966
|
+
const orgs = person.organizations || [];
|
|
967
|
+
const name = names[0]?.displayName || "";
|
|
968
|
+
const org = orgs[0]?.name || "";
|
|
969
|
+
const googleId = person.resourceName || "";
|
|
970
|
+
for (const emailEntry of emails) {
|
|
971
|
+
const email = emailEntry.value?.toLowerCase();
|
|
972
|
+
if (!email)
|
|
973
|
+
continue;
|
|
974
|
+
// Upsert into contacts
|
|
975
|
+
const existing = this.db.searchContacts(email, 1);
|
|
976
|
+
if (existing.length > 0 && existing[0].email === email) {
|
|
977
|
+
// Update name/org if from google
|
|
978
|
+
this.db.recordSentAddress(name, email);
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
this.db.recordSentAddress(name, email);
|
|
982
|
+
added++;
|
|
983
|
+
}
|
|
984
|
+
// Update google-specific fields
|
|
985
|
+
try {
|
|
986
|
+
this.db.db.prepare("UPDATE contacts SET source = 'google', google_id = ?, organization = ?, updated_at = ? WHERE email = ?").run(googleId, org, now, email);
|
|
987
|
+
}
|
|
988
|
+
catch { /* ignore */ }
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
nextPageToken = data.nextPageToken;
|
|
993
|
+
if (data.nextSyncToken)
|
|
994
|
+
this.contactsSyncToken = data.nextSyncToken;
|
|
995
|
+
} while (nextPageToken);
|
|
996
|
+
console.log(` [contacts] Synced ${added} new contacts from Google`);
|
|
997
|
+
}
|
|
998
|
+
catch (e) {
|
|
999
|
+
console.error(` [contacts] Sync error: ${e.message}`);
|
|
1000
|
+
}
|
|
1001
|
+
return added;
|
|
1002
|
+
}
|
|
1003
|
+
/** Sync contacts for all OAuth accounts */
|
|
1004
|
+
async syncAllContacts() {
|
|
1005
|
+
const settings = loadSettings();
|
|
1006
|
+
for (const account of settings.accounts) {
|
|
1007
|
+
if (account.imap.auth === "oauth2" && account.enabled) {
|
|
1008
|
+
await this.syncGoogleContacts(account.id);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
/** Shut down all watchers and timers */
|
|
1013
|
+
async shutdown() {
|
|
1014
|
+
this.stopPeriodicSync();
|
|
1015
|
+
this.stopOutboxWorker();
|
|
1016
|
+
await this.stopWatching();
|
|
1017
|
+
for (const [, client] of this.fetchClients) {
|
|
1018
|
+
try {
|
|
1019
|
+
await client.logout();
|
|
1020
|
+
}
|
|
1021
|
+
catch { /* ignore */ }
|
|
1022
|
+
}
|
|
1023
|
+
this.fetchClients.clear();
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
//# sourceMappingURL=index.js.map
|