diff --git a/changelog.md b/changelog.md index cf2417a..74bac22 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2025-03-15 - 2.0.0 - BREAKING CHANGE(platformservice) +Remove deprecated AIBridge module and update email service to use the MTA connector; update dependency versions and adjust build scripts in package.json. + +- Completely remove the aibridge module files (aibridge.classes.aibridge.ts, aibridge.classes.aibridgedb.ts, aibridge.classes.openaibridge.ts, aibridge.paths.ts, aibridge.plugins.ts, and index.ts) as they are no longer needed. +- Switch the email service from using MailgunConnector to the new MTA connector for sending emails. +- Update dependency versions for @serve.zone/interfaces, @tsclass/tsclass, letterxpress, and uuid in package.json. +- Enhance the build script in package.json and add pnpm configuration. + ## 2025-03-15 - 1.1.2 - fix(mta) Expose HttpResponse.statusCode and add explicit generic type annotations in DNSManager cache retrieval diff --git a/package.json b/package.json index 96f1e1a..997e0bb 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test": "(tstest test/)", "start": "(node --max_old_space_size=250 ./cli.js)", "startTs": "(node cli.ts.js)", + "build": "(tsbuild tsfolders --allowimplicitany)", "localPublish": "" }, "devDependencies": { @@ -22,12 +23,11 @@ "@push.rocks/tapbundle": "^5.0.22" }, "dependencies": { - "@anthropic-ai/sdk": "^0.18.0", "@api.global/typedrequest": "^3.0.19", "@api.global/typedserver": "^3.0.27", "@api.global/typedsocket": "^3.0.0", "@apiclient.xyz/cloudflare": "^6.0.3", - "@apiclient.xyz/letterxpress": "^1.0.17", + "@apiclient.xyz/letterxpress": "^1.0.20", "@push.rocks/projectinfo": "^5.0.1", "@push.rocks/qenv": "^6.0.5", "@push.rocks/smartdata": "^5.0.7", @@ -37,14 +37,15 @@ "@push.rocks/smartpath": "^5.0.5", "@push.rocks/smartpromise": "^4.0.3", "@push.rocks/smartrequest": "^2.0.21", + "@push.rocks/smartrule": "^2.0.1", "@push.rocks/smartrx": "^3.0.7", "@push.rocks/smartstate": "^2.0.0", - "@serve.zone/interfaces": "^1.0.47", - "@tsclass/tsclass": "^4.0.52", + "@serve.zone/interfaces": "^4.12.1", + "@tsclass/tsclass": "^5.0.0", + "@types/mailparser": "^3.4.5", "mailauth": "^4.6.5", "mailparser": "^3.6.9", - "openai": "^4.29.2", - "uuid": "^9.0.1" + "uuid": "^11.1.0" }, "keywords": [ "mail service", @@ -67,5 +68,12 @@ "rule management", "SMTP STARTTLS", "DNS management" - ] + ], + "pnpm": { + "onlyBuiltDependencies": [ + "esbuild", + "mongodb-memory-server", + "puppeteer" + ] + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34cf42f..81db5af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@anthropic-ai/sdk': - specifier: ^0.18.0 - version: 0.18.0 '@api.global/typedrequest': specifier: ^3.0.19 version: 3.1.10 @@ -24,8 +21,8 @@ importers: specifier: ^6.0.3 version: 6.0.3 '@apiclient.xyz/letterxpress': - specifier: ^1.0.17 - version: 1.0.19(typescript@5.7.3) + specifier: ^1.0.20 + version: 1.0.20(typescript@5.7.3) '@push.rocks/projectinfo': specifier: ^5.0.1 version: 5.0.2 @@ -53,6 +50,9 @@ importers: '@push.rocks/smartrequest': specifier: ^2.0.21 version: 2.0.23 + '@push.rocks/smartrule': + specifier: ^2.0.1 + version: 2.0.1 '@push.rocks/smartrx': specifier: ^3.0.7 version: 3.0.7 @@ -60,23 +60,23 @@ importers: specifier: ^2.0.0 version: 2.0.19 '@serve.zone/interfaces': - specifier: ^1.0.47 - version: 1.1.2 + specifier: ^4.12.1 + version: 4.12.1 '@tsclass/tsclass': - specifier: ^4.0.52 - version: 4.4.4 + specifier: ^5.0.0 + version: 5.0.0 + '@types/mailparser': + specifier: ^3.4.5 + version: 3.4.5 mailauth: specifier: ^4.6.5 version: 4.8.2 mailparser: specifier: ^3.6.9 version: 3.7.2 - openai: - specifier: ^4.29.2 - version: 4.87.3(ws@8.18.1)(zod@3.24.2) uuid: - specifier: ^9.0.1 - version: 9.0.1 + specifier: ^11.1.0 + version: 11.1.0 devDependencies: '@git.zone/tsbuild': specifier: ^2.1.17 @@ -96,9 +96,6 @@ importers: packages: - '@anthropic-ai/sdk@0.18.0': - resolution: {integrity: sha512-3XsWEn/4nPGRd4AdSguugbSDFy6Z2AWTNOeI3iK+aV22+w23+vY9CEb3Hiy0kvKIQuxSmZz/+5WKC8nPWy8gVg==} - '@api.global/typedrequest-interfaces@2.0.2': resolution: {integrity: sha512-D+mkr4IiUZ/eUgrdp5jXjBKOW/iuMcl0z2ZLQsLLypKX/psFGD3viZJ58FNRa+/1OSM38JS5wFyoWl8oPEFLrw==} @@ -117,8 +114,8 @@ packages: '@apiclient.xyz/cloudflare@6.0.3': resolution: {integrity: sha512-NOPHFrKVxfkrRn9lVBLCdo0Ibm08PuaJe3A1K6TmXWxgbeJSqGkgVMGRwLilnol/062v6/1w2ZFO6f3fAxk5Gw==} - '@apiclient.xyz/letterxpress@1.0.19': - resolution: {integrity: sha512-+TGKZ0Xq3yoxXxLNn+WLvI0zKSRZPN0t9hpgNcqehVZ7srcRdDvVewsXOL6NJVXdUByy3yfP99yMaW+9rXYnkA==} + '@apiclient.xyz/letterxpress@1.0.20': + resolution: {integrity: sha512-dPkuUa+wi0IG/NmIuCfdz0VMYYFKNQyZSDVMJXAHZPu3YzHf2xHr45n3VxwP5wZur+NG/+6pneoOnyzUosIKmw==} '@apiglobal/typedrequest-interfaces@2.0.1': resolution: {integrity: sha512-Oi7pNU4vKo5UvcCJmqkH43Us237Ws/Pp/WDYnwnonRnTmIMd+6QjNfN/gXcPnP6tbamk8r8Xzcz9mgnSDM2ysw==} @@ -823,9 +820,6 @@ packages: '@push.rocks/smartbucket@3.3.7': resolution: {integrity: sha512-RiOuEtwHJ+HFbV1nlZgh5VuMvP6PXElX6rVe7OSQsyNCBybRQa/d1qDic92+2Ejx852DGeHlyREELQCxd/a/7w==} - '@push.rocks/smartbuffer@2.0.3': - resolution: {integrity: sha512-5AcGbnuRSaF2Iyn3a1JNbNtDw0/xEaS6AOL2uTTtWqBJQQf088GFKS0/8Gn7TJiCnX+N+KOEGwi+5BAQKvcZeQ==} - '@push.rocks/smartbuffer@3.0.4': resolution: {integrity: sha512-TLfhx/JD61YC8XGO9TI6Ux6US38R14HaIM84QT8hZZod8axfXrg+h8xA8tMUBpSV8PXsQy9LzxmOq0Il1fmDXw==} @@ -949,6 +943,9 @@ packages: '@push.rocks/smartrouter@1.3.2': resolution: {integrity: sha512-JtkxClN4CaHXMSeLDNvfWPwiVEPdEoQVSX2ee3gLgbXNO9dt9hvXdIhFrnFeLwyeA6M8nJdb9SqjrjZroYJsxw==} + '@push.rocks/smartrule@2.0.1': + resolution: {integrity: sha512-8oYEnS9z+NgCAcUtXPMguYyZpHqA/ROp0bxVQwUaHDwa3YzzA8jHIXvA94hk3sxvkk0xmIpp4UhBEelzIwwJow==} + '@push.rocks/smartrx@3.0.7': resolution: {integrity: sha512-qCWy0s3RLAgGSnaw/Gu0BNaJ59CsI6RK5OJDCCqxc7P2X/S755vuLtnAR5/0dEjdhCHXHX9ytPZx+o9g/CNiyA==} @@ -1149,8 +1146,8 @@ packages: '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} - '@serve.zone/interfaces@1.1.2': - resolution: {integrity: sha512-ajHArBX7iJcUPYjYoO1rx78Mz5IzHIKzxt34kWKk5fSDrOkAoNwk0P/zpm0JZ6ClQy7/maOHTiSsNpdbBNvhQg==} + '@serve.zone/interfaces@4.12.1': + resolution: {integrity: sha512-M1ZfXmhlo3OyTtmhYWmQIVzd/AwTM5ANMHSuoZDuddq7K0UwmdYJNjRUK7nR1g4PucwFzpbMgUuXQSkhBJfvAw==} '@sideway/address@4.1.5': resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} @@ -1405,6 +1402,9 @@ packages: '@tsclass/tsclass@4.4.4': resolution: {integrity: sha512-YZOAF+u+r4u5rCev2uUd1KBTBdfyFdtDmcv4wuN+864lMccbdfRICR3SlJwCfYS1lbeV3QNLYGD30wjRXgvCJA==} + '@tsclass/tsclass@5.0.0': + resolution: {integrity: sha512-2X66VCk0Oe1L01j6GQHC6F9Gj7lpZPPSUTDNax7e29lm4OqBTyAzTR3ePR8coSbWBwsmRV8awLRSrSI+swlqWA==} + '@types/accepts@1.3.7': resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} @@ -1535,6 +1535,9 @@ packages: '@types/koa@2.15.0': resolution: {integrity: sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==} + '@types/mailparser@3.4.5': + resolution: {integrity: sha512-EPERBp7fLeFZh7tS2X36MF7jawUx3Y6/0rXciZah3CTYgwLi3e0kpGUJ6FOmUabgzis/U1g+3/JzrVWbWIOGjg==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -1553,15 +1556,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node-fetch@2.6.12': - resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} - '@types/node-forge@1.3.11': resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} - '@types/node@18.19.80': - resolution: {integrity: sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==} - '@types/node@22.13.10': resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} @@ -1687,10 +1684,6 @@ packages: resolution: {integrity: sha512-84E1025aUSjvZU1j17eCTwV7m5Zg3cZHErV3+CaJM9JPCesZwLraIa0ONIQ9w4KLgcDgJFw9UnJ0LbFf42h6tg==} engines: {node: '>=18.0.0'} - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -1815,9 +1808,6 @@ packages: bare-events: optional: true - base-64@0.1.0: - resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1948,9 +1938,6 @@ packages: character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - charenc@0.0.2: - resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} - chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -2105,9 +2092,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crypt@0.0.2: - resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} - crypto-random-string@5.0.0: resolution: {integrity: sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ==} engines: {node: '>=14.16'} @@ -2246,9 +2230,6 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - digest-fetch@1.3.0: - resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==} - dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} @@ -2423,10 +2404,6 @@ packages: resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=} engines: {node: '>= 0.6'} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -2551,9 +2528,6 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data-encoder@1.7.2: - resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} - form-data-encoder@2.1.4: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} @@ -2566,10 +2540,6 @@ packages: resolution: {integrity: sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=} engines: {node: '>=0.4.x'} - formdata-node@4.4.1: - resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} - engines: {node: '>= 12.20'} - forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -2861,9 +2831,6 @@ packages: is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - is-buffer@1.1.6: - resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} - is-deflate@1.0.0: resolution: {integrity: sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ=} @@ -3237,9 +3204,6 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - md5@2.3.0: - resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} - mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -3545,19 +3509,6 @@ packages: no-case@2.3.2: resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - node-forge@1.3.1: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} @@ -3618,18 +3569,6 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} - openai@4.87.3: - resolution: {integrity: sha512-d2D54fzMuBYTxMW8wcNmhT1rYKcTfMJ8t+4KjH2KtvYenygITiGBgHoIrzHwnDQWW+C5oCA+ikIR2jgPCFqcKQ==} - hasBin: true - peerDependencies: - ws: ^8.18.0 - zod: ^3.23.8 - peerDependenciesMeta: - ws: - optional: true - zod: - optional: true - p-cancelable@3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} @@ -4303,9 +4242,6 @@ packages: resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} engines: {node: '>=14.16'} - tr46@0.0.3: - resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=} - tr46@3.0.0: resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} engines: {node: '>=12'} @@ -4385,9 +4321,6 @@ packages: resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} engines: {node: '>=18'} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} @@ -4442,6 +4375,10 @@ packages: resolution: {integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=} engines: {node: '>= 0.4.0'} + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -4456,17 +4393,6 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - - web-streams-polyfill@4.0.0-beta.3: - resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} - engines: {node: '>= 14'} - - webidl-conversions@3.0.1: - resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=} - webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -4483,9 +4409,6 @@ packages: resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} - whatwg-url@5.0.0: - resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=} - which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} @@ -4626,20 +4549,6 @@ packages: snapshots: - '@anthropic-ai/sdk@0.18.0': - dependencies: - '@types/node': 18.19.80 - '@types/node-fetch': 2.6.12 - abort-controller: 3.0.0 - agentkeepalive: 4.6.0 - digest-fetch: 1.3.0 - form-data-encoder: 1.7.2 - formdata-node: 4.4.1 - node-fetch: 2.7.0 - web-streams-polyfill: 3.3.3 - transitivePeerDependencies: - - encoding - '@api.global/typedrequest-interfaces@2.0.2': {} '@api.global/typedrequest-interfaces@3.0.19': {} @@ -4730,15 +4639,15 @@ snapshots: '@pushrocks/smartstring': 4.0.7 '@tsclass/tsclass': 4.4.4 - '@apiclient.xyz/letterxpress@1.0.19(typescript@5.7.3)': + '@apiclient.xyz/letterxpress@1.0.20(typescript@5.7.3)': dependencies: '@design.estate/dees-document': 1.6.9(typescript@5.7.3) - '@push.rocks/smartbuffer': 2.0.3 + '@push.rocks/smartbuffer': 3.0.4 '@push.rocks/smarthash': 3.0.4 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrequest': 2.0.23 '@push.rocks/smartrx': 3.0.7 - '@tsclass/tsclass': 4.4.4 + '@tsclass/tsclass': 5.0.0 transitivePeerDependencies: - '@nuxt/kit' - bare-buffer @@ -5654,8 +5563,10 @@ snapshots: '@push.rocks/taskbuffer': 3.1.7 transitivePeerDependencies: - '@nuxt/kit' + - bufferutil - react - supports-color + - utf-8-validate - vue '@hapi/bourne@3.0.0': {} @@ -5961,10 +5872,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@push.rocks/smartbuffer@2.0.3': - dependencies: - uint8array-extras: 1.4.0 - '@push.rocks/smartbuffer@3.0.4': dependencies: uint8array-extras: 1.4.0 @@ -6299,6 +6206,8 @@ snapshots: '@push.rocks/smartrx': 3.0.7 path-to-regexp: 8.2.0 + '@push.rocks/smartrule@2.0.1': {} + '@push.rocks/smartrx@3.0.7': dependencies: '@push.rocks/smartpromise': 4.2.3 @@ -6702,7 +6611,7 @@ snapshots: domhandler: 5.0.3 selderee: 0.11.0 - '@serve.zone/interfaces@1.1.2': + '@serve.zone/interfaces@4.12.1': dependencies: '@api.global/typedrequest-interfaces': 3.0.19 '@push.rocks/smartlog-interfaces': 3.0.2 @@ -7075,6 +6984,10 @@ snapshots: dependencies: type-fest: 4.37.0 + '@tsclass/tsclass@5.0.0': + dependencies: + type-fest: 4.37.0 + '@types/accepts@1.3.7': dependencies: '@types/node': 22.13.10 @@ -7247,6 +7160,11 @@ snapshots: '@types/koa-compose': 3.2.8 '@types/node': 22.13.10 + '@types/mailparser@3.4.5': + dependencies: + '@types/node': 22.13.10 + iconv-lite: 0.6.3 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -7261,19 +7179,10 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node-fetch@2.6.12': - dependencies: - '@types/node': 18.19.80 - form-data: 4.0.2 - '@types/node-forge@1.3.11': dependencies: '@types/node': 22.13.10 - '@types/node@18.19.80': - dependencies: - undici-types: 5.26.5 - '@types/node@22.13.10': dependencies: undici-types: 6.20.0 @@ -7458,10 +7367,6 @@ snapshots: - supports-color - utf-8-validate - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -7571,8 +7476,6 @@ snapshots: bare-events: 2.5.4 optional: true - base-64@0.1.0: {} - base64-js@1.5.1: {} base64id@2.0.0: {} @@ -7724,8 +7627,6 @@ snapshots: character-entities@2.0.2: {} - charenc@0.0.2: {} - chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -7871,8 +7772,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypt@0.0.2: {} - crypto-random-string@5.0.0: dependencies: type-fest: 2.19.0 @@ -7969,11 +7868,6 @@ snapshots: diff-sequences@29.6.3: {} - digest-fetch@1.3.0: - dependencies: - base-64: 0.1.0 - md5: 2.3.0 - dijkstrajs@1.0.3: {} dir-glob@3.0.1: @@ -8186,8 +8080,6 @@ snapshots: etag@1.8.1: {} - event-target-shim@5.0.1: {} - eventemitter3@4.0.7: {} execa@5.1.1: @@ -8368,8 +8260,6 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data-encoder@1.7.2: {} - form-data-encoder@2.1.4: {} form-data@4.0.2: @@ -8381,11 +8271,6 @@ snapshots: format@0.2.2: {} - formdata-node@4.4.1: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 4.0.0-beta.3 - forwarded@0.2.0: {} fresh@0.5.2: {} @@ -8748,8 +8633,6 @@ snapshots: is-arrayish@0.3.2: {} - is-buffer@1.1.6: {} - is-deflate@1.0.0: {} is-docker@2.2.1: {} @@ -9165,12 +9048,6 @@ snapshots: md5-file@5.0.0: {} - md5@2.3.0: - dependencies: - charenc: 0.0.2 - crypt: 0.0.2 - is-buffer: 1.1.6 - mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -9645,12 +9522,6 @@ snapshots: dependencies: lower-case: 1.1.4 - node-domexception@1.0.0: {} - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - node-forge@1.3.1: {} nodemailer@6.9.16: {} @@ -9699,21 +9570,6 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openai@4.87.3(ws@8.18.1)(zod@3.24.2): - dependencies: - '@types/node': 18.19.80 - '@types/node-fetch': 2.6.12 - abort-controller: 3.0.0 - agentkeepalive: 4.6.0 - form-data-encoder: 1.7.2 - formdata-node: 4.4.1 - node-fetch: 2.7.0 - optionalDependencies: - ws: 8.18.1 - zod: 3.24.2 - transitivePeerDependencies: - - encoding - p-cancelable@3.0.0: {} p-event@4.2.0: @@ -10509,8 +10365,6 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - tr46@0.0.3: {} - tr46@3.0.0: dependencies: punycode: 2.3.1 @@ -10567,8 +10421,6 @@ snapshots: uint8array-extras@1.4.0: {} - undici-types@5.26.5: {} - undici-types@6.20.0: {} undici@5.28.4: @@ -10627,6 +10479,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@11.1.0: {} + uuid@9.0.1: {} vary@1.1.2: {} @@ -10641,12 +10495,6 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - web-streams-polyfill@3.3.3: {} - - web-streams-polyfill@4.0.0-beta.3: {} - - webidl-conversions@3.0.1: {} - webidl-conversions@7.0.0: {} whatwg-mimetype@3.0.0: {} @@ -10661,11 +10509,6 @@ snapshots: tr46: 5.1.0 webidl-conversions: 7.0.0 - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - which-module@2.0.1: {} which@2.0.2: diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index b8fb205..a0e7b2e 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/platformservice', - version: '1.1.2', + version: '2.0.0', description: 'A multifaceted platform service handling mail, SMS, letter delivery, and AI services.' } diff --git a/ts/aibridge/aibridge.classes.aibridge.ts b/ts/aibridge/aibridge.classes.aibridge.ts deleted file mode 100644 index f25b06f..0000000 --- a/ts/aibridge/aibridge.classes.aibridge.ts +++ /dev/null @@ -1,50 +0,0 @@ -import * as plugins from './aibridge.plugins.js'; -import * as paths from './aibridge.paths.js'; -import { AiBridgeDb } from './aibridge.classes.aibridgedb.js'; -import { OpenAiBridge } from './aibridge.classes.openaibridge.js'; - -export class AiBridge { - public projectinfo: plugins.projectinfo.ProjectInfo; - public serverInstance: plugins.loleServiceserver.ServiceServer; - public serviceQenv = new plugins.qenv.Qenv('./', './.nogit'); - public aibridgeDb: AiBridgeDb; - - public openAiBridge: OpenAiBridge; - - public typedrouter = new plugins.typedrequest.TypedRouter(); - - public async start() { - this.aibridgeDb = new AiBridgeDb(this); - await this.aibridgeDb.start(); - this.projectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir); - this.openAiBridge = new OpenAiBridge(this); - await this.openAiBridge.start(); - - // server - this.serverInstance = new plugins.loleServiceserver.ServiceServer({ - serviceDomain: 'aibridge.lossless.one', - serviceName: 'aibridge', - serviceVersion: this.projectinfo.npm.version, - addCustomRoutes: async (serverArg) => { - // any custom route configs go here - }, - }); - - // lets implemenet the actual typedrequest functions - this.typedrouter.addTypedHandler(new plugins.typedrequest.TypedHandler('chat', async reqArg => { - const resultChat = await this.openAiBridge.chat(reqArg.chat.systemMessage, reqArg.chat.messages[reqArg.chat.messages.length - 1].content, reqArg.chat.messages); - return { - chat: reqArg.chat, - latestMessage: resultChat.message.content, - } - })) - - await this.serverInstance.start(); - this.serverInstance.typedServer.typedrouter.addTypedRouter(this.typedrouter); - } - - public async stop() { - await this.serverInstance.stop(); - await this.aibridgeDb.stop(); - } -} diff --git a/ts/aibridge/aibridge.classes.aibridgedb.ts b/ts/aibridge/aibridge.classes.aibridgedb.ts deleted file mode 100644 index 8caa360..0000000 --- a/ts/aibridge/aibridge.classes.aibridgedb.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as plugins from './aibridge.plugins.js'; -import { AiBridge } from './aibridge.classes.aibridge.js'; - -export class AiBridgeDb { - public smartdataDb: plugins.smartdata.SmartdataDb; - public aibridgeRef: AiBridge; - - constructor(aibridgeRefArg: AiBridge) { - this.aibridgeRef = aibridgeRefArg; - } - - public async start() { - this.smartdataDb = new plugins.smartdata.SmartdataDb({ - mongoDbUser: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_USER'), - mongoDbName: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_NAME'), - mongoDbPass: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_PASS'), - mongoDbUrl: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_URL'), - }); - await this.smartdataDb.init(); - } - - public async stop() { - await this.smartdataDb.close(); - } -} diff --git a/ts/aibridge/aibridge.classes.openaibridge.ts b/ts/aibridge/aibridge.classes.openaibridge.ts deleted file mode 100644 index 4cb8304..0000000 --- a/ts/aibridge/aibridge.classes.openaibridge.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { AiBridge } from './aibridge.classes.aibridge.js'; -import * as plugins from './aibridge.plugins.js'; -import * as paths from './aibridge.paths.js'; - -export class OpenAiBridge { - public aiBridgeRef: AiBridge; - public openAiApiClient: plugins.openai.default; - constructor(aiBridgeRefArg: AiBridge) { - this.aiBridgeRef = aiBridgeRefArg; - } - - public async start() { - const openAiToken = await this.aiBridgeRef.serviceQenv.getEnvVarOnDemand('OPENAI_TOKEN'); - this.openAiApiClient = new plugins.openai.default({ - apiKey: openAiToken, - dangerouslyAllowBrowser: true, - }); - } - - public async stop() {} - - public async chat( - systemMessage: string, - userMessage: string, - messageHistory: { - role: 'assistant' | 'user'; - content: string; - }[] - ) { - const result = await this.openAiApiClient.chat.completions.create({ - model: 'gpt-4-turbo-preview', - messages: [ - { role: 'system', content: systemMessage }, - ...messageHistory, - { role: 'user', content: userMessage }, - ], - }); - return { - message: result.choices[0].message, - }; - } - - public async audio(messageArg: string) { - const done = plugins.smartpromise.defer(); - const result = await this.openAiApiClient.audio.speech.create({ - model: 'tts-1-hd', - input: messageArg, - voice: 'nova', - response_format: 'mp3', - speed: 1, - }); - const stream = result.body.pipe(plugins.smartfile.fsStream.createWriteStream(plugins.path.join(paths.nogitDir, 'output.mp3'))); - stream.on('finish', () => { - done.resolve(); - }); - return done.promise; - } -} diff --git a/ts/aibridge/aibridge.paths.ts b/ts/aibridge/aibridge.paths.ts deleted file mode 100644 index c49e550..0000000 --- a/ts/aibridge/aibridge.paths.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as plugins from './aibridge.plugins.js'; - -export const packageDir = plugins.path.join( - plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), - '../' -); - -export const assetsDir = plugins.path.join( - packageDir, - './assets/' -); - -export const nogitDir = plugins.path.join( - packageDir, - './.nogit/' -); diff --git a/ts/aibridge/aibridge.plugins.ts b/ts/aibridge/aibridge.plugins.ts deleted file mode 100644 index ac9e2e8..0000000 --- a/ts/aibridge/aibridge.plugins.ts +++ /dev/null @@ -1,32 +0,0 @@ -// node native -import * as path from 'path'; - -export { path }; - -// @losslessone_private scope -import * as loleServiceserver from '@losslessone_private/lole-serviceserver'; -import * as lointAiBridge from '@losslessone_private/loint-aibridge'; - -export { loleServiceserver, lointAiBridge }; - -// apiglobal scope -import * as typedrequest from '@api.global/typedrequest'; - -export { - typedrequest, -} - -// pushrocks scope -import * as projectinfo from '@push.rocks/projectinfo'; -import * as qenv from '@push.rocks/qenv'; -import * as smartdata from '@push.rocks/smartdata'; -import * as smartfile from '@push.rocks/smartfile'; -import * as smartpath from '@push.rocks/smartpath'; -import * as smartpromise from '@push.rocks/smartpromise'; - -export { projectinfo, qenv, smartdata, smartfile, smartpath, smartpromise }; - -// thirdparty scope -import * as antrophic from '@anthropic-ai/sdk'; -import * as openai from 'openai'; -export { antrophic as anthropic, openai }; diff --git a/ts/aibridge/index.ts b/ts/aibridge/index.ts deleted file mode 100644 index e8332f3..0000000 --- a/ts/aibridge/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { AiBridge } from './aibridge.classes.aibridge.js'; - -export { - AiBridge, -} - -let aibridgeInstance: AiBridge; -export const runCli = async () => { - aibridgeInstance = new AiBridge(); - await aibridgeInstance.start(); -}; - -export const stop = async () => { - if (aibridgeInstance) { - await aibridgeInstance.stop(); - } -}; diff --git a/ts/email/email.classes.apimanager.ts b/ts/email/email.classes.apimanager.ts index e10de35..9fe75ab 100644 --- a/ts/email/email.classes.apimanager.ts +++ b/ts/email/email.classes.apimanager.ts @@ -4,12 +4,21 @@ import { logger } from '../logger.js'; export class ApiManager { public emailRef: EmailService; - public typedRouter = new plugins.typedrequest.TypedRouter(); constructor(emailRefArg: EmailService) { this.emailRef = emailRefArg; this.emailRef.typedrouter.addTypedRouter(this.typedRouter); + + // Register API endpoints + this.registerApiEndpoints(); + } + + /** + * Register API endpoints for email functionality + */ + private registerApiEndpoints() { + // Register the SendEmail endpoint this.typedRouter.addTypedHandler( new plugins.typedrequest.TypedHandler('sendEmail', async (requestData) => { const mailToSend = new plugins.smartmail.Smartmail({ @@ -30,10 +39,12 @@ export class ApiManager { } } - await this.emailRef.mailgunConnector.sendEmail(mailToSend, requestData.to, {}); + // Send email through the service which will route to the appropriate connector + const emailId = await this.emailRef.sendEmail(mailToSend, requestData.to, {}); + logger.log( 'info', - `send an email to ${requestData.to} with subject '${mailToSend.getSubject()}'`, + `sent an email to ${requestData.to} with subject '${mailToSend.getSubject()}'`, { eventType: 'sentEmail', email: { @@ -42,10 +53,35 @@ export class ApiManager { }, } ); + return { - responseId: 'abc', // TODO: generate proper response id + responseId: emailId, }; }) ); + + // Add endpoint to check email status + this.typedRouter.addTypedHandler<{ emailId: string }>( + new plugins.typedrequest.TypedHandler('checkEmailStatus', async (requestData) => { + // If MTA is enabled, use it to check status + if (this.emailRef.mtaConnector) { + const status = await this.emailRef.mtaConnector.checkEmailStatus(requestData.emailId); + return status; + } + + // For Mailgun, we don't have a status check implementation currently + return { + status: 'unknown', + details: { message: 'Status tracking not available for current provider' } + }; + }) + ); + + // Add statistics endpoint + this.typedRouter.addTypedHandler( + new plugins.typedrequest.TypedHandler('getEmailStats', async () => { + return this.emailRef.getStats(); + }) + ); } -} +} \ No newline at end of file diff --git a/ts/email/email.classes.connector.mailgun.ts b/ts/email/email.classes.connector.mailgun.ts deleted file mode 100644 index 89f0dc0..0000000 --- a/ts/email/email.classes.connector.mailgun.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as plugins from './email.plugins.js'; -import { EmailService } from './email.classes.emailservice.js'; - -export class MailgunConnector { - public emailRef: EmailService; - public mailgunAccount: plugins.mailgun.MailgunAccount; - - constructor(emailRefArg: EmailService) { - this.emailRef = emailRefArg; - this.mailgunAccount = new plugins.mailgun.MailgunAccount({ - apiToken: this.emailRef.qenv.getEnvVarOnDemand('MAILGUN_API_TOKEN'), - region: 'eu', - }); - this.mailgunAccount.addSmtpCredentials( - this.emailRef.qenv.getEnvVarOnDemand('MAILGUN_SMTP_CREDENTIALS') - ); - } - - public async sendEmail( - smartMailArg: plugins.smartmail.Smartmail, - toArg: string, - dataArg: any = {} - ) { - this.mailgunAccount.sendSmartMail(smartMailArg, toArg, dataArg); - } - - public async receiveEmail(messageUrl: string) { - return await this.mailgunAccount.retrieveSmartMailFromMessageUrl(messageUrl); - } -} diff --git a/ts/email/email.classes.connector.mta.ts b/ts/email/email.classes.connector.mta.ts new file mode 100644 index 0000000..02b9b53 --- /dev/null +++ b/ts/email/email.classes.connector.mta.ts @@ -0,0 +1,169 @@ +import * as plugins from '../plugins.js'; +import { EmailService } from './email.classes.emailservice.js'; +import { logger } from '../logger.js'; + +// Import MTA classes +import { + MtaService, + Email as MtaEmail, + type IEmailOptions, + DeliveryStatus, + type IAttachment +} from '../mta/index.js'; + +export class MtaConnector { + public emailRef: EmailService; + private mtaService: MtaService; + + constructor(emailRefArg: EmailService, mtaService?: MtaService) { + this.emailRef = emailRefArg; + this.mtaService = mtaService || this.emailRef.mtaService; + } + + /** + * Send an email using the MTA service + * @param smartmail The email to send + * @param toAddresses Recipients (comma-separated or array) + * @param options Additional options + */ + public async sendEmail( + smartmail: plugins.smartmail.Smartmail<>, + toAddresses: string | string[], + options: any = {} + ): Promise { + try { + // Process recipients + const toArray = Array.isArray(toAddresses) + ? toAddresses + : toAddresses.split(',').map(addr => addr.trim()); + + // Map SmartMail attachments to MTA attachments + const attachments: IAttachment[] = smartmail.attachments.map(attachment => { + return { + filename: attachment.parsedPath.base, + content: Buffer.from(attachment.contentBuffer), + contentType: (attachment as any)?.getContentType?.() || 'application/octet-stream' // TODO: revisit after smartfile has been updated + }; + }); + + // Create MTA Email + const mtaEmail = new MtaEmail({ + from: smartmail.options.from, + to: toArray, + subject: smartmail.getSubject(), + text: smartmail.getBody(false), // Plain text version + html: smartmail.getBody(true), // HTML version + attachments + }); + + // Send using MTA + const emailId = await this.mtaService.send(mtaEmail); + + logger.log('info', `Email sent via MTA to ${toAddresses}`, { + eventType: 'sentEmail', + provider: 'mta', + emailId, + to: toAddresses + }); + + return emailId; + } catch (error) { + logger.log('error', `Failed to send email via MTA: ${error.message}`, { + eventType: 'emailError', + provider: 'mta', + error: error.message + }); + throw error; + } + } + + /** + * Retrieve and process an incoming email + * For MTA, this would handle an email already received by the SMTP server + * @param emailData The raw email data or identifier + */ + public async receiveEmail(emailData: string): Promise> { + try { + // In a real implementation, this would retrieve an email from the MTA storage + // For now, we can use a simplified approach: + + // Parse the email (assuming emailData is a raw email or a file path) + const parsedEmail = await plugins.mailparser.simpleParser(emailData); + + // Create a Smartmail from the parsed email + const smartmail = new plugins.smartmail.Smartmail({ + from: parsedEmail.from?.text || '', + subject: parsedEmail.subject || '', + body: parsedEmail.html || parsedEmail.text || '', + creationObjectRef: { + From: parsedEmail.from?.text || '', + To: parsedEmail.to?.text || '', + Subject: parsedEmail.subject || '' + } + }); + + // Add attachments if present + if (parsedEmail.attachments && parsedEmail.attachments.length > 0) { + for (const attachment of parsedEmail.attachments) { + smartmail.addAttachment( + await plugins.smartfile.SmartFile.fromBuffer( + attachment.filename || 'attachment', + attachment.content + ) + ); + } + } + + return smartmail; + } catch (error) { + logger.log('error', `Failed to receive email via MTA: ${error.message}`, { + eventType: 'emailError', + provider: 'mta', + error: error.message + }); + throw error; + } + } + + /** + * Check the status of a sent email + * @param emailId The email ID to check + */ + public async checkEmailStatus(emailId: string): Promise<{ + status: string; + details?: any; + }> { + try { + const status = this.mtaService.getEmailStatus(emailId); + + if (!status) { + return { + status: 'unknown', + details: { message: 'Email not found' } + }; + } + + return { + status: status.status, + details: { + attempts: status.attempts, + lastAttempt: status.lastAttempt, + nextAttempt: status.nextAttempt, + error: status.error?.message + } + }; + } catch (error) { + logger.log('error', `Failed to check email status: ${error.message}`, { + eventType: 'emailError', + provider: 'mta', + emailId, + error: error.message + }); + + return { + status: 'error', + details: { message: error.message } + }; + } + } +} \ No newline at end of file diff --git a/ts/email/email.classes.emailservice.ts b/ts/email/email.classes.emailservice.ts index 8bef71f..5005641 100644 --- a/ts/email/email.classes.emailservice.ts +++ b/ts/email/email.classes.emailservice.ts @@ -1,16 +1,22 @@ import * as plugins from '../plugins.js'; import * as paths from '../paths.js'; -import { MailgunConnector } from './email.classes.connector.mailgun.js'; +import { MtaConnector } from './email.classes.connector.mta.js'; import { RuleManager } from './email.classes.rulemanager.js'; import { ApiManager } from './email.classes.apimanager.js'; import { logger } from '../logger.js'; import type { SzPlatformService } from '../classes.platformservice.js'; +// Import MTA service +import { MtaService, type IMtaConfig } from '../mta/index.js'; export interface IEmailConstructorOptions { - mailgunApiKey: string; + useMta?: boolean; + mtaConfig?: IMtaConfig; } +/** + * Email service with support for both Mailgun and local MTA + */ export class EmailService { public platformServiceRef: SzPlatformService; @@ -18,36 +24,108 @@ export class EmailService { public typedrouter = new plugins.typedrequest.TypedRouter(); // connectors - public mailgunConnector: MailgunConnector; + public mtaConnector: MtaConnector; public qenv = new plugins.qenv.Qenv('./', '.nogit/'); - // server - public apiManager = new ApiManager(this); + // MTA service + public mtaService: MtaService; + + // services + public apiManager: ApiManager; public ruleManager: RuleManager; - constructor(platformServiceRefArg: SzPlatformService) { + // configuration + private config: IEmailConstructorOptions; + + constructor(platformServiceRefArg: SzPlatformService, options: IEmailConstructorOptions = {}) { this.platformServiceRef = platformServiceRefArg; this.platformServiceRef.typedrouter.addTypedRouter(this.typedrouter); - this.mailgunConnector = new MailgunConnector(this); + + // Set default options + this.config = { + useMta: options.useMta ?? true, + mtaConfig: options.mtaConfig || {} + }; + + if (this.config.useMta) { + // Initialize MTA service + this.mtaService = new MtaService(platformServiceRefArg, this.config.mtaConfig); + // Initialize MTA connector + this.mtaConnector = new MtaConnector(this); + } + + // Initialize API manager and rule manager + this.apiManager = new ApiManager(this); this.ruleManager = new RuleManager(this); - this.platformServiceRef.typedserver.server.addRoute( - '/mailgun-notify', - new plugins.typedserver.servertools.Handler('POST', async (req, res) => { - console.log('Got a mailgun email notification'); - res.status(200); - res.end(); - this.ruleManager.handleNotification(req.body); - }) - ); + + // Set up MTA SMTP server webhook if using MTA + if (this.config.useMta) { + // The MTA SMTP server will handle incoming emails directly + // through its SMTP protocol. No additional webhook needed. + } } + /** + * Start the email service + */ public async start() { + // Initialize rule manager await this.ruleManager.init(); + + // Start MTA service if enabled + if (this.config.useMta && this.mtaService) { + await this.mtaService.start(); + logger.log('success', 'Started MTA service'); + } + logger.log('success', `Started email service`); } + /** + * Stop the email service + */ public async stop() { + // Stop MTA service if it's running + if (this.config.useMta && this.mtaService) { + await this.mtaService.stop(); + logger.log('info', 'Stopped MTA service'); + } + + logger.log('info', 'Stopped email service'); } - -} + /** + * Send an email using the configured provider (Mailgun or MTA) + * @param email The email to send + * @param to Recipient(s) + * @param options Additional options + */ + public async sendEmail( + email: plugins.smartmail.Smartmail<>, + to: string | string[], + options: any = {} + ): Promise { + // Determine which connector to use + if (this.config.useMta && this.mtaConnector) { + return this.mtaConnector.sendEmail(email, to, options); + } else { + throw new Error('No email provider configured'); + } + } + + /** + * Get email service statistics + */ + public getStats() { + const stats: any = { + activeProviders: [] + }; + + if (this.config.useMta) { + stats.activeProviders.push('mta'); + stats.mta = this.mtaService.getStats(); + } + + return stats; + } +} \ No newline at end of file diff --git a/ts/email/email.classes.rulemanager.ts b/ts/email/email.classes.rulemanager.ts index 5318eb6..2bbcfb5 100644 --- a/ts/email/email.classes.rulemanager.ts +++ b/ts/email/email.classes.rulemanager.ts @@ -1,51 +1,101 @@ -import * as plugins from './email.plugins.js'; +import * as plugins from '../plugins.js'; import { EmailService } from './email.classes.emailservice.js'; -import { logger } from './email.logging.js'; +import { logger } from '../logger.js'; export class RuleManager { public emailRef: EmailService; public smartruleInstance = new plugins.smartrule.SmartRule< - plugins.smartmail.Smartmail + plugins.smartmail.Smartmail >(); constructor(emailRefArg: EmailService) { this.emailRef = emailRefArg; + + // Register MTA handler for incoming emails if MTA is enabled + if (this.emailRef.mtaService) { + this.setupMtaIncomingHandler(); + } } - public async handleNotification(notification: plugins.mailgun.IMailgunNotification) { - console.log(notification['message-url']); + /** + * Set up handler for incoming emails via MTA's SMTP server + */ + private setupMtaIncomingHandler() { + // The original MtaService doesn't have a direct callback for incoming emails, + // but we can modify this approach based on how you prefer to integrate. + // One option would be to extend the MtaService to add an event emitter. + + // For now, we'll use a directory watcher as an example + // This would watch the directory where MTA saves incoming emails + const incomingDir = this.emailRef.mtaService['receivedEmailsDir'] || './received'; + + // Simple file watcher (in real implementation, use proper file watching) + // This is just conceptual - would need modification to work with your specific setup + this.watchIncomingEmails(incomingDir); + } - // basic checks here - // none for now - - const fetchedSmartmail = await this.emailRef.mailgunConnector.receiveEmail( - notification['message-url'] - ); - console.log('======================='); - console.log('Received a mail:'); - console.log(`From: ${fetchedSmartmail.options.creationObjectRef.From}`); - console.log(`To: ${fetchedSmartmail.options.creationObjectRef.To}`); - console.log(`Subject: ${fetchedSmartmail.options.creationObjectRef.Subject}`); - console.log('^^^^^^^^^^^^^^^^^^^^^^^'); - - logger.log( - 'info', - `email from ${fetchedSmartmail.options.creationObjectRef.From} to ${fetchedSmartmail.options.creationObjectRef.To} with subject '${fetchedSmartmail.options.creationObjectRef.Subject}'`, - { - eventType: 'receivedEmail', - email: { - from: fetchedSmartmail.options.creationObjectRef.From, - to: fetchedSmartmail.options.creationObjectRef.To, - subject: fetchedSmartmail.options.creationObjectRef.Subject, - }, + /** + * Watch directory for incoming emails (conceptual implementation) + */ + private watchIncomingEmails(directory: string) { + console.log(`Watching for incoming emails in: ${directory}`); + + // Conceptual - in a real implementation, set up proper file watching + // or modify the MTA to emit events when emails are received + + /* + // Example using a file watcher: + const watcher = plugins.fs.watch(directory, async (eventType, filename) => { + if (eventType === 'rename' && filename.endsWith('.eml')) { + const filePath = plugins.path.join(directory, filename); + await this.handleMtaIncomingEmail(filePath); } - ); + }); + */ + } - this.smartruleInstance.makeDecision(fetchedSmartmail); + /** + * Handle incoming email received via MTA + */ + public async handleMtaIncomingEmail(emailPath: string) { + try { + // Process the email file + const fetchedSmartmail = await this.emailRef.mtaConnector.receiveEmail(emailPath); + + console.log('======================='); + console.log('Received a mail via MTA:'); + console.log(`From: ${fetchedSmartmail.options.creationObjectRef.From}`); + console.log(`To: ${fetchedSmartmail.options.creationObjectRef.To}`); + console.log(`Subject: ${fetchedSmartmail.options.creationObjectRef.Subject}`); + console.log('^^^^^^^^^^^^^^^^^^^^^^^'); + + logger.log( + 'info', + `email from ${fetchedSmartmail.options.creationObjectRef.From} to ${fetchedSmartmail.options.creationObjectRef.To} with subject '${fetchedSmartmail.options.creationObjectRef.Subject}'`, + { + eventType: 'receivedEmail', + provider: 'mta', + email: { + from: fetchedSmartmail.options.creationObjectRef.From, + to: fetchedSmartmail.options.creationObjectRef.To, + subject: fetchedSmartmail.options.creationObjectRef.Subject, + }, + } + ); + + // Process with rules + this.smartruleInstance.makeDecision(fetchedSmartmail); + } catch (error) { + logger.log('error', `Failed to process incoming MTA email: ${error.message}`, { + eventType: 'emailError', + provider: 'mta', + error: error.message + }); + } } public async init() { - // lets forward stuff + // Setup email rules await this.createForwards(); } @@ -53,20 +103,7 @@ export class RuleManager { * creates the default forwards */ public async createForwards() { - const forwards: { originalToAddress: string[]; forwardedToAddress: string[] }[] = [ - { - originalToAddress: ['bot@mail.nevermind.group'], - forwardedToAddress: ['phil@metadata.company', 'dominik@metadata.company'], - }, - { - originalToAddress: ['legal@mail.lossless.com'], - forwardedToAddress: ['phil@lossless.com'], - }, - { - originalToAddress: ['christine.nyamwaro@mail.lossless.com', 'christine@nyamwaro.com'], - forwardedToAddress: ['phil@lossless.com'], - }, - ]; + const forwards: { originalToAddress: string[]; forwardedToAddress: string[] }[] = []; console.log(`${forwards.length} forward rules configured:`); for (const forward of forwards) { console.log(forward); @@ -87,7 +124,7 @@ export class RuleManager { return 'continue'; } }, - async (smartmailArg: plugins.smartmail.Smartmail) => { + async (smartmailArg: plugins.smartmail.Smartmail) => { forward.forwardedToAddress.map(async (toArg) => { const forwardedSmartMail = new plugins.smartmail.Smartmail({ body: @@ -112,13 +149,16 @@ export class RuleManager { for (const attachment of smartmailArg.attachments) { forwardedSmartMail.addAttachment(attachment); } - await this.emailRef.mailgunConnector.sendEmail(forwardedSmartMail, toArg); + + // Use the EmailService's sendEmail method to send with the appropriate provider + await this.emailRef.sendEmail(forwardedSmartMail, toArg); + console.log(`forwarded mail to ${toArg}`); logger.log( 'info', `email from ${ smartmailArg.options.creationObjectRef.From - } to phil@lossless.com with subject '${smartmailArg.getSubject()}'`, + } to ${toArg} with subject '${smartmailArg.getSubject()}'`, { eventType: 'forwardedEmail', email: { @@ -134,4 +174,4 @@ export class RuleManager { ); } } -} +} \ No newline at end of file diff --git a/ts/mta/index.ts b/ts/mta/index.ts index d4bf289..807c547 100644 --- a/ts/mta/index.ts +++ b/ts/mta/index.ts @@ -4,5 +4,4 @@ export * from './mta.classes.dkimverifier.js'; export * from './mta.classes.mta.js'; export * from './mta.classes.smtpserver.js'; export * from './mta.classes.emailsendjob.js'; -export * from './mta.classes.mta.js'; export * from './mta.classes.email.js'; diff --git a/ts/plugins.ts b/ts/plugins.ts index da91032..f4b5090 100644 --- a/ts/plugins.ts +++ b/ts/plugins.ts @@ -47,9 +47,10 @@ import * as smartmail from '@push.rocks/smartmail'; import * as smartpath from '@push.rocks/smartpath'; import * as smartpromise from '@push.rocks/smartpromise'; import * as smartrequest from '@push.rocks/smartrequest'; +import * as smartrule from '@push.rocks/smartrule'; import * as smartrx from '@push.rocks/smartrx'; -export { projectinfo, qenv, smartdata, smartfile, smartlog, smartmail, smartpath, smartpromise, smartrequest, smartrx }; +export { projectinfo, qenv, smartdata, smartfile, smartlog, smartmail, smartpath, smartpromise, smartrequest, smartrule, smartrx }; // apiclient.xyz scope import * as letterxpress from '@apiclient.xyz/letterxpress'; diff --git a/tsconfig.json b/tsconfig.json index dfe5a55..d76da25 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "experimentalDecorators": true, + "emitDecoratorMetadata": true, "useDefineForClassFields": false, "target": "ES2022", "module": "NodeNext",