├── .datignore ├── .eslintrc ├── .gitignore ├── .nock ├── dat-gateway --redirect false │ ├── should%20handle%20requests%20for%20%3Agateway%2F%3Adns_key%2F%3Apath.json │ ├── should%20handle%20requests%20for%20%3Agateway%2F%3Akey%2F%3Apath.json │ ├── should%20not%20redirect%20loop%20requests%20for%20%3Agateway%2F%3Akey%2F.json │ ├── should%20redirect%20requests%20for%20%3Agateway%2F%3Adns_key%20to%20%3Agateway%2F%3Adns_key%2F.json │ ├── should%20redirect%20requests%20for%20%3Agateway%2F%3Akey%20to%20%3Agateway%2F%3Akey%2F.json │ ├── should%2520handle%2520requests%2520for%2520%253Agateway%252F%253Adns_key%252F%253Apath.json │ ├── should%2520handle%2520requests%2520for%2520%253Agateway%252F%253Akey%252F%253Apath.json │ ├── should%2520not%2520redirect%2520loop%2520requests%2520for%2520%253Agateway%252F%253Akey%252F.json │ ├── should%2520redirect%2520requests%2520for%2520%253Agateway%252F%253Adns_key%2520to%2520%253Agateway%252F%253Adns_key%252F.json │ └── should%2520redirect%2520requests%2520for%2520%253Agateway%252F%253Akey%2520to%2520%253Agateway%252F%253Akey%252F.json ├── dat-gateway --redirect true │ ├── should%20handle%20requests%20for%20%3Ab32_key.%3Agateway%2F%3Apath.json │ ├── should%20handle%20requests%20for%20%3Adns_key.%3Agateway%2F%3Apath.json │ ├── should%20handle%20requests%20for%20%3Ainvalid_key.%3Agateway%2F.json │ ├── should%20not%20redirect%20requests%20for%20%3Adns_key.%3Agateway%2F%3Apath%20to%20%3Adns_key.%3Agateway%2F%3Apath%2F.json │ ├── should%20redirect%20from%20%3Ab32_key.localhost%20to%20%3Ab32_key.%3Agateway.json │ ├── should%20redirect%20from%20%3Adns_key.localhost%20to%20%3Adns_key.%3Agateway.json │ ├── should%20redirect%20requests%20for%20%3Agateway%2F%3Adns_key%2F%3Apath%20to%20%3Adns_key.%3Agateway%2F%3Apath%2F.json │ ├── should%20redirect%20requests%20for%20%3Agateway%2F%3Akey%2F%3Apath%20to%20%3Ab32_key.%3Agateway%2F%3Apath%2F.json │ ├── should%2520handle%2520requests%2520for%2520%253Ab32_key.%253Agateway%252F%253Apath.json │ ├── should%2520handle%2520requests%2520for%2520%253Adns_key.%253Agateway%252F%253Apath.json │ ├── should%2520handle%2520requests%2520for%2520%253Ainvalid_key.%253Agateway%252F.json │ ├── should%2520not%2520redirect%2520requests%2520for%2520%253Adns_key.%253Agateway%252F%253Apath%2520to%2520%253Adns_key.%253Agateway%252F%253Apath%252F.json │ ├── should%2520redirect%2520from%2520%253Ab32_key.localhost%2520to%2520%253Ab32_key.%253Agateway.json │ ├── should%2520redirect%2520from%2520%253Adns_key.localhost%2520to%2520%253Adns_key.%253Agateway.json │ ├── should%2520redirect%2520requests%2520for%2520%253Agateway%252F%253Adns_key%252F%253Apath%2520to%2520%253Adns_key.%253Agateway%252F%253Apath%252F.json │ └── should%2520redirect%2520requests%2520for%2520%253Agateway%252F%253Akey%252F%253Apath%2520to%2520%253Ab32_key.%253Agateway%252F%253Apath%252F.json └── dat-gateway │ ├── cache%20directory%20should%20exist.json │ ├── cache%2520directory%2520should%2520exist.json │ ├── index%20portal%20should%20exist.json │ ├── index%2520portal%2520should%2520exist.json │ ├── should%20handle%20requests%20for%20%3Agateway%2F%3Ainvalid_key%2F.json │ ├── should%20handle%20requests%20for%20gateway%2F%3Adead_key%2F.json │ ├── should%20handle%20websockets%20for%20replication.json │ ├── should%20proactively%20deleted%20expired%20archives.json │ ├── should%20redirect%20index%20portal%20listening%20on%20loopback%20to%20normalized%20index%20portal%20host.json │ ├── should%2520handle%2520requests%2520for%2520%253Agateway%252F%253Ainvalid_key%252F.json │ ├── should%2520handle%2520requests%2520for%2520gateway%252F%253Adead_key%252F.json │ ├── should%2520handle%2520websockets%2520for%2520replication.json │ ├── should%2520proactively%2520deleted%2520expired%2520archives.json │ └── should%2520redirect%2520index%2520portal%2520listening%2520on%2520loopback%2520to%2520normalized%2520index%2520portal%2520host.json ├── .travis.yml ├── README.md ├── bin.js ├── index.html ├── index.js ├── package.json └── test.js /.datignore: -------------------------------------------------------------------------------- 1 | **/node_modules/** 2 | .nyc_output/ 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .nyc_output/ 4 | .dat/ 5 | .idea/ 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect false/should%20handle%20requests%20for%20%3Agateway%2F%3Adns_key%2F%3Apath.json: -------------------------------------------------------------------------------- 1 | [{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:27 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/","body":"","status":200,"response":["1f8b0800000000000003a493cf729b3010c6ef7e8a0d33b94526d809d324c07426eeb93df4d2a30c0b6c23248fb4fed7731fa06fd057ec235488b8d834b71c80e5fb2ddfee2229bb5a7d7efefaedcb2768b953c52c3b3d5056c50c20eb902594adb40e398fb65c8b0fd108b4ec308f7684fb8db11c416934a3f6897baab8cd2bdc518922bcdc006962924ab8522acc931b70ad25fd22d8889a38d7c61bf7ce5742805c9b2d8310837256ab42575ada30197d56eed9ec1d488bc02d8223e545f8beb5475f33486c7d6130357452bf90aee6ffcd20b7dc1a7b66b9821569035923ed5a56c67d6c3a496a5e9aae38ebb3967e42a3ddbf56959f082caa3ceaf5085a8bf510bbf83579ee6f515cbc95cdc78def853ad960bcd1cd9b9f8b243d24e93c60473fd0e55150de63b95c1c968b0bcba0bcc7f2213d3c5c761994deb2f764628545674c160fe12c8b874d97ad4d750c752bda81e3a3df2cc37e7a84e4f6f6fa29acdd25663cb0908a1afd08a55f3eb4a72c9fd726c59fdfbf7e9e2e5f2719d962ca16235b4ed9726477537637b2fb29bb1f593a65e9eb34b11f274c3d04593cfc069fd19fc8bf000000ffff","03009bf69017a8030000"],"rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:28 GMT","Content-Type","text/html","Connection","close","Vary","Accept-Encoding","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","99","X-RateLimit-Reset","1574816676","Access-Control-Allow-Origin","*","Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept","Cache-Control","public, max-age: 60","ETag","W/block-9","Content-Encoding","gzip"],"responseIsBinary":false},{"scope":"https://cloudflare-dns.com:443","method":"GET","path":"/dns-query?name=garbados.hashbase.io.&type=TXT","body":"","status":200,"response":{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"garbados.hashbase.io.","type":16}],"Authority":[{"name":"hashbase.io.","type":6,"TTL":3601,"data":"dns1.registrar-servers.com. hostmaster.registrar-servers.com. 2019090905 43200 3600 604800 3601"}]},"rawHeaders":["Date","Wed, 27 Nov 2019 01:04:28 GMT","Content-Type","application/dns-json","Content-Length","302","Connection","close","Access-Control-Allow-Origin","*","cache-control","max-age=3601","Expect-CT","max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"","Server","cloudflare","CF-RAY","53c030300ceb8cf1-PDX"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/.well-known/dat","body":"","status":200,"response":"dat://c33bc8d7c32a6e905905efdbf21efea9ff23b00d1c3ee9aea80092eaba6c4957/\nTTL=3600","rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:28 GMT","Content-Length","80","Connection","close","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","98","X-RateLimit-Reset","1574816676"],"responseIsBinary":false},{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:28 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/","body":"","status":200,"response":["1f8b0800000000000003a493cf729b3010c6ef7e8a0d33b94526d809d324c07426eeb93df4d2a30c0b6c23248fb4fed7731fa06fd057ec235488b8d834b71c80e5fb2ddfee2229bb5a7d7efefaedcb2768b953c52c3b3d5056c50c20eb902594adb40e398fb65c8b0fd108b4ec308f7684fb8db11c416934a3f6897baab8cd2bdc518922bcdc006962924ab8522acc931b70ad25fd22d8889a38d7c61bf7ce5742805c9b2d8310837256ab42575ada30197d56eed9ec1d488bc02d8223e545f8beb5475f33486c7d6130357452bf90aee6ffcd20b7dc1a7b66b9821569035923ed5a56c67d6c3a496a5e9aae38ebb3967e42a3ddbf56959f082caa3ceaf5085a8bf510bbf83579ee6f515cbc95cdc78def853ad960bcd1cd9b9f8b243d24e93c60473fd0e55150de63b95c1c968b0bcba0bcc7f2213d3c5c761994deb2f764628545674c160fe12c8b874d97ad4d750c752bda81e3a3df2cc37e7a84e4f6f6fa29acdd25663cb0908a1afd08a55f3eb4a72c9fd726c59fdfbf7e9e2e5f2719d962ca16235b4ed9726477537637b2fb29bb1f593a65e9eb34b11f274c3d04593cfc069fd19fc8bf000000ffff","03009bf69017a8030000"],"rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:28 GMT","Content-Type","text/html","Connection","close","Vary","Accept-Encoding","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","97","X-RateLimit-Reset","1574816676","Access-Control-Allow-Origin","*","Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept","Cache-Control","public, max-age: 60","ETag","W/block-9","Content-Encoding","gzip"],"responseIsBinary":false}] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect false/should%20handle%20requests%20for%20%3Agateway%2F%3Akey%2F%3Apath.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect false/should%20not%20redirect%20loop%20requests%20for%20%3Agateway%2F%3Akey%2F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect false/should%20redirect%20requests%20for%20%3Agateway%2F%3Adns_key%20to%20%3Agateway%2F%3Adns_key%2F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect false/should%20redirect%20requests%20for%20%3Agateway%2F%3Akey%20to%20%3Agateway%2F%3Akey%2F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect false/should%2520handle%2520requests%2520for%2520%253Agateway%252F%253Adns_key%252F%253Apath.json: -------------------------------------------------------------------------------- 1 | [{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:52:57 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/","body":"","status":200,"response":["1f8b0800000000000003a493cf729b3010c6ef7e8a0d33b94526d809d324c07426eeb93df4d2a30c0b6c23248fb4fed7731fa06fd057ec235488b8d834b71c80e5fb2ddfee2229bb5a7d7efefaedcb2768b953c52c3b3d5056c50c20eb902594adb40e398fb65c8b0fd108b4ec308f7684fb8db11c416934a3f6897baab8cd2bdc518922bcdc006962924ab8522acc931b70ad25fd22d8889a38d7c61bf7ce5742805c9b2d8310837256ab42575ada30197d56eed9ec1d488bc02d8223e545f8beb5475f33486c7d6130357452bf90aee6ffcd20b7dc1a7b66b9821569035923ed5a56c67d6c3a496a5e9aae38ebb3967e42a3ddbf56959f082caa3ceaf5085a8bf510bbf83579ee6f515cbc95cdc78def853ad960bcd1cd9b9f8b243d24e93c60473fd0e55150de63b95c1c968b0bcba0bcc7f2213d3c5c761994deb2f764628545674c160fe12c8b874d97ad4d750c752bda81e3a3df2cc37e7a84e4f6f6fa29acdd25663cb0908a1afd08a55f3eb4a72c9fd726c59fdfbf7e9e2e5f2719d962ca16235b4ed9726477537637b2fb29bb1f593a65e9eb34b11f274c3d04593cfc069fd19fc8bf000000ffff","03009bf69017a8030000"],"rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:52:58 GMT","Content-Type","text/html","Connection","close","Vary","Accept-Encoding","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","99","X-RateLimit-Reset","1574661187","Access-Control-Allow-Origin","*","Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept","Cache-Control","public, max-age: 60","ETag","W/block-9","Content-Encoding","gzip"],"responseIsBinary":false},{"scope":"https://dns.google.com:443","method":"GET","path":"/resolve?name=garbados.hashbase.io.&type=TXT","body":"","status":200,"response":{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"garbados.hashbase.io.","type":16}],"Authority":[{"name":"hashbase.io.","type":6,"TTL":1799,"data":"dns1.registrar-servers.com. hostmaster.registrar-servers.com. 2019090905 43200 3600 604800 3601"}],"Comment":"Response from 2001:502:cbe4::33."},"rawHeaders":["Strict-Transport-Security","max-age=31536000; includeSubDomains; preload","Access-Control-Allow-Origin","*","Date","Mon, 25 Nov 2019 05:52:58 GMT","Expires","Mon, 25 Nov 2019 05:52:58 GMT","Cache-Control","private, max-age=1799","Content-Type","application/x-javascript; charset=UTF-8","Server","HTTP server (unknown)","X-XSS-Protection","0","X-Frame-Options","SAMEORIGIN","Alt-Svc","quic=\":443\"; ma=2592000; v=\"46,43\",h3-Q050=\":443\"; ma=2592000,h3-Q049=\":443\"; ma=2592000,h3-Q048=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000","Accept-Ranges","none","Vary","Accept-Encoding","Connection","close"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/.well-known/dat","body":"","status":200,"response":"dat://c33bc8d7c32a6e905905efdbf21efea9ff23b00d1c3ee9aea80092eaba6c4957/\nTTL=3600","rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:52:58 GMT","Content-Length","80","Connection","close","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","98","X-RateLimit-Reset","1574661187"],"responseIsBinary":false},{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:52:58 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/","body":"","status":200,"response":["1f8b0800000000000003a493cf729b3010c6ef7e8a0d33b94526d809d324c07426eeb93df4d2a30c0b6c23248fb4fed7731fa06fd057ec235488b8d834b71c80e5fb2ddfee2229bb5a7d7efefaedcb2768b953c52c3b3d5056c50c20eb902594adb40e398fb65c8b0fd108b4ec308f7684fb8db11c416934a3f6897baab8cd2bdc518922bcdc006962924ab8522acc931b70ad25fd22d8889a38d7c61bf7ce5742805c9b2d8310837256ab42575ada30197d56eed9ec1d488bc02d8223e545f8beb5475f33486c7d6130357452bf90aee6ffcd20b7dc1a7b66b9821569035923ed5a56c67d6c3a496a5e9aae38ebb3967e42a3ddbf56959f082caa3ceaf5085a8bf510bbf83579ee6f515cbc95cdc78def853ad960bcd1cd9b9f8b243d24e93c60473fd0e55150de63b95c1c968b0bcba0bcc7f2213d3c5c761994deb2f764628545674c160fe12c8b874d97ad4d750c752bda81e3a3df2cc37e7a84e4f6f6fa29acdd25663cb0908a1afd08a55f3eb4a72c9fd726c59fdfbf7e9e2e5f2719d962ca16235b4ed9726477537637b2fb29bb1f593a65e9eb34b11f274c3d04593cfc069fd19fc8bf000000ffff","03009bf69017a8030000"],"rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:52:58 GMT","Content-Type","text/html","Connection","close","Vary","Accept-Encoding","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","97","X-RateLimit-Reset","1574661187","Access-Control-Allow-Origin","*","Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept","Cache-Control","public, max-age: 60","ETag","W/block-9","Content-Encoding","gzip"],"responseIsBinary":false}] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect false/should%2520handle%2520requests%2520for%2520%253Agateway%252F%253Akey%252F%253Apath.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect false/should%2520not%2520redirect%2520loop%2520requests%2520for%2520%253Agateway%252F%253Akey%252F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect false/should%2520redirect%2520requests%2520for%2520%253Agateway%252F%253Adns_key%2520to%2520%253Agateway%252F%253Adns_key%252F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect false/should%2520redirect%2520requests%2520for%2520%253Agateway%252F%253Akey%2520to%2520%253Agateway%252F%253Akey%252F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%20handle%20requests%20for%20%3Ab32_key.%3Agateway%2F%3Apath.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%20handle%20requests%20for%20%3Adns_key.%3Agateway%2F%3Apath.json: -------------------------------------------------------------------------------- 1 | [{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:33 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/","body":"","status":200,"response":["1f8b0800000000000003a493cf729b3010c6ef7e8a0d33b94526d809d324c07426eeb93df4d2a30c0b6c23248fb4fed7731fa06fd057ec235488b8d834b71c80e5fb2ddfee2229bb5a7d7efefaedcb2768b953c52c3b3d5056c50c20eb902594adb40e398fb65c8b0fd108b4ec308f7684fb8db11c416934a3f6897baab8cd2bdc518922bcdc006962924ab8522acc931b70ad25fd22d8889a38d7c61bf7ce5742805c9b2d8310837256ab42575ada30197d56eed9ec1d488bc02d8223e545f8beb5475f33486c7d6130357452bf90aee6ffcd20b7dc1a7b66b9821569035923ed5a56c67d6c3a496a5e9aae38ebb3967e42a3ddbf56959f082caa3ceaf5085a8bf510bbf83579ee6f515cbc95cdc78def853ad960bcd1cd9b9f8b243d24e93c60473fd0e55150de63b95c1c968b0bcba0bcc7f2213d3c5c761994deb2f764628545674c160fe12c8b874d97ad4d750c752bda81e3a3df2cc37e7a84e4f6f6fa29acdd25663cb0908a1afd08a55f3eb4a72c9fd726c59fdfbf7e9e2e5f2719d962ca16235b4ed9726477537637b2fb29bb1f593a65e9eb34b11f274c3d04593cfc069fd19fc8bf000000ffff","03009bf69017a8030000"],"rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:33 GMT","Content-Type","text/html","Connection","close","Vary","Accept-Encoding","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","93","X-RateLimit-Reset","1574816676","Access-Control-Allow-Origin","*","Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept","Cache-Control","public, max-age: 60","ETag","W/block-9","Content-Encoding","gzip"],"responseIsBinary":false}] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%20handle%20requests%20for%20%3Ainvalid_key.%3Agateway%2F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%20not%20redirect%20requests%20for%20%3Adns_key.%3Agateway%2F%3Apath%20to%20%3Adns_key.%3Agateway%2F%3Apath%2F.json: -------------------------------------------------------------------------------- 1 | [{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:30 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/","body":"","status":200,"response":["1f8b0800000000000003a493cf729b3010c6ef7e8a0d33b94526d809d324c07426eeb93df4d2a30c0b6c23248fb4fed7731fa06fd057ec235488b8d834b71c80e5fb2ddfee2229bb5a7d7efefaedcb2768b953c52c3b3d5056c50c20eb902594adb40e398fb65c8b0fd108b4ec308f7684fb8db11c416934a3f6897baab8cd2bdc518922bcdc006962924ab8522acc931b70ad25fd22d8889a38d7c61bf7ce5742805c9b2d8310837256ab42575ada30197d56eed9ec1d488bc02d8223e545f8beb5475f33486c7d6130357452bf90aee6ffcd20b7dc1a7b66b9821569035923ed5a56c67d6c3a496a5e9aae38ebb3967e42a3ddbf56959f082caa3ceaf5085a8bf510bbf83579ee6f515cbc95cdc78def853ad960bcd1cd9b9f8b243d24e93c60473fd0e55150de63b95c1c968b0bcba0bcc7f2213d3c5c761994deb2f764628545674c160fe12c8b874d97ad4d750c752bda81e3a3df2cc37e7a84e4f6f6fa29acdd25663cb0908a1afd08a55f3eb4a72c9fd726c59fdfbf7e9e2e5f2719d962ca16235b4ed9726477537637b2fb29bb1f593a65e9eb34b11f274c3d04593cfc069fd19fc8bf000000ffff","03009bf69017a8030000"],"rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:31 GMT","Content-Type","text/html","Connection","close","Vary","Accept-Encoding","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","95","X-RateLimit-Reset","1574816676","Access-Control-Allow-Origin","*","Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept","Cache-Control","public, max-age: 60","ETag","W/block-9","Content-Encoding","gzip"],"responseIsBinary":false},{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:31 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/","body":"","status":200,"response":["1f8b0800000000000003a493cf729b3010c6ef7e8a0d33b94526d809d324c07426eeb93df4d2a30c0b6c23248fb4fed7731fa06fd057ec235488b8d834b71c80e5fb2ddfee2229bb5a7d7efefaedcb2768b953c52c3b3d5056c50c20eb902594adb40e398fb65c8b0fd108b4ec308f7684fb8db11c416934a3f6897baab8cd2bdc518922bcdc006962924ab8522acc931b70ad25fd22d8889a38d7c61bf7ce5742805c9b2d8310837256ab42575ada30197d56eed9ec1d488bc02d8223e545f8beb5475f33486c7d6130357452bf90aee6ffcd20b7dc1a7b66b9821569035923ed5a56c67d6c3a496a5e9aae38ebb3967e42a3ddbf56959f082caa3ceaf5085a8bf510bbf83579ee6f515cbc95cdc78def853ad960bcd1cd9b9f8b243d24e93c60473fd0e55150de63b95c1c968b0bcba0bcc7f2213d3c5c761994deb2f764628545674c160fe12c8b874d97ad4d750c752bda81e3a3df2cc37e7a84e4f6f6fa29acdd25663cb0908a1afd08a55f3eb4a72c9fd726c59fdfbf7e9e2e5f2719d962ca16235b4ed9726477537637b2fb29bb1f593a65e9eb34b11f274c3d04593cfc069fd19fc8bf000000ffff","03009bf69017a8030000"],"rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:31 GMT","Content-Type","text/html","Connection","close","Vary","Accept-Encoding","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","94","X-RateLimit-Reset","1574816676","Access-Control-Allow-Origin","*","Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept","Cache-Control","public, max-age: 60","ETag","W/block-9","Content-Encoding","gzip"],"responseIsBinary":false}] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%20redirect%20from%20%3Ab32_key.localhost%20to%20%3Ab32_key.%3Agateway.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%20redirect%20from%20%3Adns_key.localhost%20to%20%3Adns_key.%3Agateway.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%20redirect%20requests%20for%20%3Agateway%2F%3Adns_key%2F%3Apath%20to%20%3Adns_key.%3Agateway%2F%3Apath%2F.json: -------------------------------------------------------------------------------- 1 | [{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Wed, 27 Nov 2019 01:04:30 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false}] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%20redirect%20requests%20for%20%3Agateway%2F%3Akey%2F%3Apath%20to%20%3Ab32_key.%3Agateway%2F%3Apath%2F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%2520handle%2520requests%2520for%2520%253Ab32_key.%253Agateway%252F%253Apath.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%2520handle%2520requests%2520for%2520%253Adns_key.%253Agateway%252F%253Apath.json: -------------------------------------------------------------------------------- 1 | [{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:53:03 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false}] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%2520handle%2520requests%2520for%2520%253Ainvalid_key.%253Agateway%252F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%2520not%2520redirect%2520requests%2520for%2520%253Adns_key.%253Agateway%252F%253Apath%2520to%2520%253Adns_key.%253Agateway%252F%253Apath%252F.json: -------------------------------------------------------------------------------- 1 | [{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:53:00 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/","body":"","status":200,"response":["1f8b0800000000000003a493cf729b3010c6ef7e8a0d33b94526d809d324c07426eeb93df4d2a30c0b6c23248fb4fed7731fa06fd057ec235488b8d834b71c80e5fb2ddfee2229bb5a7d7efefaedcb2768b953c52c3b3d5056c50c20eb902594adb40e398fb65c8b0fd108b4ec308f7684fb8db11c416934a3f6897baab8cd2bdc518922bcdc006962924ab8522acc931b70ad25fd22d8889a38d7c61bf7ce5742805c9b2d8310837256ab42575ada30197d56eed9ec1d488bc02d8223e545f8beb5475f33486c7d6130357452bf90aee6ffcd20b7dc1a7b66b9821569035923ed5a56c67d6c3a496a5e9aae38ebb3967e42a3ddbf56959f082caa3ceaf5085a8bf510bbf83579ee6f515cbc95cdc78def853ad960bcd1cd9b9f8b243d24e93c60473fd0e55150de63b95c1c968b0bcba0bcc7f2213d3c5c761994deb2f764628545674c160fe12c8b874d97ad4d750c752bda81e3a3df2cc37e7a84e4f6f6fa29acdd25663cb0908a1afd08a55f3eb4a72c9fd726c59fdfbf7e9e2e5f2719d962ca16235b4ed9726477537637b2fb29bb1f593a65e9eb34b11f274c3d04593cfc069fd19fc8bf000000ffff","03009bf69017a8030000"],"rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:53:00 GMT","Content-Type","text/html","Connection","close","Vary","Accept-Encoding","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","95","X-RateLimit-Reset","1574661187","Access-Control-Allow-Origin","*","Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept","Cache-Control","public, max-age: 60","ETag","W/block-9","Content-Encoding","gzip"],"responseIsBinary":false},{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:53:01 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false},{"scope":"https://garbados.hashbase.io:443","method":"GET","path":"/","body":"","status":200,"response":["1f8b0800000000000003a493cf729b3010c6ef7e8a0d33b94526d809d324c07426eeb93df4d2a30c0b6c23248fb4fed7731fa06fd057ec235488b8d834b71c80e5fb2ddfee2229bb5a7d7efefaedcb2768b953c52c3b3d5056c50c20eb902594adb40e398fb65c8b0fd108b4ec308f7684fb8db11c416934a3f6897baab8cd2bdc518922bcdc006962924ab8522acc931b70ad25fd22d8889a38d7c61bf7ce5742805c9b2d8310837256ab42575ada30197d56eed9ec1d488bc02d8223e545f8beb5475f33486c7d6130357452bf90aee6ffcd20b7dc1a7b66b9821569035923ed5a56c67d6c3a496a5e9aae38ebb3967e42a3ddbf56959f082caa3ceaf5085a8bf510bbf83579ee6f515cbc95cdc78def853ad960bcd1cd9b9f8b243d24e93c60473fd0e55150de63b95c1c968b0bcba0bcc7f2213d3c5c761994deb2f764628545674c160fe12c8b874d97ad4d750c752bda81e3a3df2cc37e7a84e4f6f6fa29acdd25663cb0908a1afd08a55f3eb4a72c9fd726c59fdfbf7e9e2e5f2719d962ca16235b4ed9726477537637b2fb29bb1f593a65e9eb34b11f274c3d04593cfc069fd19fc8bf000000ffff","03009bf69017a8030000"],"rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:53:01 GMT","Content-Type","text/html","Connection","close","Vary","Accept-Encoding","X-Powered-By","Express","X-RateLimit-Limit","100","X-RateLimit-Remaining","94","X-RateLimit-Reset","1574661187","Access-Control-Allow-Origin","*","Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept","Cache-Control","public, max-age: 60","ETag","W/block-9","Content-Encoding","gzip"],"responseIsBinary":false}] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%2520redirect%2520from%2520%253Ab32_key.localhost%2520to%2520%253Ab32_key.%253Agateway.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%2520redirect%2520from%2520%253Adns_key.localhost%2520to%2520%253Adns_key.%253Agateway.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%2520redirect%2520requests%2520for%2520%253Agateway%252F%253Adns_key%252F%253Apath%2520to%2520%253Adns_key.%253Agateway%252F%253Apath%252F.json: -------------------------------------------------------------------------------- 1 | [{"scope":"http://garbados.hashbase.io:80","method":"GET","path":"/","body":"","status":301,"response":"\r\n301 Moved Permanently\r\n\r\n

301 Moved Permanently

\r\n
nginx/1.14.0
\r\n\r\n\r\n","rawHeaders":["Server","nginx/1.14.0","Date","Mon, 25 Nov 2019 05:53:00 GMT","Content-Type","text/html","Content-Length","185","Connection","close","Location","https://garbados.hashbase.io/"],"responseIsBinary":false}] -------------------------------------------------------------------------------- /.nock/dat-gateway --redirect true/should%2520redirect%2520requests%2520for%2520%253Agateway%252F%253Akey%252F%253Apath%2520to%2520%253Ab32_key.%253Agateway%252F%253Apath%252F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/cache%20directory%20should%20exist.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/cache%2520directory%2520should%2520exist.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/index%20portal%20should%20exist.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/index%2520portal%2520should%2520exist.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/should%20handle%20requests%20for%20%3Agateway%2F%3Ainvalid_key%2F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/should%20handle%20requests%20for%20gateway%2F%3Adead_key%2F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/should%20handle%20websockets%20for%20replication.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/should%20proactively%20deleted%20expired%20archives.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/should%20redirect%20index%20portal%20listening%20on%20loopback%20to%20normalized%20index%20portal%20host.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/should%2520handle%2520requests%2520for%2520%253Agateway%252F%253Ainvalid_key%252F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/should%2520handle%2520requests%2520for%2520gateway%252F%253Adead_key%252F.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/should%2520handle%2520websockets%2520for%2520replication.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/should%2520proactively%2520deleted%2520expired%2520archives.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.nock/dat-gateway/should%2520redirect%2520index%2520portal%2520listening%2520on%2520loopback%2520to%2520normalized%2520index%2520portal%2520host.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | - 10 5 | - 8 6 | after_success: 7 | - npm i -g nyc coveralls 8 | - nyc npm test && nyc report --reporter=text-lcov | coveralls 9 | addons: 10 | hosts: 11 | - dat.localhost 12 | - whoop-whoop-test.dat.localhost 13 | - garbados.hashbase.io.localhost 14 | - garbados.hashbase.io.dat.localhost 15 | - ym54rv6dfjxjawif57n7ehx6vh7shmandq7otlviacjovotmjflq.localhost 16 | - ym54rv6dfjxjawif57n7ehx6vh7shmandq7otlviacjovotmjflq.dat.localhost 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dat-gateway 2 | 3 | [![Stability](https://img.shields.io/badge/stability-experimental-orange.svg)](https://nodejs.org/api/documentation.html#documentation_stability_index) 4 | [![NPM Version](https://img.shields.io/npm/v/dat-gateway.svg)](https://www.npmjs.com/package/dat-gateway) 5 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | [![Build Status](https://travis-ci.org/garbados/dat-gateway.svg?branch=master)](https://travis-ci.org/garbados/dat-gateway) 7 | [![Coverage Status](https://img.shields.io/coveralls/github/garbados/dat-gateway/master.svg)](https://coveralls.io/github/garbados/dat-gateway?branch=master) 8 | 9 | A configurable in-memory [Dat](https://datproject.org/)-to-HTTP gateway, so you can visit Dat archives from your browser. 10 | 11 | If you want a browser that can visit Dat archives, check out [Beaker](https://beakerbrowser.com/). 12 | 13 | ## Install 14 | 15 | To get the `dat-gateway` command for running your own gateway, use [`npm`](https://www.npmjs.com/): 16 | 17 | ``` 18 | npm i -g dat-gateway 19 | ``` 20 | 21 | If you have [`npx`](https://github.com/zkat/npx) installed, it's even shorter: 22 | 23 | ``` 24 | npx dat-gateway 25 | ``` 26 | 27 | ## Public Gateways: 28 | 29 | - http://gateway.mauve.moe:3000/ (Hosted by @RangerMauve) 30 | - https://pamphlets.me/ (Hosted by @brechtcs) 31 | - https://dat.bovid.space/ (Original gateway from @garbados) 32 | - https://dat.hypersource.club (Hosted by @jwerle) 33 | 34 | ## Usage 35 | 36 | You can run `dat-gateway` to start a gateway server that listens on port 3000. You can also configure it! You can print usage information with `dat-gateway -h`: 37 | 38 | ``` 39 | $ dat-gateway -h 40 | dat-gateway 41 | 42 | Options: 43 | --version Show version number [boolean] 44 | --config Path to JSON config file 45 | --host, -l Host or ip for the gateway to listen on. [default: "0.0.0.0"] 46 | --port, -p Port for the gateway to listen on. [default: 3000] 47 | --dat-port, -P Port for Dat to listen on. Defaults to Dat's internal 48 | defaults. [default: null] 49 | --dir, -d Directory to use as a cache. 50 | [string] [default: "~/.cache/dat-gateway"] 51 | --max, -m Maximum number of archives allowed in the cache. [default: 20] 52 | --period Number of milliseconds between cleaning the cache of expired 53 | archives. [default: 60000] 54 | --ttl, -t Number of milliseconds before archives expire. 55 | [default: 600000] 56 | --redirect, -r Whether to use subdomain redirects [default: false] 57 | --loopback, -L What hostname to use when serving locally. 58 | [default: "dat.localhost"] 59 | -h, --help Show help [boolean] 60 | ``` 61 | 62 | You can visit Dat archives through the gateway using a route like this: 63 | 64 | ``` 65 | http://localhost:3000/{datKey}/{path...} 66 | ``` 67 | 68 | For example: 69 | 70 | ``` 71 | http://localhost:3000/40a7f6b6147ae695bcbcff432f684c7bb5291ea339c28c1755896cdeb80bd2f9/assets/img/beaker-0.7.gif 72 | ``` 73 | 74 | The gateway will even resolve URLs using [Dat-DNS](https://github.com/beakerbrowser/beaker/wiki/Authenticated-Dat-URLs-and-HTTPS-to-Dat-Discovery): 75 | 76 | ``` 77 | http://localhost:3000/garbados.hashbase.io/icons/favicon.ico 78 | ``` 79 | 80 | The gateway will peer archives until they expire from the cache, at which point it proactively halts them and deletes them from disk. 81 | 82 | The gateway also supports replicating a hyperdrive instance using [websockets](https://github.com/maxogden/websocket-stream) 83 | 84 | ```javascript 85 | const websocket = require('websocket-stream') 86 | const hyperdrive = require('hyperdrive') 87 | 88 | const key = 'c33bc8d7c32a6e905905efdbf21efea9ff23b00d1c3ee9aea80092eaba6c4957' 89 | const url = `ws://localhost:3000/${key}` 90 | 91 | const archive = hyperdrive('./somewhere', key) 92 | 93 | archive.once('ready', () => { 94 | const socket = websocket(url) 95 | 96 | // Replicate through the socket 97 | socket.pipe(archive.replicate()).pipe(socket) 98 | }) 99 | ``` 100 | 101 | ## Subdomain redirection 102 | 103 | By default dat-gateway will serve all dats from the same origin. This means that dats using absolute URLs (starting with `/`) will be broken. 104 | This also means that all dats will share the same localStorage and indexedDB instances which can cause security issues. 105 | 106 | In order to resolve these issues, you can use the `--redirect` flag in conjunction with the `host` parameter to have each dat served on a subdomain. 107 | 108 | For example, `http://{host}:{port}/{datkey}/index.html` will be redirected to `http://{datkey32}.{host}:{port}/index.html` which will serve the file from localhost, but at a different domain, ensuring the browser isolates all the contents from each other. 109 | 110 | Please note that due to limitations in how URLs work, the dat key will be converted to it's base32 representation instead of hexadecimal using [this library](https://github.com/RangerMauve/hex-to-32) 111 | 112 | ## Serving on localhost 113 | 114 | Running a gateway locally for personal use is a great idea, but by default dat-gateway uses `dat.localhost` as its hostname when serving to the local machine. Firefox [does not support '\*.localhost' domains](https://bugzilla.mozilla.org/show_bug.cgi?id=1433933) and so this behavior breaks the gateway for Firefox users. 115 | 116 | To fix this, use the `-L, --loopback` flag to specify `localhost` as the loopback hostname, like so: 117 | 118 | ``` 119 | $ dat-gateway -L localhost 120 | ``` 121 | 122 | This will cause dat-gateway to use `localhost` as its domain name, which Firefox supports just fine. 123 | 124 | ## Contributions 125 | 126 | All contributions are welcome: bug reports, feature requests, "why doesn't this work" questions, patches for fixes and features, etc. For all of the above, [file an issue](https://github.com/garbados/dat-gateway/issues) or [submit a pull request](https://github.com/garbados/dat-gateway/pulls). 127 | 128 | ## License 129 | 130 | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) 131 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | const DatGateway = require('.') 6 | const os = require('os') 7 | const path = require('path') 8 | const cacheDir = require('xdg-basedir').cache 9 | const mkdirp = require('mkdirp') 10 | const pkg = require('./package.json') 11 | 12 | require('yargs') 13 | .version(pkg.version) 14 | .command({ 15 | command: '$0', 16 | aliases: ['start'], 17 | builder: function (yargs) { 18 | yargs.options({ 19 | host: { 20 | alias: 'l', 21 | description: 'Host or ip for the gateway to listen on.', 22 | default: '0.0.0.0' 23 | }, 24 | port: { 25 | alias: 'p', 26 | description: 'Port for the gateway to listen on.', 27 | default: 3000 28 | }, 29 | 'dat-port': { 30 | alias: 'P', 31 | description: 'Port for Dat to listen on. Defaults to Dat\'s internal defaults.', 32 | default: null 33 | }, 34 | dir: { 35 | alias: 'd', 36 | description: 'Directory to use as a cache.', 37 | coerce: function (value) { 38 | return value.replace('~', os.homedir()) 39 | }, 40 | default: path.join(cacheDir, 'dat-gateway'), 41 | normalize: true 42 | }, 43 | max: { 44 | alias: 'm', 45 | description: 'Maximum number of archives allowed in the cache.', 46 | default: 20 47 | }, 48 | period: { 49 | description: 'Number of milliseconds between cleaning the cache of expired archives.', 50 | default: 60 * 1000 // every minute 51 | }, 52 | ttl: { 53 | alias: 't', 54 | description: 'Number of milliseconds before archives expire.', 55 | default: 10 * 60 * 1000 // ten minutes 56 | }, 57 | redirect: { 58 | alias: 'r', 59 | description: 'Whether to use subdomain redirects', 60 | default: false 61 | }, 62 | loopback: { 63 | alias: 'L', 64 | description: 'What hostname to use when serving locally.', 65 | default: 'dat.localhost' 66 | }, 67 | domain: { 68 | alias: 'D', 69 | description: 'Domain name of the gateway. Used to support wildcard subdomains.', 70 | require: true 71 | } 72 | }) 73 | }, 74 | handler: function (argv) { 75 | const { domain, host, port, dir, 'dat-port': datPort, ...gatewayOpts } = argv 76 | mkdirp.sync(dir) // make sure it exists 77 | if (datPort) { 78 | gatewayOpts.dat = { port: datPort } 79 | } 80 | const gateway = new DatGateway({ domain, dir, ...gatewayOpts }) 81 | gateway 82 | .load() 83 | .then(() => { 84 | return gateway.listen(port) 85 | }) 86 | .then(function () { 87 | console.log('[dat-gateway] Now listening on ' + host + ':' + port) 88 | }) 89 | .catch(console.error) 90 | } 91 | }) 92 | .alias('h', 'help') 93 | .config() 94 | .parse() 95 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dat-gateway 5 | 6 | 7 | 42 | 43 | 44 |
45 |
46 |

dat-gateway

47 |

portal to a decentralized web

48 |
49 |

You can use this gateway to visit content hosted over the p2p Dat protocol.

50 |

So why not visit something?

51 |

Enter Dat keys in the URL like this: /:key/:path or use the form below.

52 |

53 |

54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 |
63 |

64 |

65 | 66 | View the source. 67 | 68 |

69 |
70 |
71 |
72 | 73 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const DatLibrarian = require('dat-librarian') 4 | const fs = require('fs') 5 | const hexTo32 = require('hex-to-32') 6 | const http = require('http') 7 | const hyperdriveHttp = require('hyperdrive-http') 8 | const path = require('path') 9 | const Websocket = require('websocket-stream') 10 | const { URL } = require('url') 11 | 12 | const DAT_LOCALHOST_NAME = 'dat.localhost' 13 | const IS_LOCALHOST_REGEX = /(localhost|\[?::1\]?|127(\.[0-9]{1,3}){3})$/ 14 | const BASE_32_KEY_LENGTH = 52 15 | const ERR_404 = 'Not found' 16 | const ERR_500 = 'Server error' 17 | 18 | function log () { 19 | const msg = arguments[0] 20 | arguments[0] = '[dat-gateway] ' + msg 21 | if (process.env.DEBUG || process.env.LOG) { 22 | console.log.apply(console, arguments) 23 | } 24 | } 25 | 26 | module.exports = 27 | class DatGateway extends DatLibrarian { 28 | constructor ({ dir, dat, loopback, max, net, period, ttl, redirect, domain }) { 29 | dat = dat || {} 30 | if (typeof dat.sparse === 'undefined') { 31 | dat.sparse = dat.sparse || true // only download files requested by the user 32 | } 33 | if (typeof dat.temp === 'undefined') { 34 | dat.temp = dat.temp || true // store dats in memory only 35 | } 36 | log('Creating new gateway with options: %j', { dir, dat, max, net, period, ttl }) 37 | super({ dir, dat, net }) 38 | this.domain = domain 39 | this.domainRegex = new RegExp(`.+.${this.domain}$`) 40 | this.loopback = loopback || DAT_LOCALHOST_NAME 41 | this.max = max 42 | this.period = period 43 | this.redirect = redirect 44 | this.ttl = ttl 45 | this.lru = {} 46 | if (this.ttl && this.period) { 47 | this.cleaner = setInterval(() => { 48 | log('Checking for expired archives...') 49 | const tasks = Object.keys(this.dats).filter((key) => { 50 | const now = Date.now() 51 | const lastRead = this.lru[key] 52 | const isExpired = (lastRead && ((now - lastRead) > this.ttl)) 53 | log('Archive %s expired? %s', key, isExpired) 54 | return isExpired 55 | }).map((key) => { 56 | log('Deleting expired archive %s', key) 57 | delete this.lru[key] 58 | return this.remove(key) 59 | }) 60 | return Promise.all(tasks) 61 | }, this.period) 62 | } 63 | } 64 | 65 | load () { 66 | log('Setting up...') 67 | return this.getHandler().then((handler) => { 68 | log('Setting up server...') 69 | this.server = http.createServer(handler) 70 | const websocketHandler = this.getWebsocketHandler() 71 | this.websocketServer = Websocket.createServer({ 72 | perMessageDeflate: false, 73 | server: this.server 74 | }, websocketHandler) 75 | }).then(() => { 76 | log('Loading pre-existing archives...') 77 | // load pre-existing archives 78 | return super.load() 79 | }) 80 | } 81 | 82 | /** 83 | * Promisification of server.listen() 84 | * @param {Number} port Port to listen on. 85 | * @return {Promise} Promise that resolves once the server has started listening. 86 | */ 87 | listen (port, host) { 88 | return new Promise((resolve, reject) => { 89 | this.server.listen(port, host, (err) => { 90 | if (err) return reject(err) 91 | else return resolve() 92 | }) 93 | }) 94 | } 95 | 96 | async close () { 97 | if (this.cleaner) { 98 | log('Halting cleaner...') 99 | clearInterval(this.cleaner) 100 | } 101 | if (this.server) { 102 | await new Promise(resolve => this.server.close(resolve)) 103 | } 104 | return super.close() 105 | } 106 | 107 | getIndexHtml () { 108 | return new Promise((resolve, reject) => { 109 | const filePath = path.join(__dirname, 'index.html') 110 | fs.readFile(filePath, 'utf-8', (err, html) => { 111 | if (err) return reject(err) 112 | else return resolve(html) 113 | }) 114 | }) 115 | } 116 | 117 | getWebsocketHandler () { 118 | return (stream, req) => { 119 | stream.on('error', function (e) { 120 | log('getWebsocketHandler has error: ' + e) 121 | }) 122 | const urlParts = req.url.split('/') 123 | const address = urlParts[1] 124 | if (!address) { 125 | stream.end('Must provide archive key') 126 | return Promise.resolve() 127 | } 128 | return this.addIfNew(address).then((dat) => { 129 | const archive = dat.archive 130 | const replication = archive.replicate({ 131 | live: true 132 | }) 133 | 134 | // Relay error events 135 | replication.on('error', function (e) { 136 | stream.emit('error', e) 137 | }) 138 | stream.pipe(replication).pipe(stream) 139 | }).catch((e) => { 140 | stream.end(e.message) 141 | }) 142 | } 143 | } 144 | 145 | getHandler () { 146 | return this.getIndexHtml().then((welcome) => { 147 | return (req, res) => { 148 | const start = Date.now() 149 | const requestURL = `http://${req.headers.host}${req.url}` 150 | const urlParts = new URL(requestURL) 151 | const pathParts = urlParts.pathname.split('/').slice(1) 152 | 153 | let hostname = urlParts.hostname 154 | const hostIsOnLoopback = RegExp(IS_LOCALHOST_REGEX).test(hostname) 155 | 156 | // normalize loopback interface hostnames 157 | if (hostIsOnLoopback && !hostname.endsWith(this.loopback)) { 158 | hostname = hostname.replace(IS_LOCALHOST_REGEX, DAT_LOCALHOST_NAME) 159 | 160 | // redirect to normalized hostname 161 | res.writeHead(302, { 162 | 'Access-Control-Allow-Origin': '*', 163 | Location: `http://${hostname}:${urlParts.port}${req.url}` 164 | }) 165 | return res.end() 166 | } 167 | 168 | // normalized subdomain 169 | const hostParts = hostname.split('.') 170 | let subdomain = null 171 | if (hostIsOnLoopback && hostParts.length >= 3) { 172 | subdomain = hostParts.slice(0, -2).join('.') 173 | } else if (this.domainRegex.test(hostname)) { 174 | const i = hostname.indexOf(this.domain) 175 | subdomain = hostname.slice(0, i - 1) 176 | } 177 | 178 | // get Dat archive key from subdomain or path 179 | let datKey = null 180 | if (subdomain) { 181 | if (subdomain.includes('.')) { 182 | datKey = subdomain 183 | } else if (subdomain.length === BASE_32_KEY_LENGTH) { 184 | datKey = hexTo32.decode(subdomain) 185 | } 186 | } else if (pathParts.length >= 1) { 187 | datKey = pathParts[0] // ??? 188 | } 189 | 190 | const isRedirected = Boolean(subdomain && datKey) 191 | const isRedirecting = Boolean(this.redirect && !subdomain && datKey) 192 | 193 | let path = '/' 194 | if (pathParts) { 195 | if (subdomain && isRedirected) { 196 | path = pathParts.join('/') 197 | } else if (pathParts.length >= 2) { 198 | path = pathParts.slice(1).join('/') 199 | } 200 | } 201 | 202 | // return index 203 | if (path === '/' && !datKey && !isRedirected && !isRedirecting) { 204 | res.writeHead(200) 205 | return res.end(welcome) 206 | } 207 | 208 | // redirect /:key to /:key/ 209 | if (!isRedirected && pathParts.length === 1 && !pathParts[0].endsWith('/') && pathParts[0] !== 'favicon.ico') { 210 | res.writeHead(302, { 211 | 'Access-Control-Allow-Origin': '*', 212 | Location: `${req.url}/` 213 | }) 214 | return res.end() 215 | } else { 216 | res.setHeader('Access-Control-Allow-Origin', '*') 217 | } 218 | 219 | const logError = (err, end) => log('[%s] %s %s | ERROR %s [%i ms]', datKey, req.method, path, err.message, end - start) 220 | log('[%s] %s %s', datKey, req.method, path) 221 | 222 | // redirect to subdomain 223 | if (isRedirecting) { 224 | return DatLibrarian.resolve(datKey).then((resolvedKey) => { 225 | const encodedDatKey = datKey.includes('.') ? datKey : hexTo32.encode(resolvedKey) 226 | const redirectURL = `http://${encodedDatKey}.${hostname}:${urlParts.port}/${path}${urlParts.search || ''}` 227 | log('Redirecting %s to %s', datKey, redirectURL) 228 | res.setHeader('Location', redirectURL) 229 | res.writeHead(302) 230 | res.end() 231 | }).catch((e) => { 232 | const end = Date.now() 233 | logError(e, end) 234 | res.writeHead(500) 235 | res.end(ERR_500) 236 | }) 237 | } 238 | 239 | // Return a Dat DNS entry without fetching it from the archive 240 | if (path === '.well-known/dat') { 241 | return DatLibrarian.resolve(datKey).then((resolvedAddress) => { 242 | log('Resolving address %s to %s', datKey, resolvedAddress) 243 | 244 | res.writeHead(200) 245 | res.end(`dat://${resolvedAddress}\nttl=3600`) 246 | }).catch((e) => { 247 | const end = Date.now() 248 | logError(e, end) 249 | res.writeHead(500) 250 | res.end(ERR_500) 251 | }) 252 | } 253 | 254 | // return the archive 255 | return this.addIfNew(datKey).then((dat) => { 256 | // handle it!! 257 | const end = Date.now() 258 | log('[%s] %s %s | OK [%i ms]', datKey, req.method, path, end - start) 259 | req.url = `/${path}${urlParts.search || ''}` 260 | dat.onrequest(req, res) 261 | }).catch((e) => { 262 | const end = Date.now() 263 | logError(e, end) 264 | if (e.message.indexOf('not found') > -1) { 265 | res.writeHead(404) 266 | res.end(ERR_404) 267 | } else { 268 | res.writeHead(500) 269 | res.end(ERR_500) 270 | } 271 | }) 272 | } 273 | }) 274 | } 275 | 276 | addIfNew (address) { 277 | return DatLibrarian.resolve(address).then((key) => { 278 | if (this.keys.indexOf(key) === -1) { 279 | return this.add(address) 280 | } else { 281 | this.lru[key] = Date.now() 282 | return this.get(key) 283 | } 284 | }) 285 | } 286 | 287 | clearOldest () { 288 | const sortOldestFirst = Object.keys(this.lru).sort((a, b) => { 289 | return this.lru[a] - this.lru[b] 290 | }) 291 | const oldest = sortOldestFirst[0] 292 | return this.remove(oldest) 293 | } 294 | 295 | add () { 296 | if (this.keys.length >= this.max) { 297 | // Delete the oldest item when we reach capacity and try again 298 | return this.clearOldest().then(() => this.add.apply(this, arguments)) 299 | } 300 | return super.add.apply(this, arguments).then((dat) => { 301 | log('Adding HTTP handler to archive...') 302 | if (!dat.onrequest) dat.onrequest = hyperdriveHttp(dat.archive, { live: true, exposeHeaders: true }) 303 | return new Promise((resolve) => { 304 | /* 305 | Wait for the archive to populate OR for 3s to pass, 306 | so that addresses for archives which don't exist 307 | don't hold us up all night. 308 | */ 309 | let isDone = false 310 | const done = () => { 311 | if (isDone) return null 312 | isDone = true 313 | const key = dat.archive.key.toString('hex') 314 | this.lru[key] = Date.now() 315 | return resolve(dat) 316 | } 317 | dat.archive.metadata.update(1, done) 318 | setTimeout(done, 3000) 319 | }) 320 | }) 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dat-gateway", 3 | "description": "A Dat-to-HTTP gateway and helper library.", 4 | "version": "2.3.1-beta", 5 | "main": "index.js", 6 | "bin": "bin.js", 7 | "scripts": { 8 | "start": "./bin.js", 9 | "test": "standard && dependency-check . --unused --no-dev && mocha" 10 | }, 11 | "dependencies": { 12 | "dat-librarian": "^1.1.8-beta", 13 | "hyperdrive-http": "^4.4.0", 14 | "hex-to-32": "^2.0.0", 15 | "websocket-stream": "^5.5.0", 16 | "xdg-basedir": "^4.0.0", 17 | "yargs": "^15.0.2" 18 | }, 19 | "devDependencies": { 20 | "dependency-check": "^4.1.0", 21 | "mkdirp": "^0.5.1", 22 | "mocha": "^6.2.2", 23 | "nock": "^11.7.0", 24 | "random-access-memory": "^3.1.1", 25 | "rimraf": "^3.0.0", 26 | "standard": "^14.3.1" 27 | }, 28 | "files": [ 29 | "index.html", 30 | "index.js", 31 | "bin.js" 32 | ], 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/garbados/dat-gateway.git" 36 | }, 37 | "keywords": [ 38 | "dat", 39 | "http", 40 | "https" 41 | ], 42 | "authors": [ 43 | "Paul Frazee ", 44 | "Diana Thayer " 45 | ], 46 | "license": "Apache-2.0" 47 | } 48 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* global describe it before after beforeEach afterEach */ 2 | 3 | const assert = require('assert') 4 | const fs = require('fs') 5 | const http = require('http') 6 | const hyperdrive = require('hyperdrive') 7 | const mkdirp = require('mkdirp') 8 | const nock = require('nock') 9 | const path = require('path') 10 | const ram = require('random-access-memory') 11 | const rimraf = require('rimraf') 12 | const websocket = require('websocket-stream') 13 | const hexTo32 = require('hex-to-32') 14 | 15 | const TEST_HOST = 'dat.localhost' 16 | const TEST_PORT = '5917' 17 | const TEST_GATEWAY = `${TEST_HOST}:${TEST_PORT}` 18 | const TEST_DNS_KEY = 'garbados.hashbase.io' 19 | const TEST_KEY = 'c33bc8d7c32a6e905905efdbf21efea9ff23b00d1c3ee9aea80092eaba6c4957' 20 | const TEST_ENC_KEY = hexTo32.encode(TEST_KEY) 21 | 22 | const DatGateway = require('.') 23 | 24 | const NOCK_DIR = '.nock' 25 | const RECORD_TESTS = !!process.env.RECORD_TESTS 26 | 27 | const dir = '.NOCK_DIR' 28 | const ttl = 4000 29 | const period = 1000 30 | 31 | const recordOrLoadNocks = function () { 32 | const titles = [] 33 | let test = this.currentTest 34 | while (test.parent) { 35 | titles.unshift(test.title) 36 | if (test.parent) { test = test.parent } 37 | } 38 | const dir = path.join(NOCK_DIR, ...titles.slice(0, -1)) 39 | const name = `${titles.slice(-1)[0]}.json` 40 | this._currentNock = { titles, dir, name } 41 | if (RECORD_TESTS) { 42 | nock.recorder.rec({ 43 | output_objects: true, 44 | dont_print: true 45 | }) 46 | } else { 47 | try { 48 | nock.load(path.join(dir, encodeURIComponent(name))) 49 | } catch (error) { 50 | if (error.code === 'ENOENT') { 51 | // no nock 52 | } else { 53 | throw error 54 | } 55 | } 56 | } 57 | } 58 | 59 | const concludeNocks = function () { 60 | if (RECORD_TESTS) { 61 | // save http requests for future nocking 62 | const { dir, name } = this._currentNock 63 | const fixturePath = path.join(dir, encodeURIComponent(name)) 64 | const nockCallObjects = nock.recorder.play() 65 | mkdirp.sync(dir) 66 | fs.writeFileSync(fixturePath, JSON.stringify(nockCallObjects), 'utf8') 67 | nock.restore() 68 | nock.recorder.clear() 69 | } 70 | } 71 | 72 | beforeEach(function () { 73 | recordOrLoadNocks.call(this) 74 | }) 75 | 76 | afterEach(async function () { 77 | concludeNocks.call(this) 78 | }) 79 | 80 | describe('dat-gateway', function () { 81 | this.timeout(60 * 1000) // 1 minute 82 | 83 | before(async function () { 84 | this.gateway = new DatGateway({ 85 | dir, 86 | loopback: TEST_HOST, 87 | period, 88 | ttl 89 | }) 90 | await this.gateway.load() 91 | return this.gateway.listen(5917) 92 | }) 93 | 94 | after(async function () { 95 | await this.gateway.close() 96 | rimraf.sync(dir) 97 | }) 98 | 99 | it('cache directory should exist', function () { 100 | assert.strictEqual(this.gateway.dir, dir) 101 | }) 102 | 103 | it('index portal should exist', function () { 104 | return new Promise((resolve) => { 105 | const req = http.get(`http://${TEST_GATEWAY}/`, resolve) 106 | req.on('error', console.log) 107 | }).then((res) => { 108 | assert.strictEqual(res.statusCode, 200) 109 | }).catch((e) => { 110 | console.error(e) 111 | throw e 112 | }) 113 | }) 114 | 115 | it('should redirect index portal listening on loopback to normalized index portal host', function () { 116 | return new Promise((resolve) => { 117 | const req = http.get('http://127.0.0.1:5917/', resolve) 118 | req.on('error', console.log) 119 | }).then((res) => { 120 | assert.strictEqual(res.statusCode, 302) 121 | assert.strictEqual(res.headers.location, `http://${TEST_GATEWAY}/`) 122 | }).catch((e) => { 123 | console.error(e) 124 | throw e 125 | }) 126 | }) 127 | 128 | it('should handle requests for :gateway/:invalid_key/', function () { 129 | return new Promise((resolve) => { 130 | http.get(`http://${TEST_GATEWAY}/whoop-whoop-test/`, resolve) 131 | }).then((res) => { 132 | assert.strictEqual(res.statusCode, 404) 133 | }).catch((e) => { 134 | console.error(e) 135 | throw e 136 | }) 137 | }) 138 | 139 | it('should handle requests for gateway/:dead_key/', function () { 140 | return new Promise((resolve) => { 141 | http.get(`http://${TEST_GATEWAY}/af75142d92dd1e456cf2a7e58a37f891fe42a1e49ce2a5a7859de938e38f4642/`, resolve) 142 | }).then((res) => { 143 | assert.strictEqual(res.statusCode, 200) 144 | }).catch((e) => { 145 | console.error(e) 146 | throw e 147 | }) 148 | }) 149 | 150 | it('should proactively deleted expired archives', function () { 151 | return new Promise((resolve) => { 152 | const checker = setInterval(() => { 153 | // assert that they have been deleted 154 | if (this.gateway.keys.length === 0) { 155 | clearInterval(checker) 156 | return resolve() 157 | } 158 | }, ttl) 159 | }) 160 | }) 161 | 162 | it('should handle websockets for replication', async function () { 163 | const archive = hyperdrive(ram, Buffer.from(TEST_KEY, 'hex')) 164 | const url = `ws://${TEST_GATEWAY}/${TEST_KEY}` 165 | const socket = websocket(url) 166 | 167 | await new Promise((resolve, reject) => { 168 | archive.once('error', reject) 169 | archive.once('ready', () => { 170 | socket.pipe(archive.replicate({ 171 | live: true 172 | })).pipe(socket) 173 | }) 174 | archive.on('update', () => { 175 | return resolve(socket) 176 | }) 177 | }) 178 | 179 | await await new Promise(resolve => setTimeout(resolve, 2000)) 180 | 181 | // assert favicon exists 182 | await new Promise((resolve, reject) => { 183 | archive.access('/icons/favicon.ico', 'utf-8', (e, content) => { 184 | if (e) reject(e) 185 | else resolve(content) 186 | }) 187 | }) 188 | 189 | await new Promise(resolve => socket.end(resolve)) 190 | }) 191 | }) 192 | 193 | describe('dat-gateway --redirect false', function () { 194 | this.timeout(0) 195 | 196 | before(function () { 197 | this.gateway = new DatGateway({ dir, ttl, period, redirect: false }) 198 | return this.gateway.load().then(() => { 199 | return this.gateway.listen(5917) 200 | }) 201 | }) 202 | 203 | after(function () { 204 | return this.gateway.close().then(() => { 205 | rimraf.sync(dir) 206 | }) 207 | }) 208 | 209 | it('should handle requests for :gateway/:dns_key/:path', function () { 210 | return new Promise((resolve) => { 211 | const req = http.get(`http://${TEST_GATEWAY}/${TEST_DNS_KEY}/icons/favicon.ico`, resolve) 212 | req.on('error', console.log) 213 | }).then((res) => { 214 | assert.strictEqual(res.statusCode, 200) 215 | }).catch((e) => { 216 | console.error(e) 217 | throw e 218 | }) 219 | }) 220 | 221 | it('should handle requests for :gateway/:key/:path', function () { 222 | return new Promise((resolve) => { 223 | const req = http.get(`http://${TEST_GATEWAY}/${TEST_KEY}/icons/favicon.ico`, resolve) 224 | req.on('error', console.log) 225 | }).then((res) => { 226 | assert.strictEqual(res.statusCode, 200) 227 | }).catch((e) => { 228 | console.error(e) 229 | throw e 230 | }) 231 | }) 232 | 233 | it('should redirect requests for :gateway/:dns_key to :gateway/:dns_key/', function () { 234 | return new Promise((resolve) => { 235 | http.get(`http://${TEST_GATEWAY}/${TEST_DNS_KEY}`, resolve) 236 | }).then((res) => { 237 | assert.strictEqual(res.statusCode, 302) 238 | assert.strictEqual(res.headers.location, `/${TEST_DNS_KEY}/`) 239 | }).catch((e) => { 240 | console.error(e) 241 | throw e 242 | }) 243 | }) 244 | 245 | it('should redirect requests for :gateway/:key to :gateway/:key/', function () { 246 | return new Promise((resolve) => { 247 | http.get(`http://${TEST_GATEWAY}/${TEST_KEY}`, resolve) 248 | }).then((res) => { 249 | assert.strictEqual(res.statusCode, 302) 250 | assert.strictEqual(res.headers.location, `/${TEST_KEY}/`) 251 | }).catch((e) => { 252 | console.error(e) 253 | throw e 254 | }) 255 | }) 256 | 257 | it('should not redirect loop requests for :gateway/:key/', function () { 258 | return new Promise((resolve) => { 259 | http.get(`http://${TEST_GATEWAY}/${TEST_KEY}/`, resolve) 260 | }).then((res) => { 261 | assert.strictEqual(res.statusCode, 200) 262 | }).catch((e) => { 263 | console.error(e) 264 | throw e 265 | }) 266 | }) 267 | }) 268 | 269 | describe('dat-gateway --redirect true', function () { 270 | this.timeout(0) 271 | 272 | before(function () { 273 | this.gateway = new DatGateway({ 274 | dir, 275 | loopback: TEST_HOST, 276 | period, 277 | redirect: true, 278 | ttl 279 | }) 280 | return this.gateway.load().then(() => { 281 | return this.gateway.listen(5917) 282 | }) 283 | }) 284 | 285 | after(async function () { 286 | await this.gateway.close() 287 | rimraf.sync(dir) 288 | }) 289 | 290 | it('should redirect requests for :gateway/:dns_key/:path to :dns_key.:gateway/:path/', function () { 291 | return new Promise((resolve) => { 292 | http.get(`http://${TEST_GATEWAY}/${TEST_DNS_KEY}/index.html`, resolve) 293 | }).then((res) => { 294 | assert.strictEqual(res.statusCode, 302) 295 | assert.strictEqual(res.headers.location, `http://${TEST_DNS_KEY}.${TEST_GATEWAY}/index.html`) 296 | }).catch((e) => { 297 | console.error(e) 298 | throw e 299 | }) 300 | }) 301 | 302 | it('should redirect from :b32_key.localhost to :b32_key.:gateway', function () { 303 | return new Promise((resolve) => { 304 | const req = http.get(`http://${TEST_ENC_KEY}.localhost:5917/`, resolve) 305 | req.on('error', console.log) 306 | }).then((res) => { 307 | assert.strictEqual(res.statusCode, 302) 308 | assert.strictEqual(res.headers.location, `http://${TEST_ENC_KEY}.${TEST_GATEWAY}/`) 309 | }).catch((e) => { 310 | console.error(e) 311 | throw e 312 | }) 313 | }) 314 | 315 | it('should redirect from :dns_key.localhost to :dns_key.:gateway', function () { 316 | return new Promise((resolve) => { 317 | const req = http.get(`http://${TEST_DNS_KEY}.localhost:5917/`, resolve) 318 | req.on('error', console.log) 319 | }).then((res) => { 320 | assert.strictEqual(res.statusCode, 302) 321 | assert.strictEqual(res.headers.location, `http://${TEST_DNS_KEY}.${TEST_GATEWAY}/`) 322 | }).catch((e) => { 323 | console.error(e) 324 | throw e 325 | }) 326 | }) 327 | 328 | it('should not redirect requests for :dns_key.:gateway/:path to :dns_key.:gateway/:path/', function () { 329 | return new Promise((resolve) => { 330 | const req = http.get(`http://${TEST_DNS_KEY}.${TEST_GATEWAY}/index.html`, resolve) 331 | req.on('error', console.log) 332 | }).then((res) => { 333 | assert.strictEqual(res.statusCode, 200) 334 | }).catch((e) => { 335 | console.error(e) 336 | throw e 337 | }) 338 | }) 339 | 340 | it('should redirect requests for :gateway/:key/:path to :b32_key.:gateway/:path/', function () { 341 | return new Promise((resolve) => { 342 | http.get(`http://${TEST_GATEWAY}/${TEST_KEY}/test/long/path`, resolve) 343 | }).then((res) => { 344 | assert.strictEqual(res.statusCode, 302) 345 | assert.strictEqual(res.headers.location, `http://${TEST_ENC_KEY}.${TEST_GATEWAY}/test/long/path`) 346 | }).catch((e) => { 347 | console.error(e) 348 | throw e 349 | }) 350 | }) 351 | 352 | it('should handle requests for :dns_key.:gateway/:path', function () { 353 | return new Promise((resolve) => { 354 | const req = http.get(`http://${TEST_DNS_KEY}.${TEST_GATEWAY}/icons/favicon.ico`, resolve) 355 | req.on('error', console.log) 356 | }).then((res) => { 357 | assert.strictEqual(res.statusCode, 200) 358 | }).catch((e) => { 359 | console.error(e) 360 | throw e 361 | }) 362 | }) 363 | 364 | it('should handle requests for :b32_key.:gateway/:path', function () { 365 | return new Promise((resolve) => { 366 | const req = http.get(`http://${TEST_ENC_KEY}.${TEST_GATEWAY}/icons/favicon.ico`, resolve) 367 | req.on('error', console.log) 368 | }).then((res) => { 369 | assert.strictEqual(res.statusCode, 200) 370 | }).catch((e) => { 371 | console.error(e) 372 | throw e 373 | }) 374 | }) 375 | 376 | it('should handle requests for :invalid_key.:gateway/', function () { 377 | return new Promise((resolve) => { 378 | http.get(`http://whoop-whoop-test.${TEST_GATEWAY}/`, resolve) 379 | }).then((res) => { 380 | assert.strictEqual(res.statusCode, 200) 381 | }).catch((e) => { 382 | console.error(e) 383 | throw e 384 | }) 385 | }) 386 | }) 387 | --------------------------------------------------------------------------------