commit 7322c4781d4930e55382015472f4ed0c2c927d79 Author: florian <> Date: Sun Mar 17 13:24:12 2024 +0100 feat: Initial version of blossom based web server diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d754dc0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.env +node_modules +.github +build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad22606 --- /dev/null +++ b/.gitignore @@ -0,0 +1,177 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +build \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..bc84c45 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 120, + "useTabs": false, + "tabWidth": 2 +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b5b1b24 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# syntax=docker/dockerfile:1 +FROM node:20.11 as builder + +WORKDIR /app + +# Install dependencies +COPY ./package*.json . +ENV NODE_ENV=development +RUN npm install +COPY . . +RUN npm run build + +FROM node:20.11 + +WORKDIR /app + +ENV NODE_ENV=production +COPY ./package*.json . +RUN npm install + +COPY --from=builder ./app/build ./build + +EXPOSE 3010 + +ENV DEBUG="web*,koa*,ndk*" + +ENTRYPOINT [ "node", "build/index.js" ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..94a5aa5 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# 🌸 blossom-drive-webserver + +Serves a blossom-drive as a web page. +URL: https://sevrername.com/naddr1qvzqq....getnws2yc6ec/ + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..51c698d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2437 @@ +{ + "name": "blossom-drive-webserver", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "blossom-drive-webserver", + "license": "MIT", + "dependencies": { + "@koa/cors": "^5.0.0", + "@koa/router": "^12.0.1", + "@nostr-dev-kit/ndk": "^2.5.0", + "debug": "^4.3.4", + "events": "^3.3.0", + "follow-redirects": "^1.15.6", + "http-error": "^0.0.6", + "koa": "^2.15.1", + "lodash": "^4.17.21", + "node-cache": "^5.1.2", + "nostr-tools": "^2.3.1", + "websocket-polyfill": "^0.0.3", + "ws": "^8.16.0" + }, + "bin": { + "blossom-drive-webserver": "build/index.js" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/debug": "^4.1.12", + "@types/follow-redirects": "^1.14.4", + "@types/http-errors": "^2.0.4", + "@types/koa": "^2.15.0", + "@types/koa__cors": "^5.0.0", + "@types/koa__router": "^12.0.4", + "@types/lodash": "^4.17.0", + "@types/node": "^20.11.28", + "prettier": "^3.2.5", + "tsc-watch": "^6.0.4", + "typescript": "^5.4.2" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@koa/cors": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@koa/router": { + "version": "12.0.1", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "methods": "^1.1.2", + "path-to-regexp": "^6.2.1" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@noble/ciphers": { + "version": "0.5.1", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.2", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/secp256k1": { + "version": "2.0.0", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@nostr-dev-kit/ndk": { + "version": "2.5.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.3.1", + "@noble/secp256k1": "^2.0.0", + "@scure/base": "^1.1.1", + "debug": "^4.3.4", + "light-bolt11-decoder": "^3.0.0", + "node-fetch": "^3.3.1", + "nostr-tools": "^1.15.0", + "tseep": "^1.1.1", + "typescript-lru-cache": "^2.0.0", + "utf8-buffer": "^1.0.0", + "websocket-polyfill": "^0.0.3" + } + }, + "node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools": { + "version": "1.17.0", + "license": "Unlicense", + "dependencies": { + "@noble/ciphers": "0.2.0", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools/node_modules/@noble/ciphers": { + "version": "0.2.0", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools/node_modules/@noble/curves": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools/node_modules/@noble/hashes": { + "version": "1.3.1", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools/node_modules/@scure/base": { + "version": "1.1.1", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "1.1.5", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.3.1", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.3.1", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bun": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.0.8.tgz", + "integrity": "sha512-E6UWZuN4ymAxzUBWVIGDHJ3Zey7I8cMzDZ+cB1BqhZsmd1uPb9iAQzpWMruY1mKzsuD3R+dZPoBkZz8QL1KhSA==", + "dev": true, + "dependencies": { + "bun-types": "1.0.29" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-disposition": { + "version": "0.5.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cookies": { + "version": "0.9.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.43", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/follow-redirects": { + "version": "1.14.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-assert": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/koa": { + "version": "2.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa__cors": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/koa__router": { + "version": "12.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "3.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.11.28", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.9.12", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/send/node_modules/@types/mime": { + "version": "1.3.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/bun-types": { + "version": "1.0.29", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "~20.11.3", + "@types/ws": "~8.5.10" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookies": { + "version": "0.9.1", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/delegates": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/events": { + "version": "3.3.0", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "dev": true, + "license": "MIT" + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-assert": { + "version": "1.5.0", + "license": "MIT", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-error": { + "version": "0.0.6" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/keygrip": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa": { + "version": "2.15.1", + "license": "MIT", + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "license": "MIT" + }, + "node_modules/koa-convert": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa/node_modules/http-errors": { + "version": "1.8.1", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/light-bolt11-decoder": { + "version": "3.1.1", + "license": "MIT", + "dependencies": { + "@scure/base": "1.1.1" + } + }, + "node_modules/light-bolt11-decoder/node_modules/@scure/base": { + "version": "1.1.1", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "license": "MIT" + }, + "node_modules/map-stream": { + "version": "0.1.0", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/node-cache": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/node-cleanup": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nostr-tools": { + "version": "2.3.1", + "license": "Unlicense", + "dependencies": { + "@noble/ciphers": "^0.5.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "optionalDependencies": { + "nostr-wasm": "v0.1.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/nostr-tools/node_modules/@noble/hashes": { + "version": "1.3.1", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@scure/base": { + "version": "1.1.1", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/nostr-wasm": { + "version": "0.1.0", + "license": "MIT", + "optional": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/only": { + "version": "0.0.2" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "6.2.1", + "license": "MIT" + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "dev": true, + "license": [ + "MIT", + "Apache2" + ], + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/ps-tree": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "event-stream": "=3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/split": { + "version": "0.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "~0.1.1" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/through": { + "version": "2.3.8", + "dev": true, + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsc-watch": { + "version": "6.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "node-cleanup": "^2.1.2", + "ps-tree": "^1.2.0", + "string-argv": "^0.3.1" + }, + "bin": { + "tsc-watch": "dist/lib/tsc-watch.js" + }, + "engines": { + "node": ">=12.12.0" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/tseep": { + "version": "1.2.1", + "license": "MIT" + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/tstl": { + "version": "2.5.13", + "license": "MIT" + }, + "node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "5.4.2", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-lru-cache": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT" + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/utf8-buffer": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket-polyfill": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz", + "integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==", + "dependencies": { + "tstl": "^2.0.7", + "websocket": "^1.0.28" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/ylru": { + "version": "1.3.2", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + } + }, + "dependencies": { + "@koa/cors": { + "version": "5.0.0", + "requires": { + "vary": "^1.1.2" + } + }, + "@koa/router": { + "version": "12.0.1", + "requires": { + "debug": "^4.3.4", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "methods": "^1.1.2", + "path-to-regexp": "^6.2.1" + } + }, + "@noble/ciphers": { + "version": "0.5.1" + }, + "@noble/curves": { + "version": "1.2.0", + "requires": { + "@noble/hashes": "1.3.2" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.3.2" + } + } + }, + "@noble/hashes": { + "version": "1.4.0" + }, + "@noble/secp256k1": { + "version": "2.0.0" + }, + "@nostr-dev-kit/ndk": { + "version": "2.5.0", + "requires": { + "@noble/hashes": "^1.3.1", + "@noble/secp256k1": "^2.0.0", + "@scure/base": "^1.1.1", + "debug": "^4.3.4", + "light-bolt11-decoder": "^3.0.0", + "node-fetch": "^3.3.1", + "nostr-tools": "^1.15.0", + "tseep": "^1.1.1", + "typescript-lru-cache": "^2.0.0", + "utf8-buffer": "^1.0.0", + "websocket-polyfill": "^0.0.3" + }, + "dependencies": { + "nostr-tools": { + "version": "1.17.0", + "requires": { + "@noble/ciphers": "0.2.0", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "dependencies": { + "@noble/ciphers": { + "version": "0.2.0" + }, + "@noble/curves": { + "version": "1.1.0", + "requires": { + "@noble/hashes": "1.3.1" + } + }, + "@noble/hashes": { + "version": "1.3.1" + }, + "@scure/base": { + "version": "1.1.1" + } + } + } + } + }, + "@scure/base": { + "version": "1.1.5" + }, + "@scure/bip32": { + "version": "1.3.1", + "requires": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "dependencies": { + "@noble/curves": { + "version": "1.1.0", + "requires": { + "@noble/hashes": "1.3.1" + } + }, + "@noble/hashes": { + "version": "1.3.1" + } + } + }, + "@scure/bip39": { + "version": "1.2.1", + "requires": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.3.1" + } + } + }, + "@types/accepts": { + "version": "1.3.7", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/body-parser": { + "version": "1.19.5", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bun": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.0.8.tgz", + "integrity": "sha512-E6UWZuN4ymAxzUBWVIGDHJ3Zey7I8cMzDZ+cB1BqhZsmd1uPb9iAQzpWMruY1mKzsuD3R+dZPoBkZz8QL1KhSA==", + "dev": true, + "requires": { + "bun-types": "1.0.29" + } + }, + "@types/connect": { + "version": "3.4.38", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/content-disposition": { + "version": "0.5.8", + "dev": true + }, + "@types/cookies": { + "version": "0.9.0", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "@types/debug": { + "version": "4.1.12", + "dev": true, + "requires": { + "@types/ms": "*" + } + }, + "@types/express": { + "version": "4.17.21", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.43", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/follow-redirects": { + "version": "1.14.4", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/http-assert": { + "version": "1.5.5", + "dev": true + }, + "@types/http-errors": { + "version": "2.0.4", + "dev": true + }, + "@types/keygrip": { + "version": "1.0.6", + "dev": true + }, + "@types/koa": { + "version": "2.15.0", + "dev": true, + "requires": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "@types/koa__cors": { + "version": "5.0.0", + "dev": true, + "requires": { + "@types/koa": "*" + } + }, + "@types/koa__router": { + "version": "12.0.4", + "dev": true, + "requires": { + "@types/koa": "*" + } + }, + "@types/koa-compose": { + "version": "3.2.8", + "dev": true, + "requires": { + "@types/koa": "*" + } + }, + "@types/lodash": { + "version": "4.17.0", + "dev": true + }, + "@types/mime": { + "version": "3.0.4", + "dev": true + }, + "@types/ms": { + "version": "0.7.34", + "dev": true + }, + "@types/node": { + "version": "20.11.28", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/qs": { + "version": "6.9.12", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.7", + "dev": true + }, + "@types/send": { + "version": "0.17.4", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + }, + "dependencies": { + "@types/mime": { + "version": "1.3.5", + "dev": true + } + } + }, + "@types/serve-static": { + "version": "1.15.5", + "dev": true, + "requires": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/ws": { + "version": "8.5.10", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "accepts": { + "version": "1.3.8", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "bun-types": { + "version": "1.0.29", + "dev": true, + "requires": { + "@types/node": "~20.11.3", + "@types/ws": "~8.5.10" + } + }, + "cache-content-type": { + "version": "1.0.1", + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "clone": { + "version": "2.1.2" + }, + "co": { + "version": "4.6.0" + }, + "content-disposition": { + "version": "0.5.4", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5" + }, + "cookies": { + "version": "0.9.1", + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + } + }, + "cross-spawn": { + "version": "7.0.3", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "requires": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + } + }, + "data-uri-to-buffer": { + "version": "4.0.1" + }, + "debug": { + "version": "4.3.4", + "requires": { + "ms": "2.1.2" + } + }, + "deep-equal": { + "version": "1.0.1" + }, + "delegates": { + "version": "1.0.0" + }, + "depd": { + "version": "2.0.0" + }, + "destroy": { + "version": "1.2.0" + }, + "duplexer": { + "version": "0.1.2", + "dev": true + }, + "ee-first": { + "version": "1.1.1" + }, + "encodeurl": { + "version": "1.0.2" + }, + "es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "requires": { + "d": "^1.0.2", + "ext": "^1.7.0" + } + }, + "escape-html": { + "version": "1.0.3" + }, + "esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + } + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "event-stream": { + "version": "3.3.4", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "events": { + "version": "3.3.0" + }, + "ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "requires": { + "type": "^2.7.2" + } + }, + "fetch-blob": { + "version": "3.2.0", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, + "follow-redirects": { + "version": "1.15.6" + }, + "formdata-polyfill": { + "version": "4.0.10", + "requires": { + "fetch-blob": "^3.1.2" + } + }, + "fresh": { + "version": "0.5.2" + }, + "from": { + "version": "0.1.7", + "dev": true + }, + "has-symbols": { + "version": "1.0.3" + }, + "has-tostringtag": { + "version": "1.0.2", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "http-assert": { + "version": "1.5.0", + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "dependencies": { + "http-errors": { + "version": "1.8.1", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2" + } + } + } + } + }, + "http-error": { + "version": "0.0.6" + }, + "http-errors": { + "version": "2.0.0", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "statuses": { + "version": "2.0.1" + } + } + }, + "inherits": { + "version": "2.0.4" + }, + "is-generator-function": { + "version": "1.0.10", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "isexe": { + "version": "2.0.0", + "dev": true + }, + "keygrip": { + "version": "1.1.0", + "requires": { + "tsscmp": "1.0.6" + } + }, + "koa": { + "version": "2.15.1", + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "dependencies": { + "http-errors": { + "version": "1.8.1", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2" + } + } + } + } + }, + "koa-compose": { + "version": "4.1.0" + }, + "koa-convert": { + "version": "2.0.0", + "requires": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + } + }, + "light-bolt11-decoder": { + "version": "3.1.1", + "requires": { + "@scure/base": "1.1.1" + }, + "dependencies": { + "@scure/base": { + "version": "1.1.1" + } + } + }, + "lodash": { + "version": "4.17.21" + }, + "map-stream": { + "version": "0.1.0", + "dev": true + }, + "media-typer": { + "version": "0.3.0" + }, + "methods": { + "version": "1.1.2" + }, + "mime-db": { + "version": "1.52.0" + }, + "mime-types": { + "version": "2.1.35", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.1.2" + }, + "negotiator": { + "version": "0.6.3" + }, + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node-cache": { + "version": "5.1.2", + "requires": { + "clone": "2.x" + } + }, + "node-cleanup": { + "version": "2.1.2", + "dev": true + }, + "node-domexception": { + "version": "1.0.0" + }, + "node-fetch": { + "version": "3.3.2", + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, + "node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==" + }, + "nostr-tools": { + "version": "2.3.1", + "requires": { + "@noble/ciphers": "^0.5.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1", + "nostr-wasm": "v0.1.0" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.3.1" + }, + "@scure/base": { + "version": "1.1.1" + } + } + }, + "nostr-wasm": { + "version": "0.1.0", + "optional": true + }, + "on-finished": { + "version": "2.4.1", + "requires": { + "ee-first": "1.1.1" + } + }, + "only": { + "version": "0.0.2" + }, + "parseurl": { + "version": "1.3.3" + }, + "path-key": { + "version": "3.1.1", + "dev": true + }, + "path-to-regexp": { + "version": "6.2.1" + }, + "pause-stream": { + "version": "0.0.11", + "dev": true, + "requires": { + "through": "~2.3" + } + }, + "prettier": { + "version": "3.2.5", + "dev": true + }, + "ps-tree": { + "version": "1.2.0", + "dev": true, + "requires": { + "event-stream": "=3.3.4" + } + }, + "safe-buffer": { + "version": "5.2.1" + }, + "setprototypeof": { + "version": "1.2.0" + }, + "shebang-command": { + "version": "2.0.0", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "dev": true + }, + "split": { + "version": "0.3.3", + "dev": true, + "requires": { + "through": "2" + } + }, + "statuses": { + "version": "1.5.0" + }, + "stream-combiner": { + "version": "0.0.4", + "dev": true, + "requires": { + "duplexer": "~0.1.1" + } + }, + "string-argv": { + "version": "0.3.2", + "dev": true + }, + "through": { + "version": "2.3.8", + "dev": true + }, + "toidentifier": { + "version": "1.0.1" + }, + "tsc-watch": { + "version": "6.0.4", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "node-cleanup": "^2.1.2", + "ps-tree": "^1.2.0", + "string-argv": "^0.3.1" + } + }, + "tseep": { + "version": "1.2.1" + }, + "tsscmp": { + "version": "1.0.6" + }, + "tstl": { + "version": "2.5.13" + }, + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, + "type-is": { + "version": "1.6.18", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "5.4.2", + "devOptional": true + }, + "typescript-lru-cache": { + "version": "2.0.0" + }, + "undici-types": { + "version": "5.26.5", + "dev": true + }, + "utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "utf8-buffer": { + "version": "1.0.0" + }, + "vary": { + "version": "1.1.2" + }, + "web-streams-polyfill": { + "version": "3.3.3" + }, + "websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "requires": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "websocket-polyfill": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz", + "integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==", + "requires": { + "tstl": "^2.0.7", + "websocket": "^1.0.28" + } + }, + "which": { + "version": "2.0.2", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "requires": {} + }, + "yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==" + }, + "ylru": { + "version": "1.3.2" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5f09276 --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "blossom-drive-webserver", + "description": "A web server that servces the content of a blossom drive", + "main": "index.js", + "author": "florian", + "license": "MIT", + "bin": "build/index.js", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "@types/debug": "^4.1.12", + "@types/follow-redirects": "^1.14.4", + "@types/http-errors": "^2.0.4", + "@types/koa": "^2.15.0", + "@types/koa__cors": "^5.0.0", + "@types/koa__router": "^12.0.4", + "@types/lodash": "^4.17.0", + "@types/node": "^20.11.28", + "prettier": "^3.2.5", + "tsc-watch": "^6.0.4", + "typescript": "^5.4.2" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@koa/cors": "^5.0.0", + "@koa/router": "^12.0.1", + "@nostr-dev-kit/ndk": "^2.5.0", + "debug": "^4.3.4", + "events": "^3.3.0", + "follow-redirects": "^1.15.6", + "http-error": "^0.0.6", + "koa": "^2.15.1", + "lodash": "^4.17.21", + "node-cache": "^5.1.2", + "nostr-tools": "^2.3.1", + "websocket-polyfill": "^0.0.3", + "ws": "^8.16.0" + }, + "scripts": { + "start": "node build/index.js", + "build": "tsc", + "dev": "export DEBUG=web*,ndk*,koa*;tsc-watch --onSuccess \"node ./build/index.js\"", + "format": "prettier -w ." + }, + "files": [ + "build" + ] +} diff --git a/src/cdn.ts b/src/cdn.ts new file mode 100644 index 0000000..9cdcb91 --- /dev/null +++ b/src/cdn.ts @@ -0,0 +1,43 @@ +import debug from "debug"; +import type { IncomingMessage } from "http"; +import followRedirects from "follow-redirects"; +import uniq from "lodash/uniq.js"; + +const log = debug("web:discover:upstream"); + +export async function searchCdn(servers: string[], hash: string) { + const uniqueSevers = uniq(servers); + log("Looking for", hash); + for (const server of uniqueSevers) { + try { + log("Checking CDN server", server); + return await checkCDN(server, hash); + } catch (e) { + log("Ingoring error", e); + } // Ignore errors during lookup + } + log("No CDN with the content found."); +} + +async function checkCDN(cdn: string, hash: string): Promise { + return new Promise((resolve, reject) => { + const url = new URL(hash, cdn); + const backend = url.protocol === "https:" ? followRedirects.https : followRedirects.http; + + var req = backend + .get(url.toString(), (res) => { + if (!res.statusCode) return reject(); + if (res.statusCode < 200 || res.statusCode >= 400) { + res.destroy(); + reject(new Error(res.statusMessage)); + } else { + resolve(res); + log("Found", hash, "at", cdn); + } + }) + .on("error", function (error) { + log("Ignoring error", error); + }) + .end(); + }); +} diff --git a/src/drive.ts b/src/drive.ts new file mode 100644 index 0000000..24df736 --- /dev/null +++ b/src/drive.ts @@ -0,0 +1,62 @@ +import NDK from "@nostr-dev-kit/ndk"; +import debug from "debug"; +import NodeCache from "node-cache"; +import { Drive } from "./types.js"; + +const driveCache = new NodeCache({ stdTTL: 30 }); // 30s for development + +const log = debug("web:drive:nostr"); + +const ndk = new NDK({ + explicitRelayUrls: [ + "wss://nostrue.com", + "wss://relay.damus.io", + //"wss://nostr.wine", + "wss://nos.lol", + // "wss://nostr-pub.wellorder.net", + ], +}); + +ndk.connect(); + +export const readDrive = async (kind: number, pubkey: string, driveIdentifier: string): Promise => { + const driveKey = `${kind}-${pubkey}-${driveIdentifier}`; + if (driveCache.has(driveKey)) { + log("using cached drive data for " + driveKey); + return driveCache.get(driveKey); + } + + log("Fetching drive event."); + + const filter = { + kinds: [kind], + authors: [pubkey], + "#d": [driveIdentifier], // name.toLowerCase().replaceAll(/\s/g, "-") || nanoid(8); + }; + log(filter); + const event = await ndk.fetchEvent(filter); + log("fetch finsihed"); + + if (event) { + const treeTags = event.tags.filter((t) => t[0] === "x" || t[0] === "folder"); + const files = treeTags.map((t) => ({ + hash: t[1], + path: t[2], + size: parseInt(t[3], 10) || 0, + mimeType: t[4], + })); + + // log(files); + + const servers = event.tags.filter((t) => t[0] === "r" && t[1]).map((t) => new URL("/", t[1]).toString()) || []; + + // log(servers); + + const drive = { files, servers }; + driveCache.set(driveKey, drive); + + return drive; + } else { + log("no drive event found."); + } +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..b37a4d3 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,134 @@ +import "websocket-polyfill"; +import Koa, { HttpError } from "koa"; +import debug from "debug"; +import Router from "@koa/router"; +import { nip19 } from "nostr-tools"; +import { AddressPointer } from "nostr-tools/nip19"; +import { Drive, FileMeta } from "./types.js"; +import { readDrive } from "./drive.js"; +import { searchCdn } from "./cdn.js"; + +const log = debug("web"); +const app = new Koa(); +const router = new Router(); + +// const cacheAdapter = new NDKCacheAdapterDexie({ dbName: "ndk-cache" }); +const additionalServers = ["https://cdn.hzrd149.com", "https://cdn.satellite.earth"]; + +const findFile = (drive: Drive, filePathToSearch: string): FileMeta | undefined => { + return drive.files.find((f) => f.path == "/" + filePathToSearch); +}; + +const findFolderContents = (drive: Drive, filePathToSearch: string): FileMeta[] => { + // TODO filter name + return drive.files.filter((f) => f.path.startsWith("/" + filePathToSearch)); +}; + +// handle errors +app.use(async (ctx, next) => { + try { + await next(); + } catch (err) { + if (err instanceof HttpError) { + const status = (ctx.status = err.status || 500); + if (status >= 500) console.error(err.stack); + ctx.body = status > 500 ? { message: "Something went wrong" } : { message: err.message }; + } else { + console.log(err); + ctx.status = 500; + ctx.body = { message: "Something went wrong" }; + } + } +}); + +router.get("(.*)", async (ctx, next) => { + log(`serving: host:${ctx.hostname} path:${ctx.path}`); + + if (ctx.path == "/favicon.ico") { + return next(); + } + + // naddr1qvzqqqrhvvpzpd7x76g4e756vtlldg0syczdazxz83kxcmgm3a3v0nqswj0nql5pqqzhgetnwseqmkcq93 + // test2 + // 1708411351353.jpeg + // http://localhost:3010/naddr1qvzqqqrhvvpzpd7x76g4e756vtlldg0syczdazxz83kxcmgm3a3v0nqswj0nql5pqqzhgetnwseqmkcq93/test2/1708411351353.jpeg + + const [naddr, ...path] = ctx.path.replace(/^\//, "").split("/"); + if (!naddr || !naddr.startsWith("naddr")) { + ctx.status = 400; + ctx.message = "A naddr needs to be specific in the url."; + return; + } + + let searchPath = decodeURI(path.join("/")); + log(`naddr: ${naddr} path: ${searchPath}`); + + /* TODO try lookup of index.html and fallback to list, if not found + if (searchPath.endsWith("/")) { + searchPath = searchPath + "index.html"; + } + if (searchPath == "") { + searchPath = searchPath + "/index.html"; + }*/ + + // decode naddr + const { pubkey, kind, identifier } = nip19.decode(naddr).data as AddressPointer; + + // fetch drive descriptor -> ndk + const drive = await readDrive(kind, pubkey, identifier); + log(`drive: ${drive}`); + + if (drive) { + // lookup file in file-list + const fileMeta = findFile(drive, searchPath); + // log(`fileMeta: ${fileMeta}`); + + if (fileMeta) { + const { hash, mimeType } = fileMeta; + + // lookup media sevrers for user -> ndk (optional) + const cdnSource = await searchCdn([...drive.servers, ...additionalServers], hash); + + //log(cdnSource); + + if (cdnSource) { + ctx.set({ + "Content-Type": mimeType, + "Cache-Control": "no-cache", + Connection: "keep-alive", + }); + ctx.body = cdnSource; + } else { + log("no CDN server found for blob: " + hash); + } + } else { + const folder = findFolderContents(drive, searchPath); + log("file not found in drive: " + searchPath); + ctx.status = 404; + ctx.set("Content-Type", "text/html"); + const folderList = folder.map((f) => `
  • ${f.path}
  • `); + ctx.body = `

    Index of ${searchPath || "/"}

    `; + } + } + + // fetch file from mediaserver and return to the caller +}); + +app.use(router.routes()).use(router.allowedMethods()); + +const port = process.env.PORT || 3010; +log("Starting blossom-drive-webserver on port " + port); +app.listen(port); + +async function shutdown() { + log("Shutting down blossom-drive-webserver..."); + process.exit(0); +} + +// add this handler before emitting any events +process.on("uncaughtException", function (err) { + console.error("UNCAUGHT EXCEPTION - keeping process alive:", err); +}); + +process.addListener("SIGTERM", shutdown); +process.addListener("SIGINT", shutdown); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..5ac9ca6 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,12 @@ + +export type FileMeta = { + hash: string; + path: string; + size: number; + mimeType: string; + }; + + export type Drive = { + servers: string[]; + files: FileMeta[]; + }; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e8b7ce2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "NodeNext", + "target": "es2020", + "outDir": "build", + "skipLibCheck": true, + "strict": true, + "moduleResolution": "nodenext", + "allowSyntheticDefaultImports": true + }, + "include": ["src"] +}