Change some dependencies
|
@ -1,6 +1,6 @@
|
|||
<h1 align="center"><a href="https://www.kill-the-newsletter.com">Kill the Newsletter!</a></h1>
|
||||
<h3 align="center">Convert email newsletters into Atom feeds</h3>
|
||||
<p align="center"><img alt="Convert email newsletters into Atom feeds" src="static/logo.png" width="150"></p>
|
||||
<p align="center"><img alt="Convert email newsletters into Atom feeds" src="static/logo.svg"></p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/leafac/www.kill-the-newsletter.com"><img alt="Source" src="https://img.shields.io/badge/Source---"></a>
|
||||
<a href="https://github.com/leafac/www.kill-the-newsletter.com/actions"><img alt="Continuous Integration" src="https://github.com/leafac/www.kill-the-newsletter.com/workflows/.github/workflows/main.yml/badge.svg"></a>
|
||||
|
|
|
@ -685,74 +685,6 @@
|
|||
"chalk": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"@oozcitak/dom": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.5.tgz",
|
||||
"integrity": "sha512-L6v3Mwb0TaYBYgeYlIeBaHnc+2ZEaDSbFiRm5KmqZQSoBlbPlf+l6aIH/sD5GUf2MYwULw00LT7+dOnEuAEC0A==",
|
||||
"requires": {
|
||||
"@oozcitak/infra": "1.0.5",
|
||||
"@oozcitak/url": "1.0.0",
|
||||
"@oozcitak/util": "8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oozcitak/util": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.0.0.tgz",
|
||||
"integrity": "sha512-+9Hq6yuoq/3TRV/n/xcpydGBq2qN2/DEDMqNTG7rm95K6ZE2/YY/sPyx62+1n8QsE9O26e5M1URlXsk+AnN9Jw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@oozcitak/infra": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.5.tgz",
|
||||
"integrity": "sha512-o+zZH7M6l5e3FaAWy3ojaPIVN5eusaYPrKm6MZQt0DKNdgXa2wDYExjpP0t/zx+GoQgQKzLu7cfD8rHCLt8JrQ==",
|
||||
"requires": {
|
||||
"@oozcitak/util": "8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oozcitak/util": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.0.0.tgz",
|
||||
"integrity": "sha512-+9Hq6yuoq/3TRV/n/xcpydGBq2qN2/DEDMqNTG7rm95K6ZE2/YY/sPyx62+1n8QsE9O26e5M1URlXsk+AnN9Jw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@oozcitak/url": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.0.tgz",
|
||||
"integrity": "sha512-LGrMeSxeLzsdaitxq3ZmBRVOrlRRQIgNNci6L0VRnOKlJFuRIkNm4B+BObXPCJA6JT5bEJtrrwjn30jueHJYZQ==",
|
||||
"requires": {
|
||||
"@oozcitak/infra": "1.0.3",
|
||||
"@oozcitak/util": "1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oozcitak/infra": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.3.tgz",
|
||||
"integrity": "sha512-9O2wxXGnRzy76O1XUxESxDGsXT5kzETJPvYbreO4mv6bqe1+YSuux2cZTagjJ/T4UfEwFJz5ixanOqB0QgYAag==",
|
||||
"requires": {
|
||||
"@oozcitak/util": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oozcitak/util": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-1.0.1.tgz",
|
||||
"integrity": "sha512-dFwFqcKrQnJ2SapOmRD1nQWEZUtbtIy9Y6TyJquzsalWNJsKIPxmTI0KG6Ypyl8j7v89L2wixH9fQDNrF78hKg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@oozcitak/util": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-1.0.2.tgz",
|
||||
"integrity": "sha512-4n8B1cWlJleSOSba5gxsMcN4tO8KkkcvXhNWW+ADqvq9Xj+Lrl9uCa90GRpjekqQJyt84aUX015DG81LFpZYXA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@oozcitak/util": {
|
||||
"version": "8.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.3.tgz",
|
||||
"integrity": "sha512-Ufpab7G5PfnEhQyy5kDg9C8ltWJjsVT1P/IYqacjstaqydG4Q21HAT2HUZQYBrC/a1ZLKCz87pfydlDvv8y97w=="
|
||||
},
|
||||
"@opencensus/core": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.9.tgz",
|
||||
|
@ -1139,6 +1071,17 @@
|
|||
"pretty-format": "^25.1.0"
|
||||
}
|
||||
},
|
||||
"@types/jsdom": {
|
||||
"version": "16.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.1.tgz",
|
||||
"integrity": "sha512-KCEq427OsWfpX7FRyEMb3i2XIuz8Pt3XPls4nmX0iMTDJWsHD4Kzoa3v4Uv9c9IDf11ALeHUtPcyAjTz/HV03Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/parse5": "*",
|
||||
"@types/tough-cookie": "*"
|
||||
}
|
||||
},
|
||||
"@types/mailparser": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mailparser/-/mailparser-2.7.2.tgz",
|
||||
|
@ -1169,18 +1112,18 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/parse5": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.2.tgz",
|
||||
"integrity": "sha512-BOl+6KDs4ItndUWUFchy3aEqGdHhw0BC4Uu+qoDonN/f0rbUnJbm71Ulj8Tt9jLFRaAxPLKvdS1bBLfx1qXR9g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prettier": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz",
|
||||
"integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
|
||||
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/qs": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz",
|
||||
|
@ -1193,25 +1136,6 @@
|
|||
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "16.9.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.29.tgz",
|
||||
"integrity": "sha512-aE5sV9XVqKvIR8Lqa73hXvlqBzz5hBG0jtV9jZ1uuEWRmW8KN/mdQQmsYlPx6z/b2xa8zR3jtk7WoT+2/m4suA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"version": "16.9.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.5.tgz",
|
||||
"integrity": "sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
|
||||
|
@ -1238,6 +1162,12 @@
|
|||
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/tough-cookie": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz",
|
||||
"integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/write-file-atomic": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/write-file-atomic/-/write-file-atomic-3.0.0.tgz",
|
||||
|
@ -1265,8 +1195,7 @@
|
|||
"abab": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz",
|
||||
"integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg=="
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
|
@ -1286,32 +1215,21 @@
|
|||
"acorn": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
|
||||
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg=="
|
||||
},
|
||||
"acorn-globals": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz",
|
||||
"integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==",
|
||||
"dev": true,
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz",
|
||||
"integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==",
|
||||
"requires": {
|
||||
"acorn": "^6.0.1",
|
||||
"acorn-walk": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
|
||||
"integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
|
||||
"dev": true
|
||||
}
|
||||
"acorn": "^7.1.1",
|
||||
"acorn-walk": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
|
||||
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
|
||||
"dev": true
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz",
|
||||
"integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ=="
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "4.3.0",
|
||||
|
@ -1849,8 +1767,7 @@
|
|||
"browser-process-hrtime": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
|
||||
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
|
||||
"dev": true
|
||||
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow=="
|
||||
},
|
||||
"browser-resolve": {
|
||||
"version": "1.11.3",
|
||||
|
@ -2335,14 +2252,12 @@
|
|||
"cssom": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||
"integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw=="
|
||||
},
|
||||
"cssstyle": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.2.0.tgz",
|
||||
"integrity": "sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==",
|
||||
"dev": true,
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
|
||||
"integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
|
||||
"requires": {
|
||||
"cssom": "~0.3.6"
|
||||
},
|
||||
|
@ -2350,17 +2265,10 @@
|
|||
"cssom": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
|
||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "2.6.10",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
|
||||
"integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==",
|
||||
"dev": true
|
||||
},
|
||||
"culvert": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz",
|
||||
|
@ -2380,14 +2288,13 @@
|
|||
"integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ=="
|
||||
},
|
||||
"data-urls": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz",
|
||||
"integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==",
|
||||
"dev": true,
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz",
|
||||
"integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==",
|
||||
"requires": {
|
||||
"abab": "^2.0.0",
|
||||
"whatwg-mimetype": "^2.2.0",
|
||||
"whatwg-url": "^7.0.0"
|
||||
"abab": "^2.0.3",
|
||||
"whatwg-mimetype": "^2.3.0",
|
||||
"whatwg-url": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
|
@ -2410,6 +2317,11 @@
|
|||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
|
||||
"dev": true
|
||||
},
|
||||
"decimal.js": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz",
|
||||
"integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw=="
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
|
@ -2545,12 +2457,18 @@
|
|||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
|
||||
},
|
||||
"domexception": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
|
||||
"integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
|
||||
"dev": true,
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
|
||||
"integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==",
|
||||
"requires": {
|
||||
"webidl-conversions": "^4.0.2"
|
||||
"webidl-conversions": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"webidl-conversions": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
|
||||
"integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"domhandler": {
|
||||
|
@ -2641,9 +2559,9 @@
|
|||
}
|
||||
},
|
||||
"entities": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
|
||||
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
|
@ -3397,12 +3315,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"html-encoding-sniffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
|
||||
"integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
|
||||
"dev": true,
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
||||
"integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==",
|
||||
"requires": {
|
||||
"whatwg-encoding": "^1.0.1"
|
||||
"whatwg-encoding": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"html-escaper": {
|
||||
|
@ -3433,6 +3350,13 @@
|
|||
"entities": "^1.1.1",
|
||||
"inherits": "^2.0.1",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"entities": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
|
@ -3573,8 +3497,7 @@
|
|||
"ip-regex": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
|
||||
"integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
|
||||
"dev": true
|
||||
"integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
|
@ -3750,6 +3673,11 @@
|
|||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-potential-custom-element-name": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz",
|
||||
"integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c="
|
||||
},
|
||||
"is-redirect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
|
||||
|
@ -4312,6 +4240,156 @@
|
|||
"@types/yargs": "^15.0.0",
|
||||
"chalk": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"acorn-globals": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz",
|
||||
"integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^6.0.1",
|
||||
"acorn-walk": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
|
||||
"integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
|
||||
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
|
||||
"dev": true
|
||||
},
|
||||
"data-urls": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz",
|
||||
"integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"abab": "^2.0.0",
|
||||
"whatwg-mimetype": "^2.2.0",
|
||||
"whatwg-url": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"domexception": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
|
||||
"integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"webidl-conversions": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"html-encoding-sniffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
|
||||
"integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"whatwg-encoding": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"jsdom": {
|
||||
"version": "15.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz",
|
||||
"integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"abab": "^2.0.0",
|
||||
"acorn": "^7.1.0",
|
||||
"acorn-globals": "^4.3.2",
|
||||
"array-equal": "^1.0.0",
|
||||
"cssom": "^0.4.1",
|
||||
"cssstyle": "^2.0.0",
|
||||
"data-urls": "^1.1.0",
|
||||
"domexception": "^1.0.1",
|
||||
"escodegen": "^1.11.1",
|
||||
"html-encoding-sniffer": "^1.0.2",
|
||||
"nwsapi": "^2.2.0",
|
||||
"parse5": "5.1.0",
|
||||
"pn": "^1.1.0",
|
||||
"request": "^2.88.0",
|
||||
"request-promise-native": "^1.0.7",
|
||||
"saxes": "^3.1.9",
|
||||
"symbol-tree": "^3.2.2",
|
||||
"tough-cookie": "^3.0.1",
|
||||
"w3c-hr-time": "^1.0.1",
|
||||
"w3c-xmlserializer": "^1.1.2",
|
||||
"webidl-conversions": "^4.0.2",
|
||||
"whatwg-encoding": "^1.0.5",
|
||||
"whatwg-mimetype": "^2.3.0",
|
||||
"whatwg-url": "^7.0.0",
|
||||
"ws": "^7.0.0",
|
||||
"xml-name-validator": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"parse5": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
|
||||
"integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==",
|
||||
"dev": true
|
||||
},
|
||||
"saxes": {
|
||||
"version": "3.1.11",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz",
|
||||
"integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"xmlchars": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
|
||||
"integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ip-regex": "^2.1.0",
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"tr46": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
|
||||
"integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"w3c-xmlserializer": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz",
|
||||
"integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domexception": "^1.0.1",
|
||||
"webidl-conversions": "^4.0.2",
|
||||
"xml-name-validator": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
|
||||
"integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash.sortby": "^4.7.0",
|
||||
"tr46": "^1.0.1",
|
||||
"webidl-conversions": "^4.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5084,7 +5162,8 @@
|
|||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.13.1",
|
||||
|
@ -5102,36 +5181,35 @@
|
|||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
||||
},
|
||||
"jsdom": {
|
||||
"version": "15.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz",
|
||||
"integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==",
|
||||
"dev": true,
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.2.2.tgz",
|
||||
"integrity": "sha512-pDFQbcYtKBHxRaP55zGXCJWgFHkDAYbKcsXEK/3Icu9nKYZkutUXfLBwbD+09XDutkYSHcgfQLZ0qvpAAm9mvg==",
|
||||
"requires": {
|
||||
"abab": "^2.0.0",
|
||||
"acorn": "^7.1.0",
|
||||
"acorn-globals": "^4.3.2",
|
||||
"array-equal": "^1.0.0",
|
||||
"cssom": "^0.4.1",
|
||||
"cssstyle": "^2.0.0",
|
||||
"data-urls": "^1.1.0",
|
||||
"domexception": "^1.0.1",
|
||||
"escodegen": "^1.11.1",
|
||||
"html-encoding-sniffer": "^1.0.2",
|
||||
"abab": "^2.0.3",
|
||||
"acorn": "^7.1.1",
|
||||
"acorn-globals": "^6.0.0",
|
||||
"cssom": "^0.4.4",
|
||||
"cssstyle": "^2.2.0",
|
||||
"data-urls": "^2.0.0",
|
||||
"decimal.js": "^10.2.0",
|
||||
"domexception": "^2.0.1",
|
||||
"escodegen": "^1.14.1",
|
||||
"html-encoding-sniffer": "^2.0.1",
|
||||
"is-potential-custom-element-name": "^1.0.0",
|
||||
"nwsapi": "^2.2.0",
|
||||
"parse5": "5.1.0",
|
||||
"pn": "^1.1.0",
|
||||
"request": "^2.88.0",
|
||||
"request-promise-native": "^1.0.7",
|
||||
"saxes": "^3.1.9",
|
||||
"symbol-tree": "^3.2.2",
|
||||
"parse5": "5.1.1",
|
||||
"request": "^2.88.2",
|
||||
"request-promise-native": "^1.0.8",
|
||||
"saxes": "^5.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^3.0.1",
|
||||
"w3c-hr-time": "^1.0.1",
|
||||
"w3c-xmlserializer": "^1.1.2",
|
||||
"webidl-conversions": "^4.0.2",
|
||||
"w3c-hr-time": "^1.0.2",
|
||||
"w3c-xmlserializer": "^2.0.0",
|
||||
"webidl-conversions": "^6.0.0",
|
||||
"whatwg-encoding": "^1.0.5",
|
||||
"whatwg-mimetype": "^2.3.0",
|
||||
"whatwg-url": "^7.0.0",
|
||||
"ws": "^7.0.0",
|
||||
"whatwg-url": "^8.0.0",
|
||||
"ws": "^7.2.3",
|
||||
"xml-name-validator": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -5139,7 +5217,6 @@
|
|||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
|
||||
"integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ip-regex": "^2.1.0",
|
||||
"psl": "^1.1.28",
|
||||
|
@ -5313,8 +5390,7 @@
|
|||
"lodash.sortby": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
|
||||
"dev": true
|
||||
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
|
||||
},
|
||||
"log-driver": {
|
||||
"version": "1.2.7",
|
||||
|
@ -5330,14 +5406,6 @@
|
|||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"requires": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"lowercase-keys": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
|
||||
|
@ -5772,19 +5840,13 @@
|
|||
"nwsapi": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
|
||||
"integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ=="
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"object-copy": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
|
||||
|
@ -5978,10 +6040,9 @@
|
|||
}
|
||||
},
|
||||
"parse5": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
|
||||
"integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==",
|
||||
"dev": true
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
|
||||
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
|
@ -6382,16 +6443,6 @@
|
|||
"sisteransi": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.8.1"
|
||||
}
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||
|
@ -6520,31 +6571,11 @@
|
|||
"strip-json-comments": "~2.0.1"
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
|
||||
"integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
|
||||
"integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.19.1"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"read": {
|
||||
"version": "1.0.7",
|
||||
|
@ -6682,7 +6713,6 @@
|
|||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz",
|
||||
"integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.15"
|
||||
}
|
||||
|
@ -6691,7 +6721,6 @@
|
|||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz",
|
||||
"integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"request-promise-core": "1.1.3",
|
||||
"stealthy-require": "^1.1.1",
|
||||
|
@ -6953,27 +6982,22 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"sanitize-xml-string": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/sanitize-xml-string/-/sanitize-xml-string-1.1.0.tgz",
|
||||
"integrity": "sha512-RzX25K64YtZm9FvdZr/Ac7Eeq0va1YX0xmpOkjWoREhgKXXldrJRVJhBel83nS8omIcaKcNTdLY8XzOIK920HA=="
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"saxes": {
|
||||
"version": "3.1.11",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz",
|
||||
"integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==",
|
||||
"dev": true,
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
|
||||
"integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
|
||||
"requires": {
|
||||
"xmlchars": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
|
||||
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
"xmlchars": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
|
@ -7416,8 +7440,7 @@
|
|||
"stealthy-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
|
||||
"dev": true
|
||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
|
||||
},
|
||||
"string-length": {
|
||||
"version": "3.1.0",
|
||||
|
@ -7518,8 +7541,7 @@
|
|||
"symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
|
||||
},
|
||||
"systeminformation": {
|
||||
"version": "4.23.1",
|
||||
|
@ -7699,12 +7721,11 @@
|
|||
}
|
||||
},
|
||||
"tr46": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
|
||||
"integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
|
||||
"dev": true,
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz",
|
||||
"integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==",
|
||||
"requires": {
|
||||
"punycode": "^2.1.0"
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"tree-kill": {
|
||||
|
@ -8080,19 +8101,15 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
||||
"integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browser-process-hrtime": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"w3c-xmlserializer": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz",
|
||||
"integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==",
|
||||
"dev": true,
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz",
|
||||
"integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==",
|
||||
"requires": {
|
||||
"domexception": "^1.0.1",
|
||||
"webidl-conversions": "^4.0.2",
|
||||
"xml-name-validator": "^3.0.0"
|
||||
}
|
||||
},
|
||||
|
@ -8106,16 +8123,14 @@
|
|||
}
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
|
||||
"dev": true
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
||||
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w=="
|
||||
},
|
||||
"whatwg-encoding": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",
|
||||
"integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"iconv-lite": "0.4.24"
|
||||
}
|
||||
|
@ -8123,18 +8138,23 @@
|
|||
"whatwg-mimetype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
|
||||
"integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g=="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
|
||||
"integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
|
||||
"dev": true,
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.0.0.tgz",
|
||||
"integrity": "sha512-41ou2Dugpij8/LPO5Pq64K5q++MnRCBpEHvQr26/mArEKTkCV5aoXIqyhuYtE0pkqScXwhf2JP57rkRTYM29lQ==",
|
||||
"requires": {
|
||||
"lodash.sortby": "^4.7.0",
|
||||
"tr46": "^1.0.1",
|
||||
"webidl-conversions": "^4.0.2"
|
||||
"tr46": "^2.0.0",
|
||||
"webidl-conversions": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"webidl-conversions": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
|
||||
"integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
|
@ -8247,10 +8267,9 @@
|
|||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz",
|
||||
"integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==",
|
||||
"dev": true
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz",
|
||||
"integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA=="
|
||||
},
|
||||
"xdg-basedir": {
|
||||
"version": "3.0.0",
|
||||
|
@ -8261,24 +8280,12 @@
|
|||
"xml-name-validator": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
|
||||
"integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"xmlbuilder2": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-2.1.2.tgz",
|
||||
"integrity": "sha512-PI710tmtVlQ5VmwzbRTuhmVhKnj9pM8Si+iOZCV2g2SNo3gCrpzR2Ka9wNzZtqfD+mnP+xkrqoNy0sjKZqP4Dg==",
|
||||
"requires": {
|
||||
"@oozcitak/dom": "1.15.5",
|
||||
"@oozcitak/infra": "1.0.5",
|
||||
"@oozcitak/util": "8.3.3"
|
||||
}
|
||||
"integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
|
||||
},
|
||||
"xmlchars": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
|
||||
},
|
||||
"xregexp": {
|
||||
"version": "2.0.0",
|
||||
|
|
13
package.json
|
@ -1,29 +1,28 @@
|
|||
{
|
||||
"scripts": {
|
||||
"start": "tsc && concurrently \"tsc --watch\" \"nodemon lib\"",
|
||||
"start": "tsc && open http://localhost:8000 && concurrently \"tsc --watch\" \"nodemon lib\"",
|
||||
"test": "tsc && jest && prettier --check src"
|
||||
},
|
||||
"dependencies": {
|
||||
"caddy-npm": "^2.0.0-beta.20",
|
||||
"crypto-random-string": "^3.2.0",
|
||||
"entities": "^2.0.0",
|
||||
"express": "^4.17.1",
|
||||
"jsdom": "^16.2.2",
|
||||
"mailparser": "^2.7.7",
|
||||
"pm2": "^4.2.3",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0",
|
||||
"sanitize-xml-string": "^1.1.0",
|
||||
"smtp-server": "^3.6.0",
|
||||
"write-file-atomic": "^3.0.3",
|
||||
"xmlbuilder2": "^2.1.2"
|
||||
"write-file-atomic": "^3.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "^25.1.4",
|
||||
"@types/jsdom": "^16.2.1",
|
||||
"@types/mailparser": "^2.7.2",
|
||||
"@types/node": "^13.9.8",
|
||||
"@types/nodemailer": "^6.4.0",
|
||||
"@types/qs": "^6.9.1",
|
||||
"@types/react": "^16.9.29",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/smtp-server": "^3.5.4",
|
||||
"@types/write-file-atomic": "^3.0.0",
|
||||
"axios": "^0.19.2",
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
import express from "express";
|
||||
import { SMTPServer } from "smtp-server";
|
||||
import mailparser from "mailparser";
|
||||
import { promises as fs } from "fs";
|
||||
import * as entities from "entities";
|
||||
import { JSDOM } from "jsdom";
|
||||
import * as sanitizeXMLString from "sanitize-xml-string";
|
||||
import writeFileAtomic from "write-file-atomic";
|
||||
import cryptoRandomString from "crypto-random-string";
|
||||
|
||||
export const WEB_PORT = process.env.WEB_PORT ?? 8000;
|
||||
export const EMAIL_PORT = process.env.EMAIL_PORT ?? 2525;
|
||||
export const BASE_URL = process.env.BASE_URL ?? "http://localhost:8000";
|
||||
export const EMAIL_DOMAIN = process.env.EMAIL_DOMAIN ?? "localhost";
|
||||
export const ISSUE_REPORT =
|
||||
process.env.ISSUE_REPORT ?? "mailto:kill-the-newsletter@leafac.com";
|
||||
|
||||
export const webServer = express()
|
||||
.use(express.static("static"))
|
||||
.use(express.urlencoded({ extended: true }))
|
||||
.get("/", (req, res) =>
|
||||
res.send(
|
||||
layout(`
|
||||
<form method="POST" action="/">
|
||||
<p>
|
||||
<input type="text" name="name" placeholder="Newsletter Name…" maxlength="500" size="30" required>
|
||||
<button>Create Inbox</button>
|
||||
</p>
|
||||
</form>
|
||||
`)
|
||||
)
|
||||
)
|
||||
.post("/", async (req, res, next) => {
|
||||
try {
|
||||
const { name } = req.body;
|
||||
const identifier = createIdentifier();
|
||||
await writeFileAtomic(feedPath(identifier), feed(X(name), identifier));
|
||||
res.send(
|
||||
layout(`
|
||||
<p><strong>“${H(name)}” Inbox Created</strong></p>
|
||||
${created(identifier)}
|
||||
`)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
}
|
||||
})
|
||||
.get("/alternate", (req, res) =>
|
||||
res.send(
|
||||
layout(`
|
||||
<p>Typically each entry in a feed includes a link<br>to an online version of the same content,<br>but the content from the entries in a <strong>Kill the Newsletter!</strong><br>feed come from emails—an online version may not even exist—<br>so you’re reading this instead.</p>
|
||||
<p><a href="${BASE_URL}"><strong>Create an Inbox</strong></a></p>
|
||||
`)
|
||||
)
|
||||
)
|
||||
.listen(WEB_PORT);
|
||||
|
||||
export const emailServer = new SMTPServer({
|
||||
disabledCommands: ["AUTH", "STARTTLS"],
|
||||
async onData(stream, session, callback) {
|
||||
try {
|
||||
const email = await mailparser.simpleParser(stream);
|
||||
const newEntry = entry(
|
||||
X(email.subject ?? ""),
|
||||
X(email.from?.text ?? ""),
|
||||
X(typeof email.html === "string" ? email.html : email.textAsHtml ?? "")
|
||||
);
|
||||
for (const { address } of session.envelope.rcptTo) {
|
||||
const match = address.match(
|
||||
new RegExp(`^(?<identifier>\\w+)@${EMAIL_DOMAIN}$`)
|
||||
);
|
||||
if (match?.groups === undefined) continue;
|
||||
const path = feedPath(match.groups.identifier);
|
||||
const xmlText = await fs.readFile(path, "utf8").catch(() => null);
|
||||
if (xmlText === null) continue;
|
||||
const xml = new JSDOM(xmlText, { contentType: "text/xml" });
|
||||
const document = xml.window.document;
|
||||
const updated = document.querySelector("feed > updated");
|
||||
if (updated === null)
|
||||
throw new Error(`Can’t find ‘updated’ field in feed at ‘${path}’.`);
|
||||
updated.textContent = now();
|
||||
const firstEntry = document.querySelector("feed > entry:first-of-type");
|
||||
if (firstEntry !== null)
|
||||
firstEntry.insertAdjacentHTML("beforebegin", newEntry);
|
||||
else
|
||||
document
|
||||
.querySelector("feed")!
|
||||
.insertAdjacentHTML("beforeend", newEntry);
|
||||
while (
|
||||
document.querySelector("feed > entry") !== null &&
|
||||
xml.serialize().length > 500_000
|
||||
)
|
||||
document.querySelector("feed > entry:last-of-type")!.remove();
|
||||
await writeFileAtomic(
|
||||
path,
|
||||
`<?xml version="1.0" encoding="utf-8"?>${xml.serialize()}`
|
||||
);
|
||||
}
|
||||
callback();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error receiving email: ${JSON.stringify(session, null, 2)}`
|
||||
);
|
||||
console.error(error);
|
||||
stream.resume();
|
||||
callback(new Error("Failed to receive message. Please try again."));
|
||||
}
|
||||
},
|
||||
}).listen(EMAIL_PORT);
|
||||
|
||||
function layout(content: string): string {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Kill the Newsletter!</title>
|
||||
<meta name="author" content="Leandro Facchinetti">
|
||||
<meta name="description" content="Convert email newsletters into Atom feeds.">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><a href="/">Kill the Newsletter!</a></h1>
|
||||
<p>Convert email newsletters into Atom feeds</p>
|
||||
<p><img alt="Convert email newsletters into Atom feeds" src="/logo.svg"></p>
|
||||
</header>
|
||||
<main>${content}</main>
|
||||
<footer><p>By <a href="https://www.leafac.com">Leandro Facchinetti</a> · <a href="https://github.com/leafac/www.kill-the-newsletter.com">Source</a> · <a href="${ISSUE_REPORT}">Report an Issue</a></p></footer>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
function created(identifier: string): string {
|
||||
return `
|
||||
<p>Sign up for the newsletter with<br><code>${feedEmail(
|
||||
identifier
|
||||
)}</code></p>
|
||||
<p>Subscribe to the Atom feed at<br><code>${feedURL(identifier)}</code></p>
|
||||
<p>Don’t share these addresses.<br>They contain an identifier that other people could use<br>to send you spam and to control your newsletter subscriptions.</p>
|
||||
<p>Enjoy your readings!</p>
|
||||
<p><a href="${BASE_URL}"><strong>Create Another Inbox</strong></a></p>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
function feed(name: string, identifier: string): string {
|
||||
return `<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<link rel="self" type="application/atom+xml" href="${feedURL(
|
||||
identifier
|
||||
)}"/>
|
||||
<link rel="alternate" type="text/html" href="${BASE_URL}/alternate"/>
|
||||
<id>${urn(identifier)}</id>
|
||||
<title>${name}</title>
|
||||
<subtitle>Kill the Newsletter! Inbox: ${feedEmail(
|
||||
identifier
|
||||
)} → ${feedURL(identifier)}</subtitle>
|
||||
<updated>${now()}</updated>
|
||||
<author><name>Kill the Newsletter!</name></author>
|
||||
${entry(
|
||||
`“${name}” Inbox Created`,
|
||||
"Kill the Newsletter!",
|
||||
X(created(identifier))
|
||||
)}
|
||||
</feed>
|
||||
`;
|
||||
}
|
||||
|
||||
function entry(title: string, author: string, content: string): string {
|
||||
return `
|
||||
<entry>
|
||||
<id>${urn(createIdentifier())}</id>
|
||||
<title>${title}</title>
|
||||
<author><name>${author}</name></author>
|
||||
<updated>${now()}</updated>
|
||||
<link rel="alternate" type="text/html" href="${BASE_URL}/alternate"/>
|
||||
<content type="html">${content}</content>
|
||||
</entry>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
function createIdentifier(): string {
|
||||
return cryptoRandomString({
|
||||
length: 20,
|
||||
characters: "1234567890qwertyuiopasdfghjklzxcvbnm",
|
||||
});
|
||||
}
|
||||
|
||||
function now(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function feedPath(identifier: string): string {
|
||||
return `static/feeds/${identifier}.xml`;
|
||||
}
|
||||
|
||||
function feedURL(identifier: string): string {
|
||||
return `${BASE_URL}/feeds/${identifier}.xml`;
|
||||
}
|
||||
|
||||
function feedEmail(identifier: string): string {
|
||||
return `${identifier}@${EMAIL_DOMAIN}`;
|
||||
}
|
||||
|
||||
function urn(identifier: string): string {
|
||||
return `urn:kill-the-newsletter:${identifier}`;
|
||||
}
|
||||
|
||||
function X(string: string): string {
|
||||
return entities.encodeXML(sanitizeXMLString.sanitize(string));
|
||||
}
|
||||
|
||||
function H(string: string): string {
|
||||
return entities.encodeHTML(sanitizeXMLString.sanitize(string));
|
||||
}
|
342
src/index.tsx
|
@ -1,342 +0,0 @@
|
|||
import express from "express";
|
||||
import { SMTPServer } from "smtp-server";
|
||||
import mailparser from "mailparser";
|
||||
import React from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import * as xmlbuilder2 from "xmlbuilder2";
|
||||
import { promises as fs } from "fs";
|
||||
import writeFileAtomic from "write-file-atomic";
|
||||
import cryptoRandomString from "crypto-random-string";
|
||||
|
||||
export const WEB_PORT = process.env.WEB_PORT ?? 8000;
|
||||
export const EMAIL_PORT = process.env.EMAIL_PORT ?? 2525;
|
||||
export const BASE_URL = process.env.BASE_URL ?? "http://localhost:8000";
|
||||
export const EMAIL_DOMAIN = process.env.EMAIL_DOMAIN ?? "localhost";
|
||||
export const ISSUE_REPORT =
|
||||
process.env.ISSUE_REPORT ?? "mailto:kill-the-newsletter@leafac.com";
|
||||
|
||||
export const webServer = express()
|
||||
.use(express.static("static"))
|
||||
.use(express.urlencoded({ extended: true }))
|
||||
.get("/", (req, res) =>
|
||||
res.send(
|
||||
renderHTML(
|
||||
<Layout>
|
||||
<Form></Form>
|
||||
</Layout>
|
||||
)
|
||||
)
|
||||
)
|
||||
.post("/", (req, res, next) => {
|
||||
let name: string;
|
||||
let identifier: string;
|
||||
(async () => {
|
||||
name = req.body.name;
|
||||
identifier = createIdentifier();
|
||||
await fs.writeFile(
|
||||
feedPath(identifier),
|
||||
renderXML(Feed({ name, identifier }))
|
||||
);
|
||||
res.send(
|
||||
renderHTML(
|
||||
<Layout>
|
||||
<p>
|
||||
<strong>“{name}” Inbox Created</strong>
|
||||
</p>
|
||||
<Created identifier={identifier}></Created>
|
||||
</Layout>
|
||||
)
|
||||
);
|
||||
})().catch((error) => {
|
||||
console.error(
|
||||
`Error creating feed: ${JSON.stringify({ name, identifier }, null, 2)}`
|
||||
);
|
||||
console.error(error);
|
||||
next(error);
|
||||
});
|
||||
})
|
||||
.get("/alternate", (req, res) =>
|
||||
res.send(
|
||||
renderHTML(
|
||||
<Layout>
|
||||
<p>
|
||||
Typically each entry on a feed includes a link
|
||||
<br />
|
||||
to an online version of the same content,
|
||||
<br />
|
||||
but the content from the entries on a{" "}
|
||||
<strong>Kill the Newsletter!</strong>
|
||||
<br /> feed come from emails—an online version may not even exist—
|
||||
<br />
|
||||
so you’re reading this instead.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href={BASE_URL}>
|
||||
<strong>Create an Inbox</strong>
|
||||
</a>
|
||||
</p>
|
||||
</Layout>
|
||||
)
|
||||
)
|
||||
)
|
||||
.listen(WEB_PORT);
|
||||
|
||||
export const emailServer = new SMTPServer({
|
||||
disabledCommands: ["AUTH", "STARTTLS"],
|
||||
onData(stream, session, callback) {
|
||||
let email: mailparser.ParsedMail;
|
||||
(async () => {
|
||||
email = await mailparser.simpleParser(stream);
|
||||
const { entry } = Entry({
|
||||
title: email.subject ?? "",
|
||||
author: email.from?.text ?? "",
|
||||
content:
|
||||
typeof email.html === "string" ? email.html : email.textAsHtml ?? "",
|
||||
});
|
||||
for (const { address } of session.envelope.rcptTo) {
|
||||
const match = address.match(new RegExp(`^(\\w+)@${EMAIL_DOMAIN}$`));
|
||||
if (match === null) continue;
|
||||
const identifier = match[1];
|
||||
const path = feedPath(identifier);
|
||||
const xmlText = await fs.readFile(path, "utf8").catch(() => null);
|
||||
if (xmlText === null) continue;
|
||||
const xml = parseXML(xmlText);
|
||||
xml.feed.updated = now();
|
||||
if (xml.feed.entry === undefined) xml.feed.entry = [];
|
||||
if (!Array.isArray(xml.feed.entry)) xml.feed.entry = [xml.feed.entry];
|
||||
xml.feed.entry.unshift(entry);
|
||||
while (xml.feed.entry.length > 0 && renderXML(xml).length > 500_000)
|
||||
xml.feed.entry.pop();
|
||||
await writeFileAtomic(path, renderXML(xml));
|
||||
}
|
||||
callback();
|
||||
})().catch((error) => {
|
||||
console.error(
|
||||
`Error receiving email: ${JSON.stringify({ session, email }, null, 2)}`
|
||||
);
|
||||
console.error(error);
|
||||
stream.resume();
|
||||
callback(new Error("Failed to receive message. Please try again."));
|
||||
});
|
||||
},
|
||||
}).listen(EMAIL_PORT);
|
||||
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="author" content="Leandro Facchinetti" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Convert email newsletters into Atom feeds."
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="/favicon-32x32.png"
|
||||
sizes="32x32"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="/favicon-16x16.png"
|
||||
sizes="16x16"
|
||||
/>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/fonts/typeface-pt-sans/index.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/fonts/typeface-pt-mono/index.css"
|
||||
/>
|
||||
<link rel="stylesheet" type="text/css" href="/styles.css" />
|
||||
<title>Kill the Newsletter!</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>
|
||||
<a href="/">Kill the Newsletter!</a>
|
||||
</h1>
|
||||
<p>Convert email newsletters into Atom feeds</p>
|
||||
<p>
|
||||
<img
|
||||
alt="Convert email newsletters into Atom feeds"
|
||||
src="/logo.png"
|
||||
width="150"
|
||||
/>
|
||||
</p>
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
<footer>
|
||||
<p>
|
||||
By <a href="https://www.leafac.com">Leandro Facchinetti</a> ·{" "}
|
||||
<a href="https://github.com/leafac/www.kill-the-newsletter.com">
|
||||
Source
|
||||
</a>{" "}
|
||||
· <a href={ISSUE_REPORT}>Report an Issue</a>
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
function Form() {
|
||||
return (
|
||||
<form method="POST" action="/">
|
||||
<p>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="Newsletter Name…"
|
||||
maxLength={500}
|
||||
size={30}
|
||||
required
|
||||
/>
|
||||
<button>Create Inbox</button>
|
||||
</p>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function Created({ identifier }: { identifier: string }) {
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
Sign up for the newsletter with
|
||||
<br />
|
||||
<code>{feedEmail(identifier)}</code>
|
||||
</p>
|
||||
<p>
|
||||
Subscribe to the Atom feed at
|
||||
<br />
|
||||
<code>{feedURL(identifier)}</code>
|
||||
</p>
|
||||
<p>
|
||||
Don’t share these addresses.
|
||||
<br />
|
||||
They contain an identifier that other people could use
|
||||
<br />
|
||||
to send you spam and to control your newsletter subscriptions.
|
||||
</p>
|
||||
<p>Enjoy your readings!</p>
|
||||
<p>
|
||||
<a href={BASE_URL}>
|
||||
<strong>Create Another Inbox</strong>
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Feed({ name, identifier }: { name: string; identifier: string }) {
|
||||
return {
|
||||
feed: {
|
||||
"@xmlns": "http://www.w3.org/2005/Atom",
|
||||
link: [
|
||||
{
|
||||
"@rel": "self",
|
||||
"@type": "application/atom+xml",
|
||||
"@href": feedURL(identifier),
|
||||
},
|
||||
{
|
||||
"@rel": "alternate",
|
||||
"@type": "text/html",
|
||||
"@href": `${BASE_URL}/alternate`,
|
||||
},
|
||||
],
|
||||
id: urn(identifier),
|
||||
title: name,
|
||||
subtitle: `Kill the Newsletter! Inbox: ${feedEmail(
|
||||
identifier
|
||||
)} → ${feedURL(identifier)}`,
|
||||
updated: now(),
|
||||
author: { name: "Kill the Newsletter!" },
|
||||
...Entry({
|
||||
title: `“${name}” Inbox Created`,
|
||||
author: "Kill the Newsletter!",
|
||||
content: ReactDOMServer.renderToStaticMarkup(
|
||||
<Created identifier={identifier}></Created>
|
||||
),
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function Entry({
|
||||
title,
|
||||
author,
|
||||
content,
|
||||
}: {
|
||||
title: string;
|
||||
author: string;
|
||||
content: string;
|
||||
}) {
|
||||
return {
|
||||
entry: {
|
||||
id: urn(createIdentifier()),
|
||||
title,
|
||||
author: { name: author },
|
||||
updated: now(),
|
||||
link: {
|
||||
"@rel": "alternate",
|
||||
"@type": "text/html",
|
||||
"@href": `${BASE_URL}/alternate`,
|
||||
},
|
||||
content: { "@type": "html", "#": content },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createIdentifier(): string {
|
||||
return cryptoRandomString({
|
||||
length: 20,
|
||||
characters: "1234567890qwertyuiopasdfghjklzxcvbnm",
|
||||
});
|
||||
}
|
||||
|
||||
function now(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function feedPath(identifier: string): string {
|
||||
return `static/feeds/${identifier}.xml`;
|
||||
}
|
||||
|
||||
function feedURL(identifier: string): string {
|
||||
return `${BASE_URL}/feeds/${identifier}.xml`;
|
||||
}
|
||||
|
||||
function feedEmail(identifier: string): string {
|
||||
return `${identifier}@${EMAIL_DOMAIN}`;
|
||||
}
|
||||
|
||||
function urn(identifier: string): string {
|
||||
return `urn:kill-the-newsletter:${identifier}`;
|
||||
}
|
||||
|
||||
function renderHTML(component: React.ReactElement): string {
|
||||
return `<!DOCTYPE html>\n${ReactDOMServer.renderToStaticMarkup(component)}`;
|
||||
}
|
||||
|
||||
function renderXML(xml: object): string {
|
||||
return xmlbuilder2.convert({ invalidCharReplacement: "" }, xml, {
|
||||
format: "xml",
|
||||
noDoubleEncoding: true,
|
||||
prettyPrint: true,
|
||||
});
|
||||
}
|
||||
|
||||
function parseXML(xml: string): any {
|
||||
return xmlbuilder2.convert({ invalidCharReplacement: "" }, xml, {
|
||||
format: "object",
|
||||
noDoubleEncoding: true,
|
||||
});
|
||||
}
|
30
src/test.ts
|
@ -70,10 +70,10 @@ describe("receive email", () => {
|
|||
from: "publisher@example.com",
|
||||
to: `${identifier}@${EMAIL_DOMAIN}`,
|
||||
subject: "New Message",
|
||||
html: "<p>Invalid XML character (backspace): |\b|</p>",
|
||||
html: "<p>Invalid XML character (backspace): |\b|💩</p>",
|
||||
});
|
||||
const feed = await getFeed(identifier);
|
||||
expect(feed).toMatch("Invalid XML character (backspace): ||");
|
||||
expect(feed).toMatch("Invalid XML character (backspace): ||💩");
|
||||
});
|
||||
|
||||
test("invalid XML character in text", async () => {
|
||||
|
@ -82,10 +82,12 @@ describe("receive email", () => {
|
|||
from: "publisher@example.com",
|
||||
to: `${identifier}@${EMAIL_DOMAIN}`,
|
||||
subject: "New Message",
|
||||
text: "Invalid XML character (backspace): |\b|",
|
||||
text: "Invalid XML character (backspace): |\b|💩",
|
||||
});
|
||||
const feed = await getFeed(identifier);
|
||||
expect(feed).toMatch("Invalid XML character (backspace): |&#x8;|");
|
||||
expect(feed).toMatch(
|
||||
"Invalid XML character (backspace): |&#x8;|&#x1F4A9;"
|
||||
);
|
||||
});
|
||||
|
||||
test("missing content", async () => {
|
||||
|
@ -124,6 +126,26 @@ describe("receive email", () => {
|
|||
expect(feed).not.toMatch("REPETITION 0");
|
||||
});
|
||||
|
||||
test("too big entry", async () => {
|
||||
const identifier = await createFeed();
|
||||
await emailClient.sendMail({
|
||||
from: "publisher@example.com",
|
||||
to: `${identifier}@${EMAIL_DOMAIN}`,
|
||||
subject: "New Message",
|
||||
text: `TOO BIG`.repeat(100_000),
|
||||
});
|
||||
expect(await getFeed(identifier)).not.toMatch("<entry>");
|
||||
await emailClient.sendMail({
|
||||
from: "publisher@example.com",
|
||||
to: `${identifier}@${EMAIL_DOMAIN}`,
|
||||
subject: "New Message",
|
||||
text: `NORMAL SIZE`,
|
||||
});
|
||||
const feed = await getFeed(identifier);
|
||||
expect(feed).toMatch("<entry>");
|
||||
expect(feed).toMatch("NORMAL SIZE");
|
||||
});
|
||||
|
||||
test("nonexistent address", async () => {
|
||||
await emailClient.sendMail({
|
||||
from: "publisher@example.com",
|
||||
|
|
Before Width: | Height: | Size: 356 B After Width: | Height: | Size: 411 B |
Before Width: | Height: | Size: 812 B After Width: | Height: | Size: 869 B |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 6.2 KiB |
BIN
static/logo.key
BIN
static/logo.png
Before Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 6.0 KiB |
|
@ -22,7 +22,7 @@
|
|||
"_resolved": "https://registry.npmjs.org/typeface-pt-mono/-/typeface-pt-mono-0.0.72.tgz",
|
||||
"_shasum": "1ce4a636d335c77bb01712ba136a99f764647a04",
|
||||
"_spec": "typeface-pt-mono",
|
||||
"_where": "/Users/leafac/Projects/www.leafac.com",
|
||||
"_where": "/Users/leafac/Code/www.kill-the-newsletter.com/static",
|
||||
"author": {
|
||||
"name": "Kyle Mathews",
|
||||
"email": "mathews.kyle@gmail.com"
|
|
@ -22,7 +22,7 @@
|
|||
"_resolved": "https://registry.npmjs.org/typeface-pt-sans/-/typeface-pt-sans-0.0.72.tgz",
|
||||
"_shasum": "c47d198510b2ed1272ad6329cf1ae5f782ebf4cf",
|
||||
"_spec": "typeface-pt-sans",
|
||||
"_where": "/Users/leafac/Projects/www.kill-the-newsletter.com/static",
|
||||
"_where": "/Users/leafac/Code/www.kill-the-newsletter.com/static",
|
||||
"author": {
|
||||
"name": "Kyle Mathews",
|
||||
"email": "mathews.kyle@gmail.com"
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"typeface-pt-mono": {
|
||||
"version": "0.0.72",
|
||||
"resolved": "https://registry.npmjs.org/typeface-pt-mono/-/typeface-pt-mono-0.0.72.tgz",
|
||||
"integrity": "sha512-7AMVSvB0oT2TDPyCJlVKSIxPjspI08Rlhg/SEUwQORSyalZgtSt2pDJEvVZuJbBIak70oB2ts6vL4iunYPUGKA=="
|
||||
},
|
||||
"typeface-pt-sans": {
|
||||
"version": "0.0.72",
|
||||
"resolved": "https://registry.npmjs.org/typeface-pt-sans/-/typeface-pt-sans-0.0.72.tgz",
|
||||
"integrity": "sha512-eDMv72dE+0dwjIDltzZAUusF+cDgOfZKazHZK08uKCOG/BEzY6oc/WqtQLN71j5/voTXFi5M4+nXNbZnCayDMQ=="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"typeface-pt-mono": "0.0.72",
|
||||
"typeface-pt-sans": "0.0.72"
|
||||
}
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
@import "node_modules/typeface-pt-sans/index.css";
|
||||
@import "node_modules/typeface-pt-mono/index.css";
|
||||
|
||||
body {
|
||||
font-family: "PT Sans", sans-serif;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
padding: 0 1em;
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
"compilerOptions": {
|
||||
"target": "ES2019",
|
||||
"module": "commonjs",
|
||||
"jsx": "react",
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
|
|