diff --git a/.changeset/bright-eels-grab.md b/.changeset/bright-eels-grab.md new file mode 100644 index 000000000..2f2558c6d --- /dev/null +++ b/.changeset/bright-eels-grab.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Remove CORS_PROXY env option in docker image diff --git a/.changeset/few-readers-lay.md b/.changeset/few-readers-lay.md new file mode 100644 index 000000000..7e6427937 --- /dev/null +++ b/.changeset/few-readers-lay.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add REQUEST_PROXY, TOR_PROXY, and I2P_PROXY env options in docker image diff --git a/README.md b/README.md index 16eaf5814..cdb22f505 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ noStrudels docker image has a few options for connecting to other services runni - `CACHE_RELAY`: if set the client will use the relay to cache all of its events instead of storing them in the browser cache - `IMAGE_PROXY`: can be set to a local [imageproxy](https://github.com/willnorris/imageproxy) instance so the app can resize profile images -- `CORS_PROXY`: can be set to a local [cors-anywhere](https://github.com/Rob--W/cors-anywhere) instance so the app can proxy http request +- `REQUEST_PROXY`: can be set to a local [cors-anywhere](https://github.com/Rob--W/cors-anywhere) instance so the app can proxy http request +- `PROXY_FIRST`: if this is set to `true` all http requests will go through the request proxy first You can find a full example of all these services in the [docker-compose.yaml](./docker-compose.yaml) diff --git a/docker-compose.yaml b/docker-compose.yaml index 6b925b43a..c7329b1b7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,10 +4,14 @@ volumes: data: {} services: - cors: - image: ghcr.io/hzrd149/docker-cors-anywhere:0.4.5 - environment: - CORSANYWHERE_REQUIRE_HEADERS: "host" + i2p-proxy: + image: purplei2p/i2pd:release-2.51.0 + tor-proxy: + image: dockage/tor-privoxy:latest + # cors: + # image: ghcr.io/hzrd149/docker-cors-anywhere:0.4.5 + # environment: + # CORSANYWHERE_REQUIRE_HEADERS: "host" imageproxy: image: ghcr.io/willnorris/imageproxy:v0.11.2 relay: @@ -19,11 +23,14 @@ services: image: ghcr.io/hzrd149/nostrudel:latest depends_on: - relay - - cors + - tor-proxy + - i2p-proxy - imageproxy environment: CACHE_RELAY: relay:8080 IMAGE_PROXY: imageproxy:8080 - CORS_PROXY: cors:8080 + TOR_PROXY: tor-proxy:9050 + I2P_PROXY: i2p-proxy:4444 + REQUEST_PROXY: "true" ports: - 8080:80 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 14e1715c7..3d81087eb 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -3,6 +3,41 @@ set -e PROXY_PASS_BLOCK="" +# start tor if set to true +if [ "$TOR_PROXY" = "true" ]; then + echo "Starting tor socks proxy" + + tor & + tor_process=$! + TOR_PROXY="127.0.0.1:9050" +fi + +# inject request proxy +if [ -n "$REQUEST_PROXY" ]; then + REQUEST_PROXY_URL="$REQUEST_PROXY" + + if [ "$REQUEST_PROXY" = "true" ]; then + REQUEST_PROXY_URL="127.0.0.1:8080" + fi + + echo "Request proxy set to $REQUEST_PROXY" + sed -i 's/REQUEST_PROXY = ""/REQUEST_PROXY = "\/request-proxy"/g' /usr/share/nginx/html/index.html + PROXY_PASS_BLOCK="$PROXY_PASS_BLOCK + location /request-proxy/ { + proxy_pass http://$REQUEST_PROXY_URL; + rewrite ^/request-proxy/(.*) /\$1 break; + } + " + + if [ -n "$PROXY_FIRST" ]; then + echo "Telling app to use request proxy first" + sed -i 's/PROXY_FIRST = false/PROXY_FIRST = true/g' /usr/share/nginx/html/index.html + fi +else + echo "No request proxy set" +fi + +# inject cache relay URL if [ -n "$CACHE_RELAY" ]; then echo "Cache relay set to $CACHE_RELAY" sed -i 's/CACHE_RELAY_ENABLED = false/CACHE_RELAY_ENABLED = true/g' /usr/share/nginx/html/index.html @@ -18,19 +53,7 @@ else echo "No cache relay set" fi -if [ -n "$CORS_PROXY" ]; then - echo "CORS proxy set to $CORS_PROXY" - sed -i 's/CORS_PROXY_PATH = ""/CORS_PROXY_PATH = "\/corsproxy"/g' /usr/share/nginx/html/index.html - PROXY_PASS_BLOCK="$PROXY_PASS_BLOCK - location /corsproxy/ { - proxy_pass http://$CORS_PROXY; - rewrite ^/corsproxy/(.*) /\$1 break; - } - " -else - echo "No CORS proxy set" -fi - +# inject image proxy URL if [ -n "$IMAGE_PROXY" ]; then echo "Image proxy set to $IMAGE_PROXY" sed -i 's/IMAGE_PROXY_PATH = ""/IMAGE_PROXY_PATH = "\/imageproxy"/g' /usr/share/nginx/html/index.html @@ -99,9 +122,27 @@ echo "$NGINX_CONF" > $CONF_FILE _term() { echo "Caught SIGTERM signal!" + + # stop node server + if [ "$REQUEST_PROXY" = "true" ]; then + kill -SIGTERM "$node_process" 2>/dev/null + fi + + # stop tor if started + if [ "$TOR_PROXY" = "true" ]; then + kill -SIGTERM "$tor_process" 2>/dev/null + fi + + # stop nginx kill -SIGTERM "$nginx_process" 2>/dev/null } +if [ "$REQUEST_PROXY" = "true" ]; then + echo "Starting local request proxy" + node server/index.js & + node_process=$! +fi + nginx -g 'daemon off;' & nginx_process=$! diff --git a/dockerfile b/dockerfile index 73aae6599..af18a978d 100644 --- a/dockerfile +++ b/dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM node:20.11 as builder +FROM node:20-alpine as builder WORKDIR /app @@ -17,12 +17,33 @@ RUN yarn build FROM nginx:stable-alpine-slim EXPOSE 80 + +# install nodejs +RUN apk add --no-cache libstdc++ wget +RUN wget https://unofficial-builds.nodejs.org/download/release/v20.12.2/node-v20.12.2-linux-x64-musl.tar.gz +RUN tar -xf node-v20.12.2-linux-x64-musl.tar.gz && mv node-v20.12.2-linux-x64-musl node-v20.12.2 +RUN rm node-v20.12.2-linux-x64-musl.tar.gz + +# install tor +# copied from https://github.com/klemmchr/tor-alpine/blob/master/Dockerfile +RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && \ + apk -U upgrade && \ + apk -v add tor@edge torsocks@edge + +# remove tmp files +RUN rm -rf /var/cache/apk/* /tmp/* /var/tmp/* + +ENV PATH="/node-v20.12.2/bin:$PATH" + +WORKDIR /app COPY --from=builder /app/dist /usr/share/nginx/html -ENV CACHE_RELAY="" -ENV IMAGE_PROXY="" -ENV CORS_PROXY="" -ADD ./docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh -RUN chmod a+x /usr/local/bin/docker-entrypoint.sh +# copy server +COPY server/ /app/server/ +RUN cd /app/server/ && npm install -ENTRYPOINT "/usr/local/bin/docker-entrypoint.sh" +# setup entrypoint +ADD ./docker-entrypoint.sh docker-entrypoint.sh +RUN chmod a+x docker-entrypoint.sh + +ENTRYPOINT "/app/docker-entrypoint.sh" diff --git a/index.html b/index.html index c59dd4b1f..7e53819c2 100644 --- a/index.html +++ b/index.html @@ -23,7 +23,8 @@ diff --git a/package.json b/package.json index 27526463c..79a4fdd70 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,9 @@ "three-spritetext": "^1.8.1", "three-stdlib": "^2.29.4", "webln": "^0.3.2", + "workbox-core": "7.0.0", + "workbox-precaching": "7.0.0", + "workbox-routing": "7.0.0", "yet-another-react-lightbox": "^3.17.3", "zen-observable": "^0.10.0" }, diff --git a/server/index.js b/server/index.js new file mode 100644 index 000000000..329bd0438 --- /dev/null +++ b/server/index.js @@ -0,0 +1,52 @@ +var cors_proxy = require("cors-anywhere"); +var { PacProxyAgent } = require("pac-proxy-agent"); + +const { TOR_PROXY, I2P_PROXY } = process.env; + +if (TOR_PROXY) console.log("Tor Proxy:", TOR_PROXY); +if (I2P_PROXY) console.log("I2P Proxy:", I2P_PROXY); + +const I2pConfig = I2P_PROXY + ? ` +if (shExpMatch(host, "*.i2p")) +{ + return "PROXY ${I2P_PROXY}"; +}`.trim() + : ""; +const TorConfig = TOR_PROXY + ? ` +if (shExpMatch(host, "*.onion")) +{ + return "SOCKS5 ${TOR_PROXY}"; +}`.trim() + : ""; + +const PACFile = ` +// SPDX-License-Identifier: CC0-1.0 + +function FindProxyForURL(url, host) +{ + ${I2pConfig} + ${TorConfig} + return "DIRECT"; +} +`.trim(); + +const PACURI = "pac+data:application/x-ns-proxy-autoconfig;base64," + btoa(PACFile); + +var host = "127.0.0.1"; +var port = 8080; + +cors_proxy + .createServer({ + requireHeader: [], + removeHeaders: ["cookie", "cookie2"], + redirectSameOrigin: true, + httpProxyOptions: { + xfwd: false, + agent: new PacProxyAgent(PACURI), + }, + }) + .listen(port, host, () => { + console.log("Running HTTP request proxy on " + host + ":" + port); + }); diff --git a/server/package.json b/server/package.json new file mode 100644 index 000000000..389591ce7 --- /dev/null +++ b/server/package.json @@ -0,0 +1,12 @@ +{ + "name": "server", + "version": "1.0.0", + "private": true, + "main": "index.js", + "license": "MIT", + "dependencies": { + "cors-anywhere": "^0.4.4", + "pac-proxy-agent": "^7.0.1", + "workbox-precaching": "^7.1.0" + } +} diff --git a/server/yarn.lock b/server/yarn.lock new file mode 100644 index 000000000..bd3d0bd25 --- /dev/null +++ b/server/yarn.lock @@ -0,0 +1,269 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@tootallnate/quickjs-emscripten@^0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c" + integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== + +agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== + dependencies: + debug "^4.3.4" + +ast-types@^0.13.4: + version "0.13.4" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" + integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== + dependencies: + tslib "^2.0.1" + +basic-ftp@^5.0.2: + version "5.0.5" + resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.0.5.tgz#14a474f5fffecca1f4f406f1c26b18f800225ac0" + integrity sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg== + +cors-anywhere@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cors-anywhere/-/cors-anywhere-0.4.4.tgz#98892fcab55f408fff13a63e125135c18dc22ca8" + integrity sha512-8OBFwnzMgR4mNrAeAyOLB2EruS2z7u02of2bOu7i9kKYlZG+niS7CTHLPgEXKWW2NAOJWRry9RRCaL9lJRjNqg== + dependencies: + http-proxy "1.11.1" + proxy-from-env "0.0.1" + +data-uri-to-buffer@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz#8a58bb67384b261a38ef18bea1810cb01badd28b" + integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== + +debug@4, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +degenerator@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-5.0.1.tgz#9403bf297c6dad9a1ece409b37db27954f91f2f5" + integrity sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ== + dependencies: + ast-types "^0.13.4" + escodegen "^2.1.0" + esprima "^4.0.1" + +escodegen@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionalDependencies: + source-map "~0.6.1" + +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eventemitter3@1.x.x: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" + integrity sha512-DOFqA1MF46fmZl2xtzXR3MPCRsXqgoFqdXcrCVYM3JNnfUeHTm/fh/v/iU7gBFpwkuBmoJPAm5GuhdDfSEJMJA== + +fs-extra@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +get-uri@^6.0.1: + version "6.0.3" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.3.tgz#0d26697bc13cf91092e519aa63aa60ee5b6f385a" + integrity sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw== + dependencies: + basic-ftp "^5.0.2" + data-uri-to-buffer "^6.0.2" + debug "^4.3.4" + fs-extra "^11.2.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +http-proxy-agent@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +http-proxy@1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.11.1.tgz#71df55757e802d58ea810df2244019dda05ae85d" + integrity sha512-qz7jZarkVG3G6GMq+4VRJPSN4NkIjL4VMTNhKGd8jc25BumeJjWWvnY3A7OkCGa8W1TTxbaK3dcE0ijFalITVA== + dependencies: + eventemitter3 "1.x.x" + requires-port "0.x.x" + +https-proxy-agent@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168" + integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg== + dependencies: + agent-base "^7.0.2" + debug "4" + +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +netmask@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== + +pac-proxy-agent@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz#6b9ddc002ec3ff0ba5fdf4a8a21d363bcc612d75" + integrity sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A== + dependencies: + "@tootallnate/quickjs-emscripten" "^0.23.0" + agent-base "^7.0.2" + debug "^4.3.4" + get-uri "^6.0.1" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.2" + pac-resolver "^7.0.0" + socks-proxy-agent "^8.0.2" + +pac-resolver@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.1.tgz#54675558ea368b64d210fd9c92a640b5f3b8abb6" + integrity sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg== + dependencies: + degenerator "^5.0.0" + netmask "^2.0.2" + +proxy-from-env@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-0.0.1.tgz#b27c4946e9e6d5dbadb7598a6435d3014c4cfd49" + integrity sha512-B9Hnta3CATuMS0q6kt5hEezOPM+V3dgaRewkFtFoaRQYTVNsHqUvFXmndH06z3QO1ZdDnRELv5vfY6zAj/gG7A== + +requires-port@0.x.x: + version "0.0.1" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-0.0.1.tgz#4b4414411d9df7c855995dd899a8c78a2951c16d" + integrity sha512-AzPDCliPoWDSvEVYRQmpzuPhGGEnPrQz9YiOEvn+UdB9ixBpw+4IOZWtwctmpzySLZTy7ynpn47V14H4yaowtA== + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks-proxy-agent@^8.0.2: + version "8.0.3" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz#6b2da3d77364fde6292e810b496cb70440b9b89d" + integrity sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A== + dependencies: + agent-base "^7.1.1" + debug "^4.3.4" + socks "^2.7.1" + +socks@^2.7.1: + version "2.8.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +tslib@^2.0.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +workbox-core@7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-7.1.0.tgz#1867576f994f20d9991b71a7d0b2581af22db170" + integrity sha512-5KB4KOY8rtL31nEF7BfvU7FMzKT4B5TkbYa2tzkS+Peqj0gayMT9SytSFtNzlrvMaWgv6y/yvP9C0IbpFjV30Q== + +workbox-precaching@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-7.1.0.tgz#71e27ec2e85661a41b48dec0c92dae707c429eaa" + integrity sha512-LyxzQts+UEpgtmfnolo0hHdNjoB7EoRWcF7EDslt+lQGd0lW4iTvvSe3v5JiIckQSB5KTW5xiCqjFviRKPj1zA== + dependencies: + workbox-core "7.1.0" + workbox-routing "7.1.0" + workbox-strategies "7.1.0" + +workbox-routing@7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-7.1.0.tgz#c44bda350d1c5eb633ee97a660e64ce5473250c4" + integrity sha512-oOYk+kLriUY2QyHkIilxUlVcFqwduLJB7oRZIENbqPGeBP/3TWHYNNdmGNhz1dvKuw7aqvJ7CQxn27/jprlTdg== + dependencies: + workbox-core "7.1.0" + +workbox-strategies@7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-7.1.0.tgz#a589f2adc0df8f33049c7f4d4cdf4c9556715918" + integrity sha512-/UracPiGhUNehGjRm/tLUQ+9PtWmCbRufWtV0tNrALuf+HZ4F7cmObSEK+E4/Bx1p8Syx2tM+pkIrvtyetdlew== + dependencies: + workbox-core "7.1.0" diff --git a/src/classes/relay-pool.ts b/src/classes/relay-pool.ts index 178552533..572d73710 100644 --- a/src/classes/relay-pool.ts +++ b/src/classes/relay-pool.ts @@ -28,6 +28,7 @@ export default class RelayPool { requestRelay(url: string | URL, connect = true) { url = validateRelayURL(url); + const key = url.toString(); if (!this.relays.has(key)) { const newRelay = new AbstractRelay(key, { verifyEvent: verifyEventMethod }); diff --git a/src/components/external-embeds/types/image.tsx b/src/components/external-embeds/types/image.tsx index f735b6241..076f178de 100644 --- a/src/components/external-embeds/types/image.tsx +++ b/src/components/external-embeds/types/image.tsx @@ -40,7 +40,7 @@ export type EmbeddedImageProps = Omit( diff --git a/src/components/reload-prompt.tsx b/src/components/reload-prompt.tsx index a22f37ae8..44158df0c 100644 --- a/src/components/reload-prompt.tsx +++ b/src/components/reload-prompt.tsx @@ -11,7 +11,8 @@ export function ReloadPrompt(props: Omit) { updateServiceWorker, } = useRegisterSW({ onRegistered(r) { - console.log("SW Registered: " + r); + console.log("SW Registered"); + console.log(r); if (r) { setInterval(() => r.update(), intervalMS); @@ -21,7 +22,8 @@ export function ReloadPrompt(props: Omit) { toast({ status: "success", title: "App Installed", duration: 2000, isClosable: true }); }, onRegisterError(error) { - console.log("SW registration error", error); + console.log("SW registration error"); + console.log(error); }, }); diff --git a/src/helpers/cors.ts b/src/helpers/cors.ts deleted file mode 100644 index eeaaacbb8..000000000 --- a/src/helpers/cors.ts +++ /dev/null @@ -1,31 +0,0 @@ -import appSettings from "../services/settings/app-settings"; -import { convertToUrl } from "./url"; - -const corsFailedHosts = new Set(); - -export function createCorsUrl(url: URL | string, corsProxy?: string) { - if (!corsProxy && window.CORS_PROXY_PATH) corsProxy = new URL(window.CORS_PROXY_PATH, location.origin).toString(); - if (!corsProxy && appSettings.value.corsProxy) corsProxy = appSettings.value.corsProxy; - if (!corsProxy) return url; - - if (corsProxy.includes("")) { - return corsProxy.replace("", "" + url); - } else if (corsProxy.includes("")) { - return corsProxy.replace("", encodeURIComponent("" + url)); - } else { - return corsProxy.endsWith("/") ? corsProxy + url : corsProxy + "/" + url; - } -} - -export function fetchWithCorsFallback(url: URL | string, opts?: RequestInit) { - if (!appSettings.value.corsProxy) return fetch(url, opts); - - if (corsFailedHosts.has(convertToUrl(url).host)) { - return fetch(createCorsUrl(url), opts); - } - - return fetch(url, opts).catch((e) => { - corsFailedHosts.add(convertToUrl(url).host); - return fetch(createCorsUrl(url), opts); - }); -} diff --git a/src/helpers/debug.ts b/src/helpers/debug.ts index 7fcc3c725..67674e351 100644 --- a/src/helpers/debug.ts +++ b/src/helpers/debug.ts @@ -1,2 +1,5 @@ import debug from "debug"; + +if (!localStorage.getItem("debug") && import.meta.env.DEV) debug.enable("noStrudel,noStrudel:*"); + export const logger = debug("noStrudel"); diff --git a/src/helpers/request.ts b/src/helpers/request.ts new file mode 100644 index 000000000..f20ece136 --- /dev/null +++ b/src/helpers/request.ts @@ -0,0 +1,44 @@ +import appSettings from "../services/settings/app-settings"; +import { convertToUrl } from "./url"; + +const clearNetFailedHosts = new Set(); +const proxyFailedHosts = new Set(); + +export function createRequestProxyUrl(url: URL | string, corsProxy?: string) { + if (!corsProxy && window.REQUEST_PROXY) corsProxy = new URL(window.REQUEST_PROXY, location.origin).toString(); + if (!corsProxy && appSettings.value.corsProxy) corsProxy = appSettings.value.corsProxy; + if (!corsProxy) return url; + + if (corsProxy.includes("")) { + return corsProxy.replace("", "" + url); + } else if (corsProxy.includes("")) { + return corsProxy.replace("", encodeURIComponent("" + url)); + } else { + return corsProxy.endsWith("/") ? corsProxy + url : corsProxy + "/" + url; + } +} + +export function fetchWithProxy(url: URL | string, opts?: RequestInit) { + if (!appSettings.value.corsProxy && !window.REQUEST_PROXY) return fetch(url, opts); + + const u = typeof url === "string" ? convertToUrl(url) : url; + + // if its an onion domain try the request proxy first + if ((u.host.endsWith(".onion") || u.host.endsWith(".i2p")) && !proxyFailedHosts.has(u.host)) { + return fetch(createRequestProxyUrl(url), opts).catch((e) => { + proxyFailedHosts.add(u.host); + return fetch(url, opts); + }); + } + + // if the clear net request has failed. use the proxy + if (clearNetFailedHosts.has(u.host)) { + return fetch(createRequestProxyUrl(url), opts); + } + + // try clear net first and fallback to request proxy + return fetch(url, opts).catch((e) => { + clearNetFailedHosts.add(u.host); + return fetch(createRequestProxyUrl(url), opts); + }); +} diff --git a/src/hooks/use-open-graph-data.ts b/src/hooks/use-open-graph-data.ts index b4e173758..7e85b74ef 100644 --- a/src/hooks/use-open-graph-data.ts +++ b/src/hooks/use-open-graph-data.ts @@ -1,5 +1,5 @@ import { useAsync } from "react-use"; -import { fetchWithCorsFallback } from "../helpers/cors"; +import { fetchWithProxy } from "../helpers/request"; import type { OgObjectInteral } from "../lib/open-graph-scraper/types"; import useAppSettings from "./use-app-settings"; @@ -22,7 +22,7 @@ export default function useOpenGraphData(url: URL) { try { const controller = new AbortController(); - const res = await fetchWithCorsFallback(url, { signal: controller.signal }); + const res = await fetchWithProxy(url, { signal: controller.signal }); const contentType = res.headers.get("content-type"); if (contentType?.includes("text/html")) { diff --git a/src/services/dns-identity.ts b/src/services/dns-identity.ts index 1f9bd0f2b..e1ce83d22 100644 --- a/src/services/dns-identity.ts +++ b/src/services/dns-identity.ts @@ -2,7 +2,7 @@ import dayjs from "dayjs"; import db from "./db"; import _throttle from "lodash.throttle"; -import { fetchWithCorsFallback } from "../helpers/cors"; +import { fetchWithProxy } from "../helpers/request"; import SuperMap from "../classes/super-map"; import Subject from "../classes/subject"; @@ -42,7 +42,7 @@ class DnsIdentityService { const { name, domain } = parseAddress(address); if (!name || !domain) throw new Error("invalid address " + address); - const json = await fetchWithCorsFallback(`https://${domain}/.well-known/nostr.json?name=${name}`) + const json = await fetchWithProxy(`https://${domain}/.well-known/nostr.json?name=${name}`) .then((res) => res.json() as Promise) .then((json) => { // convert all keys in names, and relays to lower case diff --git a/src/services/lnurl-metadata.ts b/src/services/lnurl-metadata.ts index 00032c5c8..a5e31f43f 100644 --- a/src/services/lnurl-metadata.ts +++ b/src/services/lnurl-metadata.ts @@ -1,4 +1,4 @@ -import { fetchWithCorsFallback } from "../helpers/cors"; +import { fetchWithProxy } from "../helpers/request"; import { getLudEndpoint } from "../helpers/lnurl"; type LNURLPMetadata = { @@ -24,9 +24,7 @@ class LNURLMetadataService { const url = getLudEndpoint(addressOrLNURL); if (!url) return; try { - const metadata = await fetchWithCorsFallback(url).then( - (res) => res.json() as Promise, - ); + const metadata = await fetchWithProxy(url).then((res) => res.json() as Promise); if ((metadata as LNURLPMetadata).tag === "payRequest") { return metadata as LNURLPMetadata; } diff --git a/src/services/relay-info.ts b/src/services/relay-info.ts index 001145773..a9a843501 100644 --- a/src/services/relay-info.ts +++ b/src/services/relay-info.ts @@ -1,5 +1,5 @@ import db from "./db"; -import { fetchWithCorsFallback } from "../helpers/cors"; +import { fetchWithProxy } from "../helpers/request"; import { isHexKey } from "../helpers/nip19"; import { validateRelayURL } from "../helpers/relay"; @@ -26,7 +26,7 @@ async function fetchInfo(relay: string) { const url = validateRelayURL(relay); url.protocol = url.protocol === "ws:" ? "http" : "https"; - const infoDoc = await fetchWithCorsFallback(url, { headers: { Accept: "application/nostr+json" } }).then( + const infoDoc = await fetchWithProxy(url, { headers: { Accept: "application/nostr+json" } }).then( (res) => res.json() as Promise, ); diff --git a/src/types/window.d.ts b/src/types/window.d.ts index 1c90dc084..ec511bad4 100644 --- a/src/types/window.d.ts +++ b/src/types/window.d.ts @@ -1,5 +1,5 @@ interface Window { CACHE_RELAY_ENABLED: boolean; IMAGE_PROXY_PATH: string; - CORS_PROXY_PATH: string; + REQUEST_PROXY: string; } diff --git a/src/views/settings/privacy-settings.tsx b/src/views/settings/privacy-settings.tsx index 1c1fbc80a..c18379dad 100644 --- a/src/views/settings/privacy-settings.tsx +++ b/src/views/settings/privacy-settings.tsx @@ -17,7 +17,7 @@ import { import { useFormContext } from "react-hook-form"; import { safeUrl } from "../../helpers/parse"; import { AppSettings } from "../../services/settings/migrations"; -import { createCorsUrl } from "../../helpers/cors"; +import { createRequestProxyUrl } from "../../helpers/request"; import { SpyIcon } from "../../components/icons"; async function validateInvidiousUrl(url?: string) { @@ -30,12 +30,12 @@ async function validateInvidiousUrl(url?: string) { } } -async function validateCorsProxy(url?: string) { +async function validateRequestProxy(url?: string) { if (!url) return true; try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 2000); - const res = await fetch(createCorsUrl("https://example.com", url), { signal: controller.signal }); + const res = await fetch(createRequestProxyUrl("https://example.com", url), { signal: controller.signal }); return res.ok || "Cant reach instance"; } catch (e) { return "Cant reach instance"; @@ -124,16 +124,25 @@ export default function PrivacySettings() { - CORS Proxy - + Request Proxy + {window.REQUEST_PROXY ? ( + <> + {}} readOnly isDisabled /> + + This noStrudel version has the request proxy hard coded to {window.REQUEST_PROXY} + + + ) : ( + + )} {formState.errors.corsProxy && {formState.errors.corsProxy.message}} - This is used as a fallback ( to bypass CORS restrictions ) when verifying NIP-05 ids and fetching - open-graph metadata. + This is used as a fallback ( to bypass CORS restrictions ) or to make connections to .onion and .i2p + domains
This can either point to an instance of{" "} diff --git a/tsconfig.json b/tsconfig.json index c6544962e..f807c7fda 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": ["DOM", "DOM.Iterable", "ESNext"], + "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"], "allowJs": false, "skipLibCheck": true, "esModuleInterop": true, diff --git a/vite.config.ts b/vite.config.ts index ed28bf391..2fb4ed809 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -13,6 +13,14 @@ export default defineConfig({ react(), VitePWA({ registerType: "prompt", + // strategies: "injectManifest", + // srcDir: "src", + // filename: "sw.ts", + // devOptions: { + // // NOTE: ESM service workers is not supported by firefox + // type: "module", + // enabled: true, + // }, workbox: { // This increase the cache limit to 3mB maximumFileSizeToCacheInBytes: 2097152 * 1.5,