diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 44184dd886..4f1915defb 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -762,7 +762,7 @@ importers: version: file:projects/pod-gmail.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(encoding@0.1.13)(gcp-metadata@5.3.0(encoding@0.1.13))(snappy@7.2.2)(socks@2.8.3)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3)) '@rush-temp/pod-inbound-mail': specifier: file:./projects/pod-inbound-mail.tgz - version: file:projects/pod-inbound-mail.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9)) + version: file:projects/pod-inbound-mail.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(bufferutil@4.0.8)(utf-8-validate@6.0.4) '@rush-temp/pod-love': specifier: file:./projects/pod-love.tgz version: file:projects/pod-love.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(bufferutil@4.0.8)(utf-8-validate@6.0.4) @@ -1576,6 +1576,9 @@ importers: '@types/toposort': specifier: ^2.0.3 version: 2.0.7 + '@types/turndown': + specifier: ^5.0.5 + version: 5.0.5 '@types/uuid': specifier: ^8.3.1 version: 8.3.4 @@ -1840,6 +1843,9 @@ importers: intl-messageformat: specifier: ^10.7.14 version: 10.7.14 + isomorphic-dompurify: + specifier: ^2.22.0 + version: 2.22.0(bufferutil@4.0.8)(utf-8-validate@6.0.4) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3)) @@ -2101,6 +2107,9 @@ importers: ts-standard: specifier: ^12.0.2 version: 12.0.2(typescript@5.3.3) + turndown: + specifier: ^7.2.0 + version: 7.2.0 tus-js-client: specifier: ^4.3.1 version: 4.3.1 @@ -2184,6 +2193,9 @@ packages: '@anticrm/skillset@0.6.0': resolution: {integrity: sha512-k5/57q9ie73oCFKAxZrKy/BSnakiaVZsJmfjzmqhoko+3dZq2M2o/h1r+RSZrQ6re51iMqFdsVZAtfdPL15hjA==} + '@asamuzakjp/css-color@3.1.1': + resolution: {integrity: sha512-hpRD68SV2OMcZCsrbdkccTw5FXjNDLo5OuqSHyHZfwweGsDWZwDJ2+gONyNAbazZclobMirACLw0lk8WVxIqxA==} + '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -2556,6 +2568,34 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@csstools/color-helpers@5.0.2': + resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.2': + resolution: {integrity: sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-color-parser@3.0.8': + resolution: {integrity: sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + engines: {node: '>=18'} + '@dabh/diagnostics@2.0.3': resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} @@ -3356,6 +3396,9 @@ packages: '@mermaid-js/parser@0.3.0': resolution: {integrity: sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==} + '@mixmark-io/domino@2.2.0': + resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} + '@mongodb-js/saslprep@1.1.9': resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==} @@ -4039,7 +4082,7 @@ packages: version: 0.0.0 '@rush-temp/contact@file:projects/contact.tgz': - resolution: {integrity: sha512-79dZ9DTczWnB5eBmX1XH8d/mw8RjbGeCK+ryNpym3Bh3kykq7RwuTeTRvIlPTzk+vkCCXzb34A/zSdaslTyfnw==, tarball: file:projects/contact.tgz} + resolution: {integrity: sha512-mbni7SPWQvWylU2uoM88r+eOGhktRpU70B26/2smq4B8TMd3GnHKDnjzKmVE7Ck6kh43z/9B9f2t203UAR0Tpg==, tarball: file:projects/contact.tgz} version: 0.0.0 '@rush-temp/controlled-documents-assets@file:projects/controlled-documents-assets.tgz': @@ -4351,7 +4394,7 @@ packages: version: 0.0.0 '@rush-temp/model-contact@file:projects/model-contact.tgz': - resolution: {integrity: sha512-xIr8DSFUT2GSZ/4VvDc1zrlab+WhjCCM2tN/TNBEGroVUL2QW0/3VqODWs0UorvniaVWVoQNAj13cPZpE7yEfw==, tarball: file:projects/model-contact.tgz} + resolution: {integrity: sha512-R5QhJlkccqefuHjHaIg/POYsrtnWCYDPE6MwNTQ+1F/CxGYuCJ2kDfEZMlKZiLitiruAkKbvq8xBo/nl2/PNfw==, tarball: file:projects/model-contact.tgz} version: 0.0.0 '@rush-temp/model-controlled-documents@file:projects/model-controlled-documents.tgz': @@ -4759,7 +4802,7 @@ packages: version: 0.0.0 '@rush-temp/pod-inbound-mail@file:projects/pod-inbound-mail.tgz': - resolution: {integrity: sha512-aRbv7vSb4jFYBQc+EroIkXWtJK21XAmGCYeMdCuhyKU862V5Ik3Dag68jW7pNLRupf4dw9gzJ2/0LlSr89FvgQ==, tarball: file:projects/pod-inbound-mail.tgz} + resolution: {integrity: sha512-AUKPM2FBTj0I2baAcnQVRI8vDJRdKEfPGwGLsxJSfvMyx2KhtZQi2if6h7mwS3lN+hFBHEqjRsj4VG9Ag8x7tg==, tarball: file:projects/pod-inbound-mail.tgz} version: 0.0.0 '@rush-temp/pod-love@file:projects/pod-love.tgz': @@ -4847,7 +4890,7 @@ packages: version: 0.0.0 '@rush-temp/process-resources@file:projects/process-resources.tgz': - resolution: {integrity: sha512-ZUhW+nxxAlPdXaL99Tdd5iKav4paqRvB1t4zOs5Pd4ez2K9EVIe0/OFbZAn1ic0MClrp5TxmLSPbu//YrFT3VQ==, tarball: file:projects/process-resources.tgz} + resolution: {integrity: sha512-A97floT9oEaheLWcX3NSIqoX0b8ZpGmGJpvsGxZ4aMLP3tbVyVD9x8wvWywv8O5LlLHe6qVMI9U75OB4EOC2+g==, tarball: file:projects/process-resources.tgz} version: 0.0.0 '@rush-temp/process@file:projects/process.tgz': @@ -5027,7 +5070,7 @@ packages: version: 0.0.0 '@rush-temp/server-contact-resources@file:projects/server-contact-resources.tgz': - resolution: {integrity: sha512-6R2S1cbwLtDKCesTPG9tJVy7mJB6EJogkf8TDAv9RFPRqxBkzdQh6OlgkLQsRMgpuneoJjwmSxXFHhX3btVnug==, tarball: file:projects/server-contact-resources.tgz} + resolution: {integrity: sha512-NrPPkOfgwC2q7Dw5MKB+lQwUAWpsI/F/C0n8KHOlPT6ZASxXkR0TPNxtul1oQl2EnxCN+Z6NmUAi+2rbHOOtXQ==, tarball: file:projects/server-contact-resources.tgz} version: 0.0.0 '@rush-temp/server-contact@file:projects/server-contact.tgz': @@ -6526,6 +6569,9 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/turndown@5.0.5': + resolution: {integrity: sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==} + '@types/unist@2.0.10': resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} @@ -6830,6 +6876,10 @@ packages: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + agentkeepalive@4.5.0: resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} engines: {node: '>= 8.0.0'} @@ -7326,6 +7376,10 @@ packages: resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} @@ -7771,6 +7825,10 @@ packages: resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} engines: {node: '>=8'} + cssstyle@4.3.0: + resolution: {integrity: sha512-6r0NiY0xizYqfBvWp1G7WXJ06/bZyrk7Dc6PHql82C/pKGUTKu4yAX4Y8JPamb1ob9nBKuxWzCGTRuGwU3yxJQ==} + engines: {node: '>=18'} + csv-parse@5.1.0: resolution: {integrity: sha512-JL+Q6YEikT2uoe57InjFFa6VejhSv0tDwOxeQ1bVQKeUC/NCnLAAZ8n3PzowPQQLuZ37fysDYZipB2UJkH9C6A==} @@ -7956,6 +8014,10 @@ packages: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -8249,6 +8311,9 @@ packages: dompurify@3.2.3: resolution: {integrity: sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==} + dompurify@3.2.4: + resolution: {integrity: sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==} + domutils@1.5.1: resolution: {integrity: sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==} @@ -8296,6 +8361,10 @@ packages: duck@0.1.12: resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -8469,6 +8538,10 @@ packages: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -8484,10 +8557,18 @@ packages: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.3: resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} @@ -9039,6 +9120,10 @@ packages: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + formdata-node@4.4.1: resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} engines: {node: '>= 12.20'} @@ -9144,10 +9229,18 @@ packages: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stdin@8.0.0: resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} engines: {node: '>=10'} @@ -9255,6 +9348,10 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + got@11.8.6: resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} engines: {node: '>=10.19.0'} @@ -9330,6 +9427,10 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} @@ -9382,6 +9483,10 @@ packages: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} engines: {node: '>=12'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + html-entities@2.4.0: resolution: {integrity: sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==} @@ -9487,6 +9592,10 @@ packages: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -9845,6 +9954,10 @@ packages: resolution: {integrity: sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==} engines: {node: '>=0.10.0'} + isomorphic-dompurify@2.22.0: + resolution: {integrity: sha512-A2xsDNST1yB94rErEnwqlzSvGllCJ4e8lDMe1OWBH2hvpfc/2qzgMEiDshTO1HwO+PIDTiYeOc7ZDB7Ds49BOg==} + engines: {node: '>=18'} + isomorphic.js@0.2.5: resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} @@ -10076,6 +10189,15 @@ packages: canvas: optional: true + jsdom@26.0.0: + resolution: {integrity: sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -10520,6 +10642,9 @@ packages: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -10578,6 +10703,10 @@ packages: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + md5@2.3.0: resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} @@ -11007,6 +11136,9 @@ packages: nwsapi@2.2.13: resolution: {integrity: sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==} + nwsapi@2.2.20: + resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} + oauth@0.10.0: resolution: {integrity: sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==} @@ -11259,6 +11391,9 @@ packages: parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + parseley@0.12.1: resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} @@ -12011,6 +12146,9 @@ packages: roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -12739,6 +12877,13 @@ packages: tippy.js@6.3.7: resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + tldts-core@6.1.85: + resolution: {integrity: sha512-DTjUVvxckL1fIoPSb3KE7ISNtkWSawZdpfxGxwiIrZoO6EbHVDXXUIlIuWympPaeS+BLGyggozX/HTMsRAdsoA==} + + tldts@6.1.85: + resolution: {integrity: sha512-gBdZ1RjCSevRPFix/hpaUWeak2/RNUZB4/8frF1r5uYMHjFptkiT0JXIebWvgI/0ZHXvxaUDDJshiA0j6GdL3w==} + hasBin: true + tmp-promise@3.0.3: resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} @@ -12780,6 +12925,10 @@ packages: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -12791,6 +12940,10 @@ packages: resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} engines: {node: '>=14'} + tr46@5.1.0: + resolution: {integrity: sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==} + engines: {node: '>=18'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -12918,6 +13071,9 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + turndown@7.2.0: + resolution: {integrity: sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==} + tus-js-client@4.3.1: resolution: {integrity: sha512-ZLeYmjrkaU1fUsKbIi8JML52uAocjEZtBx4DKjRrqzrZa0O4MYwT6db+oqePlspV+FxXJAyFBc/L5gwUi2OFsg==} engines: {node: '>=18'} @@ -13185,6 +13341,10 @@ packages: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} engines: {node: '>=14'} + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -13299,10 +13459,18 @@ packages: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} engines: {node: '>=12'} + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + whatwg-url@11.0.0: resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} engines: {node: '>=12'} @@ -13311,6 +13479,10 @@ packages: resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==} engines: {node: '>=16'} + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -13424,6 +13596,10 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xml-parse-from-string@1.0.1: resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} @@ -13591,6 +13767,14 @@ snapshots: '@anticrm/skillset@0.6.0': {} + '@asamuzakjp/css-color@3.1.1': + dependencies: + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + lru-cache: 10.4.3 + '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -14353,6 +14537,26 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@csstools/color-helpers@5.0.2': {} + + '@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-color-parser@3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/color-helpers': 5.0.2 + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-tokenizer@3.0.3': {} + '@dabh/diagnostics@2.0.3': dependencies: colorspace: 1.1.4 @@ -15381,6 +15585,8 @@ snapshots: dependencies: langium: 3.0.0 + '@mixmark-io/domino@2.2.0': {} + '@mongodb-js/saslprep@1.1.9': dependencies: sparse-bitfield: 3.0.3 @@ -21763,13 +21969,17 @@ snapshots: - supports-color - ts-node - '@rush-temp/pod-inbound-mail@file:projects/pod-inbound-mail.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))': + '@rush-temp/pod-inbound-mail@file:projects/pod-inbound-mail.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(bufferutil@4.0.8)(utf-8-validate@6.0.4)': dependencies: + '@hcengineering/communication-rest-client': 0.1.155(typescript@5.7.3) + '@hcengineering/communication-sdk-types': 0.1.155(typescript@5.7.3) + '@hcengineering/communication-types': 0.1.155(typescript@5.7.3) '@tsconfig/node16': 1.0.4 '@types/cors': 2.8.17 '@types/express': 4.17.21 '@types/jest': 29.5.12 '@types/node': 20.11.19 + '@types/turndown': 5.0.5 '@types/uuid': 8.3.4 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.7.3) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.7.3) @@ -21785,11 +21995,13 @@ snapshots: eslint-plugin-node: 11.1.0(eslint@8.56.0) eslint-plugin-promise: 6.1.1(eslint@8.56.0) express: 4.21.2 + isomorphic-dompurify: 2.22.0(bufferutil@4.0.8)(utf-8-validate@6.0.4) jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3)) jwt-simple: 0.5.6 prettier: 3.2.5 ts-jest: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.11.19)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3)))(typescript@5.7.3) ts-node: 10.9.2(@types/node@20.11.19)(typescript@5.7.3) + turndown: 7.2.0 typescript: 5.7.3 uuid: 8.3.2 transitivePeerDependencies: @@ -21799,8 +22011,11 @@ snapshots: - '@swc/wasm' - babel-jest - babel-plugin-macros + - bufferutil + - canvas - node-notifier - supports-color + - utf-8-validate '@rush-temp/pod-love@file:projects/pod-love.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(bufferutil@4.0.8)(utf-8-validate@6.0.4)': dependencies: @@ -28571,6 +28786,8 @@ snapshots: '@types/trusted-types@2.0.7': {} + '@types/turndown@5.0.5': {} + '@types/unist@2.0.10': {} '@types/uuid@8.3.4': {} @@ -29101,6 +29318,8 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.3: {} + agentkeepalive@4.5.0: dependencies: humanize-ms: 1.2.1 @@ -29740,6 +29959,11 @@ snapshots: normalize-url: 6.1.0 responselike: 2.0.1 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 @@ -30245,6 +30469,11 @@ snapshots: dependencies: cssom: 0.3.8 + cssstyle@4.3.0: + dependencies: + '@asamuzakjp/css-color': 3.1.1 + rrweb-cssom: 0.8.0 + csv-parse@5.1.0: {} csv-stringify@6.5.2: {} @@ -30456,6 +30685,11 @@ snapshots: whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + data-view-buffer@1.0.1: dependencies: call-bind: 1.0.7 @@ -30733,6 +30967,10 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 + dompurify@3.2.4: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@1.5.1: dependencies: dom-serializer: 0.1.1 @@ -30787,6 +31025,12 @@ snapshots: dependencies: underscore: 1.13.7 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + duplexer@0.1.2: {} dynamic-dedupe@0.3.0: @@ -31059,6 +31303,8 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + es-define-property@1.0.1: {} + es-errors@1.3.0: {} es-iterator-helpers@1.0.19: @@ -31084,12 +31330,23 @@ snapshots: dependencies: es-errors: 1.3.0 + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.0.3: dependencies: get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 hasown: 2.0.1 + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + es-shim-unscopables@1.0.2: dependencies: hasown: 2.0.1 @@ -31839,6 +32096,13 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + formdata-node@4.4.1: dependencies: node-domexception: 1.0.0 @@ -31965,8 +32229,26 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.1 + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-package-type@0.1.0: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-stdin@8.0.0: {} get-stream@4.1.0: @@ -32128,6 +32410,8 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + gopd@1.2.0: {} + got@11.8.6: dependencies: '@sindresorhus/is': 4.6.0 @@ -32209,6 +32493,8 @@ snapshots: has-symbols@1.0.3: {} + has-symbols@1.1.0: {} + has-tostringtag@1.0.2: dependencies: has-symbols: 1.0.3 @@ -32256,6 +32542,10 @@ snapshots: dependencies: whatwg-encoding: 2.0.0 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + html-entities@2.4.0: {} html-escaper@2.0.2: {} @@ -32416,6 +32706,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} humanize-ms@1.2.1: @@ -32714,6 +33011,16 @@ snapshots: isobject@4.0.0: {} + isomorphic-dompurify@2.22.0(bufferutil@4.0.8)(utf-8-validate@6.0.4): + dependencies: + dompurify: 3.2.4 + jsdom: 26.0.0(bufferutil@4.0.8)(utf-8-validate@6.0.4) + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + isomorphic.js@0.2.5: {} isstream@0.1.2: {} @@ -33182,6 +33489,34 @@ snapshots: - supports-color - utf-8-validate + jsdom@26.0.0(bufferutil@4.0.8)(utf-8-validate@6.0.4): + dependencies: + cssstyle: 4.3.0 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.2 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.20 + parse5: 7.2.1 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@2.5.2: {} json-bigint@1.0.0: @@ -33686,6 +34021,8 @@ snapshots: lru-cache@10.2.0: {} + lru-cache@10.4.3: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -33769,6 +34106,8 @@ snapshots: escape-string-regexp: 4.0.0 optional: true + math-intrinsics@1.1.0: {} + md5@2.3.0: dependencies: charenc: 0.0.2 @@ -34175,6 +34514,8 @@ snapshots: nwsapi@2.2.13: {} + nwsapi@2.2.20: {} + oauth@0.10.0: {} object-assign@4.1.1: {} @@ -34460,6 +34801,10 @@ snapshots: dependencies: entities: 4.5.0 + parse5@7.2.1: + dependencies: + entities: 4.5.0 + parseley@0.12.1: dependencies: leac: 0.6.0 @@ -35257,6 +35602,8 @@ snapshots: points-on-curve: 0.2.0 points-on-path: 0.2.1 + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -36171,6 +36518,12 @@ snapshots: dependencies: '@popperjs/core': 2.11.8 + tldts-core@6.1.85: {} + + tldts@6.1.85: + dependencies: + tldts-core: 6.1.85 + tmp-promise@3.0.3: dependencies: tmp: 0.2.3 @@ -36205,6 +36558,10 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.85 + tr46@0.0.3: {} tr46@3.0.0: @@ -36215,6 +36572,10 @@ snapshots: dependencies: punycode: 2.3.1 + tr46@5.1.0: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} triple-beam@1.4.1: {} @@ -36443,6 +36804,10 @@ snapshots: dependencies: safe-buffer: 5.2.1 + turndown@7.2.0: + dependencies: + '@mixmark-io/domino': 2.2.0 + tus-js-client@4.3.1: dependencies: buffer-from: 1.1.2 @@ -36698,6 +37063,10 @@ snapshots: dependencies: xml-name-validator: 4.0.0 + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -36895,8 +37264,14 @@ snapshots: dependencies: iconv-lite: 0.6.3 + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + whatwg-mimetype@3.0.0: {} + whatwg-mimetype@4.0.0: {} + whatwg-url@11.0.0: dependencies: tr46: 3.0.0 @@ -36907,6 +37282,11 @@ snapshots: tr46: 4.1.1 webidl-conversions: 7.0.0 + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.0 + webidl-conversions: 7.0.0 + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -37058,6 +37438,8 @@ snapshots: xml-name-validator@4.0.0: {} + xml-name-validator@5.0.0: {} + xml-parse-from-string@1.0.1: {} xml2js@0.5.0: diff --git a/services/mail/pod-inbound-mail/package.json b/services/mail/pod-inbound-mail/package.json index 41f97095ed..b1fde8781c 100644 --- a/services/mail/pod-inbound-mail/package.json +++ b/services/mail/pod-inbound-mail/package.json @@ -30,44 +30,50 @@ "_phase:validate": "compile validate" }, "devDependencies": { - "cross-env": "~7.0.3", "@hcengineering/platform-rig": "^0.6.0", - "@types/node": "~20.11.16", - "@typescript-eslint/eslint-plugin": "^6.11.0", - "@typescript-eslint/parser": "^6.11.0", - "eslint-config-standard-with-typescript": "^40.0.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-n": "^15.4.0", - "eslint": "^8.54.0", - "esbuild": "^0.24.2", - "prettier": "^3.1.0", - "ts-node": "^10.8.0", - "typescript": "^5.3.3", - "jest": "^29.7.0", - "ts-jest": "^29.1.1", - "@types/jest": "^29.5.5", "@tsconfig/node16": "^1.0.4", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", - "eslint-plugin-node": "^11.1.0" + "@types/jest": "^29.5.5", + "@types/node": "~20.11.16", + "@types/turndown": "^5.0.5", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "cross-env": "~7.0.3", + "esbuild": "^0.24.2", + "eslint": "^8.54.0", + "eslint-config-standard-with-typescript": "^40.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-n": "^15.4.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "jest": "^29.7.0", + "prettier": "^3.1.0", + "ts-jest": "^29.1.1", + "ts-node": "^10.8.0", + "typescript": "^5.3.3" }, "dependencies": { "@hcengineering/account-client": "^0.6.0", + "@hcengineering/analytics-service": "^0.6.0", "@hcengineering/api-client": "^0.6.0", - "@hcengineering/attachment": "^0.6.14", "@hcengineering/card": "^0.6.0", - "@hcengineering/chunter": "^0.6.20", + "@hcengineering/communication-rest-client": "0.1.155", + "@hcengineering/communication-sdk-types": "0.1.155", + "@hcengineering/communication-types": "0.1.155", "@hcengineering/contact": "^0.6.24", "@hcengineering/core": "^0.6.32", "@hcengineering/mail": "^0.6.0", - "@hcengineering/text-html": "^0.6.0", + "@hcengineering/server-core": "^0.6.1", + "@hcengineering/server-storage": "^0.6.0", "@types/uuid": "^8.3.1", - "eml-parse-js": "^1.2.0-beta.0", - "uuid": "^8.3.2", "cors": "^2.8.5", - "express": "^4.21.2", "dotenv": "~16.0.0", - "jwt-simple": "^0.5.6" + "eml-parse-js": "^1.2.0-beta.0", + "express": "^4.21.2", + "isomorphic-dompurify": "^2.22.0", + "jwt-simple": "^0.5.6", + "turndown": "^7.2.0", + "uuid": "^8.3.2" } } diff --git a/services/mail/pod-inbound-mail/src/config.ts b/services/mail/pod-inbound-mail/src/config.ts index 2e0f6357ea..a6263d8d1b 100644 --- a/services/mail/pod-inbound-mail/src/config.ts +++ b/services/mail/pod-inbound-mail/src/config.ts @@ -23,6 +23,8 @@ interface Config { workspaceUrl: string ignoredAddresses: string[] hookToken?: string + mailSizeLimit?: string + storageConfig?: string } const config: Config = { @@ -36,7 +38,9 @@ const config: Config = { throw Error('WORKSPACE_URL env var is not set') })(), ignoredAddresses: process.env.IGNORED_ADDRESSES?.split(',') ?? [], - hookToken: process.env.HOOK_TOKEN + hookToken: process.env.HOOK_TOKEN, + mailSizeLimit: process.env.MAIL_SIZE_LIMIT ?? '50mb', + storageConfig: process.env.STORAGE_CONFIG } export default config diff --git a/services/mail/pod-inbound-mail/src/handlerMta.ts b/services/mail/pod-inbound-mail/src/handlerMta.ts index 85f459e2a2..30a5e016c2 100644 --- a/services/mail/pod-inbound-mail/src/handlerMta.ts +++ b/services/mail/pod-inbound-mail/src/handlerMta.ts @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. // -import { createHash } from 'crypto' +import { createHash, randomUUID } from 'crypto' import { readEml, ReadedEmlJson } from 'eml-parse-js' import { Request, Response } from 'express' -import { htmlToMarkup } from '@hcengineering/text-html' -import { createMessages } from './message' +import TurndownService from 'turndown' +import DOMPurify from 'isomorphic-dompurify' +import { MeasureContext } from '@hcengineering/core' +import { type Attachment, createMessages } from './message' import config from './config' interface MtaMessage { @@ -34,7 +36,7 @@ interface MtaMessage { } } -export async function handleMtaHook (req: Request, res: Response): Promise { +export async function handleMtaHook (req: Request, res: Response, ctx: MeasureContext): Promise { try { if (config.hookToken !== undefined) { const token = req.headers['x-hook-token'] @@ -47,12 +49,11 @@ export async function handleMtaHook (req: Request, res: Response): Promise const from = { address: mta.envelope.from.address, name: '' } if (config.ignoredAddresses.includes(from.address)) { - console.log(`Ignoring message from ${from.address}`) return } const fromHeader = mta.message.headers.find((header) => header[0] === 'From')?.[1] if (fromHeader !== undefined) { - from.name = extractContactName(fromHeader) + from.name = extractContactName(ctx, fromHeader) } const tos = mta.envelope.to.map((to) => ({ address: to.address, name: '' })) @@ -61,7 +62,7 @@ export async function handleMtaHook (req: Request, res: Response): Promise for (const part of toHeader.split(',')) { for (const to of tos) { if (part.includes(to.address)) { - to.name = extractContactName(part) + to.name = extractContactName(ctx, part) } } } @@ -69,7 +70,7 @@ export async function handleMtaHook (req: Request, res: Response): Promise const subject = (mta.message.headers.find((header) => header[0] === 'Subject')?.[1] ?? '').trim() const inReplyTo = mta.message.headers.find((header) => header[0] === 'In-Reply-To')?.[1]?.trim() - const content = await getContent(mta) + const { content, attachments } = await parseContent(ctx, mta) let mailId = mta.message.headers.find((header) => header[0] === 'Message-ID')?.[1].trim() if (mailId === undefined) { @@ -85,15 +86,19 @@ export async function handleMtaHook (req: Request, res: Response): Promise .digest('hex') } - await createMessages(mailId, from, tos, subject, content, inReplyTo) - } catch (err) { - console.error('mta-hook', err) + await createMessages(ctx, mailId, from, tos, subject, content, attachments, inReplyTo) + } catch (error) { + ctx.error('mta-hook', { error }) } finally { + // Any error in the mta-hook should not prevent the mail server from handling emails res.status(200).send({ action: 'accept' }) } } -async function getContent (mta: MtaMessage): Promise { +async function parseContent ( + ctx: MeasureContext, + mta: MtaMessage +): Promise<{ content: string, attachments: Attachment[] }> { const contentType = mta.message.headers.find((header) => header[0] === 'Content-Type')?.[1] if (contentType === undefined) { throw new Error('Content-Type header not found') @@ -110,30 +115,63 @@ async function getContent (mta: MtaMessage): Promise { } }) }) + + let content = email.text ?? '' + let isMarkdown = false if (email.html !== undefined) { try { - // Some mailers (e.g. Google) use divs instead of paragraphs - const html = email.html.replaceAll('', '

') - const markup = htmlToMarkup(html) - return JSON.stringify(markup) - } catch (err) { - console.warn('Failed to parse html content', err) + const html = DOMPurify.sanitize(email.html) + const tds = new TurndownService() + content = tds.turndown(html) + isMarkdown = true + console.log('Markdown:\n', content) + } catch (error) { + ctx.warn('Failed to parse html content', { error }) } } - return email.text ?? '' + + const attachments: Attachment[] = [] + if (config.storageConfig !== undefined) { + for (const a of email.attachments ?? []) { + console.log('Attachment', a) + if (a.name === undefined || a.name.length === 0) { + // EML parser returns attachments with empty name for parts of content + // that do not have "Content-Disposition: attachment" e.g. for part + // Content-Type: text/calendar; charset="UTF-8"; method=REQUEST + continue + } + const attachment: Attachment = { + id: randomUUID(), + name: a.name, + data: Buffer.from(a.data64, 'base64'), + contentType: a.contentType.split(';')[0].trim() + } + attachments.push(attachment) + + // For inline images, replace the CID references with the blob id + if (isMarkdown && a.inline && a.id !== undefined) { + const cid = a.id.replace(/[<>]/g, '') + content = content.replaceAll( + new RegExp(`!\\[.*?\\]\\(cid:${cid}\\)`, 'g'), + `![${a.name}](cid:${attachment.id})` + ) + } + } + } + return { content, attachments } } -function extractContactName (fromHeader: string): string { +function extractContactName (ctx: MeasureContext, fromHeader: string): string { // Match name part that appears before an email in angle brackets const nameMatch = fromHeader.match(/^\s*"?([^"<]+?)"?\s*<.+?>/) const name = nameMatch?.[1].trim() ?? '' if (name.length > 0) { - return decodeMimeWord(name) + return decodeMimeWord(ctx, name) } return '' } -function decodeMimeWord (text: string): string { +function decodeMimeWord (ctx: MeasureContext, text: string): string { return text.replace(/=\?([^?]+)\?([BQ])\?([^?]+)\?=/gi, (match, charset, encoding, content) => { try { if (encoding.toUpperCase() === 'B') { @@ -149,7 +187,7 @@ function decodeMimeWord (text: string): string { } return match } catch (error) { - console.warn('Failed to decode encoded word:', match, error) + ctx.warn('Failed to decode encoded word', { error }) return match } }) diff --git a/services/mail/pod-inbound-mail/src/index.ts b/services/mail/pod-inbound-mail/src/index.ts index 638fe6c593..ecd1551555 100644 --- a/services/mail/pod-inbound-mail/src/index.ts +++ b/services/mail/pod-inbound-mail/src/index.ts @@ -15,27 +15,49 @@ import cors from 'cors' import express, { NextFunction, Request, Response } from 'express' +import { join } from 'path' +import { SplitLogger } from '@hcengineering/analytics-service' +import { MeasureContext, MeasureMetricsContext, newMetrics } from '@hcengineering/core' +import { initStatisticsContext } from '@hcengineering/server-core' import { handleMtaHook } from './handlerMta' import config from './config' -type RequestHandler = (req: Request, res: Response, next?: NextFunction) => Promise - -const catchError = (fn: RequestHandler) => (req: Request, res: Response, next: NextFunction) => { - void (async () => { - try { - await fn(req, res, next) - } catch (err: unknown) { - console.error(req.method, req.path, err) - next(err) - } - })() -} +type RequestHandler = (req: Request, res: Response, ctx: MeasureContext, next?: NextFunction) => Promise async function main (): Promise { + const ctx = initStatisticsContext('inbound-mail', { + factory: () => + new MeasureMetricsContext( + 'inbound-mail', + {}, + {}, + newMetrics(), + new SplitLogger('inbound-mail', { + root: join(process.cwd(), 'logs'), + enableConsole: (process.env.ENABLE_CONSOLE ?? 'true') === 'true' + }) + ) + }) + const app = express() app.use(cors()) - app.use(express.json()) + app.use( + express.json({ + limit: config.mailSizeLimit + }) + ) + + const catchError = (fn: RequestHandler) => (req: Request, res: Response, next: NextFunction) => { + void (async () => { + try { + await fn(req, res, ctx) + } catch (error: any) { + ctx.error('Failed to handle request', { method: req.method, path: req.path, error }) + next(error) + } + })() + } app.post('/mta-hook', catchError(handleMtaHook)) @@ -43,13 +65,19 @@ async function main (): Promise { res.status(404).send({ message: 'Not found' }) }) - app.use((err: any, _req: any, res: any, _next: any) => { + app.use((err: any, req: Request, res: any, _next: any) => { + ctx.error(err) + if (req.path === '/mta-hook') { + // Any error in the mta-hook should not prevent the mail server from handling emails + // At least the PayloadTooLargeError falls here before reacing our code + res.status(200).send({ action: 'accept' }) + return + } res.status(500).send({ message: err.message }) }) const server = app.listen(config.port, () => { - console.log(`server started on port ${config.port}`) - console.log({ ...config, secret: '(stripped)', hookToken: '(stripped)' }) + ctx.info('server started', { ...config, secret: '(stripped)', hookToken: '(stripped)' }) }) const shutdown = (): void => { @@ -60,11 +88,11 @@ async function main (): Promise { process.on('SIGINT', shutdown) process.on('SIGTERM', shutdown) - process.on('uncaughtException', (e) => { - console.error(e) + process.on('uncaughtException', (error: any) => { + ctx.error('Uncaught exception', { error }) }) - process.on('unhandledRejection', (e) => { - console.error(e) + process.on('unhandledRejection', (error: any) => { + ctx.error('Unhandled rejection', { error }) }) } diff --git a/services/mail/pod-inbound-mail/src/message.ts b/services/mail/pod-inbound-mail/src/message.ts index 0a4e205d96..bf003ecc16 100644 --- a/services/mail/pod-inbound-mail/src/message.ts +++ b/services/mail/pod-inbound-mail/src/message.ts @@ -13,7 +13,19 @@ // limitations under the License. // import { encode } from 'jwt-simple' +import { getClient as getAccountClient } from '@hcengineering/account-client' +import { createRestTxOperations } from '@hcengineering/api-client' +import { type Card } from '@hcengineering/card' import { + type RestClient as CommunicationClient, + createRestClient as getCommunicationClient +} from '@hcengineering/communication-rest-client' +import { type CreateFileEvent, type CreateMessageEvent, RequestEventType } from '@hcengineering/communication-sdk-types' +import { MessageType } from '@hcengineering/communication-types' +import contact, { PersonSpace } from '@hcengineering/contact' +import { + type Blob, + type MeasureContext, type PersonId, type Ref, type TxOperations, @@ -22,15 +34,18 @@ import { RateLimiter, systemAccountUuid } from '@hcengineering/core' -import { getClient as getAccountClient } from '@hcengineering/account-client' -import { createRestTxOperations } from '@hcengineering/api-client' -import { type Card } from '@hcengineering/card' -import chunter from '@hcengineering/chunter' -import contact, { PersonSpace } from '@hcengineering/contact' import mail from '@hcengineering/mail' +import { buildStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage' import config from './config' import { ensureGlobalPerson, ensureLocalPerson } from './person' +export interface Attachment { + id: string + name: string + data: Buffer + contentType: string +} + function generateToken (): string { return encode( { @@ -42,28 +57,32 @@ function generateToken (): string { } export async function createMessages ( + ctx: MeasureContext, mailId: string, from: { address: string, name: string }, tos: { address: string, name: string }[], subject: string, content: string, + attachments: Attachment[], inReplyTo?: string ): Promise { - console.log(`[${mailId}] Sending message ${from.address} --> ${tos.map((to) => to.address).join(',')}`) + ctx.info('Sending message', { mailId, from: from.address, to: tos.map((to) => to.address).join(',') }) const accountClient = getAccountClient(config.accountsUrl, generateToken()) const wsInfo = await accountClient.selectWorkspace(config.workspaceUrl) const transactorUrl = wsInfo.endpoint.replace('ws://', 'http://').replace('wss://', 'https://') - const client = await createRestTxOperations(transactorUrl, wsInfo.workspace, wsInfo.token) + const txClient = await createRestTxOperations(transactorUrl, wsInfo.workspace, wsInfo.token) + const msgClient = getCommunicationClient(wsInfo.endpoint, wsInfo.workspace, wsInfo.token) - const fromPerson = await ensureGlobalPerson(accountClient, mailId, from) + const fromPerson = await ensureGlobalPerson(ctx, accountClient, mailId, from) if (fromPerson === undefined) { - console.error(`[${mailId}] Unable to create message without a proper FROM`) + ctx.error('Unable to create message without a proper FROM', { mailId }) return } try { await ensureLocalPerson( - client, + ctx, + txClient, mailId, fromPerson.uuid, fromPerson.socialId, @@ -71,21 +90,22 @@ export async function createMessages ( fromPerson.firstName, fromPerson.lastName ) - } catch (err) { - console.error(`[${mailId}] Failed to ensure local FROM person`, err) - console.error(`[${mailId}] Unable to create message without a proper FROM`) + } catch (error) { + ctx.error('Failed to ensure local FROM person', { error, mailId }) + ctx.error('Unable to create message without a proper FROM', { mailId }) return } const toPersons: { address: string, uuid: PersonUuid, socialId: PersonId }[] = [] for (const to of tos) { - const toPerson = await ensureGlobalPerson(accountClient, mailId, to) + const toPerson = await ensureGlobalPerson(ctx, accountClient, mailId, to) if (toPerson === undefined) { continue } try { await ensureLocalPerson( - client, + ctx, + txClient, mailId, toPerson.uuid, toPerson.socialId, @@ -93,42 +113,101 @@ export async function createMessages ( toPerson.firstName, toPerson.lastName ) - } catch (err) { - console.error(`[${mailId}] Failed to ensure local TO person, skip`, err) + } catch (error) { + ctx.error('Failed to ensure local TO person, skip', { error, mailId }) continue } toPersons.push({ address: to.address, ...toPerson }) } if (toPersons.length === 0) { - console.error(`[${mailId}] Unable to create message without a proper TO`) + ctx.error('Unable to create message without a proper TO', { mailId }) return } const modifiedBy = fromPerson.socialId const participants = [fromPerson.socialId, ...toPersons.map((p) => p.socialId)] - try { - const spaces = await getPersonSpaces(client, mailId, fromPerson.uuid, from.address) - if (spaces.length > 0) { - await saveMessageToSpaces(client, mailId, spaces, participants, modifiedBy, subject, content, inReplyTo) + const attachedBlobs: Attachment[] = [] + if (config.storageConfig !== undefined) { + const storageConfig = storageConfigFromEnv() + const storageAdapter = buildStorageFromConfig(storageConfig) + try { + for (const a of attachments ?? []) { + try { + await storageAdapter.put( + ctx, + { + uuid: wsInfo.workspace, + url: wsInfo.workspaceUrl, + dataId: wsInfo.workspaceDataId + }, + a.id, + a.data, + a.contentType + ) + attachedBlobs.push(a) + ctx.info('Uploaded attachment', { mailId, blobId: a.id, name: a.name, contentType: a.contentType }) + } catch (error) { + ctx.error('Failed to upload attachment', { name: a.name, error, mailId }) + } + } + } finally { + await storageAdapter.close() } - } catch (err) { - console.error(`[${mailId}] Failed to save message to personal spaces of ${fromPerson.uuid} (${from.address})`, err) + } + + try { + const spaces = await getPersonSpaces(ctx, txClient, mailId, fromPerson.uuid, from.address) + if (spaces.length > 0) { + await saveMessageToSpaces( + ctx, + txClient, + msgClient, + mailId, + spaces, + participants, + modifiedBy, + subject, + content, + attachedBlobs, + inReplyTo + ) + } + } catch (error) { + ctx.error('Failed to save message to personal spaces', { + error, + mailId, + personUuid: fromPerson.uuid, + email: from.address + }) } for (const to of toPersons) { try { - const spaces = await getPersonSpaces(client, mailId, to.uuid, to.address) + const spaces = await getPersonSpaces(ctx, txClient, mailId, to.uuid, to.address) if (spaces.length > 0) { - await saveMessageToSpaces(client, mailId, spaces, participants, modifiedBy, subject, content, inReplyTo) + await saveMessageToSpaces( + ctx, + txClient, + msgClient, + mailId, + spaces, + participants, + modifiedBy, + subject, + content, + attachedBlobs, + inReplyTo + ) } - } catch (err) { - console.error(`[${mailId}] Failed to save message spaces of ${to.socialId} (${to.address})`, err) + } catch (error) { + ctx.error('Failed to save message spaces', { error, mailId, personUuid: to.uuid, email: to.address }) } } } async function getPersonSpaces ( + ctx: MeasureContext, client: TxOperations, mailId: string, personUuid: PersonUuid, @@ -138,29 +217,32 @@ async function getPersonSpaces ( const personRefs = persons.map((p) => p._id) const spaces = await client.findAll(contact.class.PersonSpace, { person: { $in: personRefs } }) if (spaces.length === 0) { - console.log(`[${mailId}] No personal space found for ${personUuid} (${email}), skip`) + ctx.info('No personal space found, skip', { mailId, personUuid, email }) } return spaces } async function saveMessageToSpaces ( + ctx: MeasureContext, client: TxOperations, + msgClient: CommunicationClient, mailId: string, spaces: PersonSpace[], participants: PersonId[], modifiedBy: PersonId, subject: string, content: string, + attachments: Attachment[], inReplyTo?: string ): Promise { const rateLimiter = new RateLimiter(10) for (const space of spaces) { await rateLimiter.add(async () => { - console.log(`[${mailId}] Saving message to space ${space._id}`) + ctx.info('Saving message to space', { mailId, space: space._id }) const route = await client.findOne(mail.class.MailRoute, { mailId }) if (route !== undefined) { - console.log(`[${mailId}] Message is already in the thread ${route.threadId}, skip`) + ctx.info('Message is already in the thread, skip', { mailId, threadId: route.threadId }) return } @@ -169,7 +251,7 @@ async function saveMessageToSpaces ( const route = await client.findOne(mail.class.MailRoute, { mailId: inReplyTo }) if (route !== undefined) { threadId = route.threadId as Ref - console.log(`[${mailId}] Found existing thread ${threadId}`) + ctx.info('Found existing thread', { mailId, threadId }) } } if (threadId === undefined) { @@ -190,20 +272,32 @@ async function saveMessageToSpaces ( modifiedBy ) threadId = newThreadId as Ref - console.log(`[${mailId}] Created new thread ${threadId}`) + ctx.info('Created new thread', { mailId, threadId }) } - await client.addCollection( - chunter.class.ChatMessage, - space._id, - threadId, - mail.class.MailThread, - 'messages', - { message: content }, - generateId(), - undefined, - modifiedBy - ) + const msgEvent: CreateMessageEvent = { + type: RequestEventType.CreateMessage, + messageType: MessageType.Message, + card: threadId, + content, + creator: modifiedBy + } + const { id: messageId } = (await msgClient.event(msgEvent)) as any + ctx.info('Created message', { mailId, messageId }) + + for (const a of attachments) { + const fileEvent: CreateFileEvent = { + type: RequestEventType.CreateFile, + card: threadId, + message: messageId, + blobId: a.id as Ref, + fileType: a.contentType, + filename: a.name, + size: a.data.length, + creator: modifiedBy + } + await msgClient.event(fileEvent) + } await client.createDoc( mail.class.MailRoute, diff --git a/services/mail/pod-inbound-mail/src/person.ts b/services/mail/pod-inbound-mail/src/person.ts index 4e9b51e1c3..8399224ef6 100644 --- a/services/mail/pod-inbound-mail/src/person.ts +++ b/services/mail/pod-inbound-mail/src/person.ts @@ -13,6 +13,7 @@ // limitations under the License. // import { + type MeasureContext, buildSocialIdString, generateId, PersonId, @@ -25,11 +26,18 @@ import contact, { AvatarType, combineName, SocialIdentity } from '@hcengineering import { AccountClient } from '@hcengineering/account-client' export async function ensureGlobalPerson ( + ctx: MeasureContext, client: AccountClient, mailId: string, contact: { address: string, name: string } ): Promise<{ socialId: PersonId, uuid: PersonUuid, firstName: string, lastName: string } | undefined> { - const [firstName, lastName] = contact.name.split(' ') + let [firstName, lastName] = contact.name.split(' ') + if (firstName === undefined || firstName.length === 0) { + firstName = contact.address.split('@')[0] + } + if (lastName === undefined || lastName.length === 0) { + lastName = contact.address.split('@')[1] + } const socialKey = buildSocialIdString({ type: SocialIdType.EMAIL, value: contact.address }) const socialId = await client.findSocialIdBySocialKey(socialKey) const uuid = await client.findPersonBySocialKey(socialKey) @@ -38,15 +46,16 @@ export async function ensureGlobalPerson ( } try { const globalPerson = await client.ensurePerson(SocialIdType.EMAIL, contact.address, firstName, lastName) - console.log(`[${mailId}] Created global person for ${contact.address}: ${globalPerson.uuid}`) + ctx.info('Created global person', { mailId, email: contact.address, personUuid: globalPerson.uuid }) return { ...globalPerson, firstName, lastName } - } catch (err) { - console.error(`[${mailId}] Failed to create global person for ${contact.address}`, err) + } catch (error) { + ctx.error('Failed to create global person', { mailId, error, email: contact.address }) } return undefined } export async function ensureLocalPerson ( + ctx: MeasureContext, client: TxOperations, mailId: string, personUuid: PersonUuid, @@ -71,7 +80,7 @@ export async function ensureLocalPerson ( if (person === undefined) { throw new Error(`Failed to create local person for ${personUuid}`) } else { - console.log(`[${mailId}] Created local person for ${personUuid}: ${person._id}`) + ctx.info('Created local person', { mailId, personUuid, _id: person._id }) } } const socialId = await client.findOne(contact.class.SocialIdentity, { @@ -93,7 +102,7 @@ export async function ensureLocalPerson ( }, personId as any as Ref ) - console.log(`[${mailId}] Created local socialId for ${personUuid}: ${email}`) + ctx.info('Created local socialId', { mailId, personUuid, email }) } const channel = await client.findOne(contact.class.Channel, { attachedTo: person._id, @@ -114,6 +123,6 @@ export async function ensureLocalPerson ( }, generateId() ) - console.log(`[${mailId}] Created channel for ${personUuid}: ${email}`) + ctx.info('Created channel', { mailId, personUuid, email }) } }