aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-28 14:14:30 -0800
committerAdam Soltys <[email protected]>2026-02-28 14:14:30 -0800
commita1632ae402d11fdd7ffc360de97c55e0b4fad677 (patch)
treeaad5a1ea73f95dcd055c1d0b4ebf2708c36b4e01
parent76c7e52172f3dc25ee109434157d4d982ad9d2bd (diff)
downloadcoinos-server-a1632ae402d11fdd7ffc360de97c55e0b4fad677.tar.gz
coinos-server-a1632ae402d11fdd7ffc360de97c55e0b4fad677.zip
migrate to hono
-rw-r--r--.github/workflows/lint.yml8
-rw-r--r--.husky/pre-commit1
-rw-r--r--bun.lock162
-rw-r--r--index.ts62
-rw-r--r--lib/app.ts225
-rw-r--r--lib/auth.ts87
-rw-r--r--lib/lightning.ts4
-rw-r--r--lib/sockets.ts137
-rw-r--r--lib/upload.ts17
-rw-r--r--lib/utils.ts2
-rw-r--r--package.json17
-rw-r--r--routes/ecash.ts63
-rw-r--r--routes/email.ts7
-rw-r--r--routes/info.ts16
-rw-r--r--routes/invoices.ts51
-rw-r--r--routes/items.ts59
-rw-r--r--routes/lnurl.ts67
-rw-r--r--routes/locations.ts20
-rw-r--r--routes/nostr.ts139
-rw-r--r--routes/payments.ts358
-rw-r--r--routes/rates.ts12
-rw-r--r--routes/shopify.ts19
-rw-r--r--routes/square.ts24
-rw-r--r--routes/users.ts579
24 files changed, 954 insertions, 1182 deletions
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index c7c9dd41..23049352 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -10,6 +10,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.head_ref || github.ref_name }}
+ token: ${{ secrets.GITHUB_TOKEN }}
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- - run: bun run lint
+ - run: bun run lint:fix
+ - uses: stefanzweifel/git-auto-commit-action@v5
+ with:
+ commit_message: "style: auto-fix lint & formatting"
diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100644
index ea5a55b6..00000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1 +0,0 @@
-bunx lint-staged
diff --git a/bun.lock b/bun.lock
index 690aa8a8..1dae26f8 100644
--- a/bun.lock
+++ b/bun.lock
@@ -10,14 +10,6 @@
"@aws-sdk/client-ses": "^3.997.0",
"@cashu/cashu-ts": "^2.9.0",
"@coinos/rpc": "^0.0.6",
- "@fastify/cors": "^11.2.0",
- "@fastify/http-proxy": "^11.4.1",
- "@fastify/multipart": "^9.4.0",
- "@fastify/passport": "^3.0.2",
- "@fastify/rate-limit": "^10.3.0",
- "@fastify/secure-session": "^8.3.0",
- "@fastify/static": "^9.0.0",
- "@fastify/websocket": "^11.2.0",
"@noble/hashes": "^2.0.1",
"@scure/base": "^2.0.0",
"@scure/bip32": "^2.0.1",
@@ -26,19 +18,17 @@
"bech32": "^2.0.0",
"clightning-client": "github:asoltys/lightning-client-js#master",
"eventsource": "^4.1.0",
- "fastify": "^5.7.4",
"file-type": "^21.3.0",
"fluent-ffmpeg": "^2.1.3",
"got": "^14.6.6",
"handlebars": "^4.7.8",
+ "hono": "^4.7.0",
"jsonwebtoken": "^9.0.3",
"lnurl": "^0.27.0",
"mqtt": "^5.15.0",
"nostr": "^0.2.8",
"nostr-tools": "^2.23.1",
"otplib": "^12.0.1",
- "passport-jwt": "^4.0.1",
- "passport-local": "1.0.0",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
"pump": "^3.0.3",
@@ -166,48 +156,6 @@
"@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
- "@fastify/accept-negotiator": ["@fastify/[email protected]", "", {}, "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ=="],
-
- "@fastify/ajv-compiler": ["@fastify/[email protected]", "", { "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A=="],
-
- "@fastify/busboy": ["@fastify/[email protected]", "", {}, "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA=="],
-
- "@fastify/cookie": ["@fastify/[email protected]", "", { "dependencies": { "cookie": "^1.0.0", "fastify-plugin": "^5.0.0" } }, "sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA=="],
-
- "@fastify/cors": ["@fastify/[email protected]", "", { "dependencies": { "fastify-plugin": "^5.0.0", "toad-cache": "^3.7.0" } }, "sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw=="],
-
- "@fastify/deepmerge": ["@fastify/[email protected]", "", {}, "sha512-aO5giNgFN+rD4fMUAkro9nEL7c9gh5Q3lh0ZGKMDAhQAytf22HLicF/qZ2EYTDmH+XL2WvdazwBfOdmp6NiwBg=="],
-
- "@fastify/error": ["@fastify/[email protected]", "", {}, "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ=="],
-
- "@fastify/fast-json-stringify-compiler": ["@fastify/[email protected]", "", { "dependencies": { "fast-json-stringify": "^6.0.0" } }, "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ=="],
-
- "@fastify/flash": ["@fastify/[email protected]", "", { "dependencies": { "fastify-plugin": "^5.0.0" } }, "sha512-MyUvkiIR9zRqfMPCuWeTf8uyYpPhL+eZH4oSTxvqtbW+ifbo1Bk1SJtlrxmFgu52ZAAKUnYtI5OuSY9/GR22SA=="],
-
- "@fastify/forwarded": ["@fastify/[email protected]", "", {}, "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw=="],
-
- "@fastify/http-proxy": ["@fastify/[email protected]", "", { "dependencies": { "@fastify/reply-from": "^12.5.0", "fast-querystring": "^1.1.2", "fastify-plugin": "^5.1.0", "ws": "^8.18.3" } }, "sha512-sMYEIB0c7qCYutpZyS12c8xnVgmEMSUUVU2XjcNq2JzHf6Hta1BWcpnG5FXxR3WEm48uZNCi0MxnIYtwjwd21Q=="],
-
- "@fastify/merge-json-schemas": ["@fastify/[email protected]", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A=="],
-
- "@fastify/multipart": ["@fastify/[email protected]", "", { "dependencies": { "@fastify/busboy": "^3.0.0", "@fastify/deepmerge": "^3.0.0", "@fastify/error": "^4.0.0", "fastify-plugin": "^5.0.0", "secure-json-parse": "^4.0.0" } }, "sha512-Z404bzZeLSXTBmp/trCBuoVFX28pM7rhv849Q5TsbTFZHuk1lc4QjQITTPK92DKVpXmNtJXeHSSc7GYvqFpxAQ=="],
-
- "@fastify/passport": ["@fastify/[email protected]", "", { "dependencies": { "@fastify/flash": "^6.0.0", "fastify-plugin": "^5.0.0" } }, "sha512-pUzOxHTaTta6RvtbD4Ct4uTH7wWfgFtyj8apTu+IMe0w67qm5Q5fZsESOVdcC5IothVEySf54fyM9qQ5VNHP3A=="],
-
- "@fastify/proxy-addr": ["@fastify/[email protected]", "", { "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" } }, "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw=="],
-
- "@fastify/rate-limit": ["@fastify/[email protected]", "", { "dependencies": { "@lukeed/ms": "^2.0.2", "fastify-plugin": "^5.0.0", "toad-cache": "^3.7.0" } }, "sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q=="],
-
- "@fastify/reply-from": ["@fastify/[email protected]", "", { "dependencies": { "@fastify/error": "^4.0.0", "end-of-stream": "^1.4.4", "fast-content-type-parse": "^3.0.0", "fast-querystring": "^1.1.2", "fastify-plugin": "^5.0.1", "toad-cache": "^3.7.0", "undici": "^7.0.0" } }, "sha512-NIV5YiQR5eNU0dg0G050yHpxb1D1HZ1XD8gIcpyT74qDHYLXDD913VjnfzwI7bbiZ/F+cxHkbduC22K6rfXywA=="],
-
- "@fastify/secure-session": ["@fastify/[email protected]", "", { "dependencies": { "@fastify/cookie": "^11.0.1", "fastify-plugin": "^5.0.0", "sodium-native": "^5.0.1" }, "bin": { "secure-session": "genkey.js" } }, "sha512-jyXbYWAp5MCaCA+7idsV+Op3kMN/oDtskZ0SE3QPl53Lo0GFF7p/lk1UEliGaT0OSX+RGSrd2gQlUu1ta1t7yQ=="],
-
- "@fastify/send": ["@fastify/[email protected]", "", { "dependencies": { "@lukeed/ms": "^2.0.2", "escape-html": "~1.0.3", "fast-decode-uri-component": "^1.0.1", "http-errors": "^2.0.0", "mime": "^3" } }, "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw=="],
-
- "@fastify/static": ["@fastify/[email protected]", "", { "dependencies": { "@fastify/accept-negotiator": "^2.0.0", "@fastify/send": "^4.0.0", "content-disposition": "^1.0.1", "fastify-plugin": "^5.0.0", "fastq": "^1.17.1", "glob": "^13.0.0" } }, "sha512-r64H8Woe/vfilg5RTy7lwWlE8ZZcTrc3kebYFMEUBrMqlydhQyoiExQXdYAy2REVpST/G35+stAM8WYp1WGmMA=="],
-
- "@fastify/websocket": ["@fastify/[email protected]", "", { "dependencies": { "duplexify": "^4.1.3", "fastify-plugin": "^5.0.0", "ws": "^8.16.0" } }, "sha512-3HrDPbAG1CzUCqnslgJxppvzaAZffieOVbLp1DAy1huCSynUWPifSvfdEDUR8HlJLp3sp1A36uOM2tJogADS8w=="],
-
"@hexagon/base64": ["@hexagon/[email protected]", "", {}, "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="],
"@img/sharp-darwin-arm64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
@@ -256,8 +204,6 @@
"@levischuck/tiny-cbor": ["@levischuck/[email protected]", "", {}, "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="],
- "@lukeed/ms": ["@lukeed/[email protected]", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="],
-
"@marcbachmann/cel-js": ["@marcbachmann/[email protected]", "", { "bin": { "cel-evaluate": "bin/cel-evaluate.js" } }, "sha512-P6o26TvjStT8V8+8EF+yq9Pp7ZFV00bpiUMbssr76XbIZGxaB+NNWeBp6WNxOrR9gp0JPzvJueCKHpOs5LE9PQ=="],
"@noble/ciphers": ["@noble/[email protected]", "", {}, "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw=="],
@@ -504,16 +450,10 @@
"abort-controller": ["[email protected]", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
- "abstract-logging": ["[email protected]", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="],
-
"accepts": ["[email protected]", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"agent-base": ["[email protected]", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
- "ajv": ["[email protected]", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
-
- "ajv-formats": ["[email protected]", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
-
"ansi-escapes": ["[email protected]", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
"ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
@@ -536,16 +476,8 @@
"available-typed-arrays": ["[email protected]", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
- "avvio": ["[email protected]", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw=="],
-
"axios": ["[email protected]", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
- "bare-addon-resolve": ["[email protected]", "", { "dependencies": { "bare-module-resolve": "^1.10.0", "bare-semver": "^1.0.0" }, "peerDependencies": { "bare-url": "*" }, "optionalPeers": ["bare-url"] }, "sha512-cIo93Y4Maw8ZNrfq5qlvRBqCHUyeSzMyFBqJRNi91SOspeWiFq3ixiq0ExYWmtOBauIkCLod2Mng73xr7fmkXA=="],
-
- "bare-module-resolve": ["[email protected]", "", { "dependencies": { "bare-semver": "^1.0.0" }, "peerDependencies": { "bare-url": "*" }, "optionalPeers": ["bare-url"] }, "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg=="],
-
- "bare-semver": ["[email protected]", "", {}, "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA=="],
-
"base-x": ["[email protected]", "", {}, "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw=="],
"base64-js": ["[email protected]", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
@@ -634,11 +566,11 @@
"concat-stream": ["[email protected]", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="],
- "content-disposition": ["[email protected]", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
+ "content-disposition": ["[email protected]", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
"content-type": ["[email protected]", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
- "cookie": ["[email protected]", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
+ "cookie": ["[email protected]", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
"cookie-signature": ["[email protected]", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="],
@@ -658,8 +590,6 @@
"depd": ["[email protected]", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
- "dequal": ["[email protected]", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
-
"destroy": ["[email protected]", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
"detect-browser": ["[email protected]", "", {}, "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w=="],
@@ -670,8 +600,6 @@
"dunder-proto": ["[email protected]", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
- "duplexify": ["[email protected]", "", { "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", "stream-shift": "^1.0.2" } }, "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA=="],
-
"ecdsa-sig-formatter": ["[email protected]", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
"ee-first": ["[email protected]", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
@@ -712,40 +640,20 @@
"express-handlebars": ["[email protected]", "", { "dependencies": { "glob": "^8.1.0", "graceful-fs": "^4.2.10", "handlebars": "^4.7.7" } }, "sha512-iYeMFpc/hMD+E6FNAZA5fgWeXnXr4rslOSPkeEV6TwdmpJ5lEXuWX0u9vFYs31P2MURctQq2batR09oeNj0LIg=="],
- "fast-content-type-parse": ["[email protected]", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
-
"fast-copy": ["[email protected]", "", {}, "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw=="],
- "fast-decode-uri-component": ["[email protected]", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
-
- "fast-deep-equal": ["[email protected]", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
-
- "fast-json-stringify": ["[email protected]", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA=="],
-
- "fast-querystring": ["[email protected]", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
-
"fast-safe-stringify": ["[email protected]", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
"fast-unique-numbers": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.28.6", "tslib": "^2.8.1" } }, "sha512-3Mtq8p1zQinjGyWfKeuBunbuFoixG72AUkk4VvzbX4ykCW9Q4FzRaNyIlfQhUjnKw2ARVP+/CKnoyr6wfHftig=="],
- "fast-uri": ["[email protected]", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
-
"fast-xml-parser": ["[email protected]", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
- "fastify": ["[email protected]", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA=="],
-
- "fastify-plugin": ["[email protected]", "", {}, "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw=="],
-
- "fastq": ["[email protected]", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
-
"file-type": ["[email protected]", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="],
"fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"finalhandler": ["[email protected]", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg=="],
- "find-my-way": ["[email protected]", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-5Ye4vHsypZRYtS01ob/iwHzGRUDELlsoCftI/OZFhcLs1M0tkGPcXldE80TAZC5yYuJMBPJQQ43UHlqbJWiX2w=="],
-
"fluent-ffmpeg": ["[email protected]", "", { "dependencies": { "async": "^0.2.9", "which": "^1.1.1" } }, "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q=="],
"follow-redirects": ["[email protected]", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
@@ -776,7 +684,7 @@
"get-stream": ["[email protected]", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
- "glob": ["[email protected]", "", { "dependencies": { "minimatch": "^10.1.2", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w=="],
+ "glob": ["[email protected]", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="],
"glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@@ -806,6 +714,8 @@
"hmac-drbg": ["[email protected]", "", { "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg=="],
+ "hono": ["[email protected]", "", {}, "sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg=="],
+
"http-cache-semantics": ["[email protected]", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
"http-errors": ["[email protected]", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
@@ -832,7 +742,7 @@
"ip-address": ["[email protected]", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
- "ipaddr.js": ["[email protected]", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
+ "ipaddr.js": ["[email protected]", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-arrayish": ["[email protected]", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
@@ -860,10 +770,6 @@
"js-sdsl": ["[email protected]", "", {}, "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ=="],
- "json-schema-ref-resolver": ["[email protected]", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A=="],
-
- "json-schema-traverse": ["[email protected]", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
-
"jsonwebtoken": ["[email protected]", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
"jwa": ["[email protected]", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
@@ -872,8 +778,6 @@
"keyv": ["[email protected]", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
- "light-my-request": ["[email protected]", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="],
-
"lightning-backends": ["[email protected]", "", { "dependencies": { "@bleskomat/form": "1.3.2", "async": "3.2.6", "bolt11": "1.4.1", "secp256k1": "5.0.0", "socks-proxy-agent": "8.0.4" } }, "sha512-jVYaWLhbgGRugndh/W30nd0AjP9D1CcZnbf3tZwGFpG700cuEx2rrufdov8Q28gC3//mfheOQjnRNgf0xPU6Ww=="],
"lint-staged": ["[email protected]", "", { "dependencies": { "commander": "^14.0.2", "listr2": "^9.0.5", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow=="],
@@ -924,7 +828,7 @@
"micromatch": ["[email protected]", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
- "mime": ["[email protected]", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
+ "mime": ["[email protected]", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
"mime-db": ["[email protected]", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
@@ -942,8 +846,6 @@
"minimist": ["[email protected]", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
- "minipass": ["[email protected]", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
-
"mqtt": ["[email protected]", "", { "dependencies": { "@types/readable-stream": "^4.0.21", "@types/ws": "^8.18.1", "commist": "^3.2.0", "concat-stream": "^2.0.0", "debug": "^4.4.1", "help-me": "^5.0.0", "lru-cache": "^10.4.3", "minimist": "^1.2.8", "mqtt-packet": "^9.0.2", "number-allocator": "^1.0.14", "readable-stream": "^4.7.0", "rfdc": "^1.4.1", "socks": "^2.8.6", "split2": "^4.2.0", "worker-timers": "^8.0.23", "ws": "^8.18.3" }, "bin": { "mqtt": "build/bin/mqtt.js", "mqtt_pub": "build/bin/pub.js", "mqtt_sub": "build/bin/sub.js" } }, "sha512-KC+wAssYk83Qu5bT8YDzDYgUJxPhbLeVsDvpY2QvL28PnXYJzC2WkKruyMUgBAZaQ7h9lo9k2g4neRNUUxzgMw=="],
"mqtt-packet": ["[email protected]", "", { "dependencies": { "bl": "^6.0.8", "debug": "^4.3.4", "process-nextick-args": "^2.0.1" } }, "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA=="],
@@ -998,14 +900,6 @@
"parseurl": ["[email protected]", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
- "passport-jwt": ["[email protected]", "", { "dependencies": { "jsonwebtoken": "^9.0.0", "passport-strategy": "^1.0.0" } }, "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ=="],
-
- "passport-local": ["[email protected]", "", { "dependencies": { "passport-strategy": "1.x.x" } }, "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow=="],
-
- "passport-strategy": ["[email protected]", "", {}, "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA=="],
-
- "path-scurry": ["[email protected]", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
-
"path-to-regexp": ["[email protected]", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
"picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
@@ -1062,28 +956,18 @@
"reflect-metadata": ["[email protected]", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
- "require-addon": ["[email protected]", "", { "dependencies": { "bare-addon-resolve": "^1.3.0" } }, "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA=="],
-
- "require-from-string": ["[email protected]", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
-
"resolve-alpn": ["[email protected]", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="],
"responselike": ["[email protected]", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA=="],
"restore-cursor": ["[email protected]", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
- "ret": ["[email protected]", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="],
-
- "reusify": ["[email protected]", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
-
"rfdc": ["[email protected]", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
"ripemd160": ["[email protected]", "", { "dependencies": { "hash-base": "^3.1.2", "inherits": "^2.0.4" } }, "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA=="],
"safe-buffer": ["[email protected]", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
- "safe-regex2": ["[email protected]", "", { "dependencies": { "ret": "~0.5.0" } }, "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw=="],
-
"safe-stable-stringify": ["[email protected]", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
"safer-buffer": ["[email protected]", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
@@ -1098,8 +982,6 @@
"serve-static": ["[email protected]", "", { "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.18.0" } }, "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g=="],
- "set-cookie-parser": ["[email protected]", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
-
"set-function-length": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"setprototypeof": ["[email protected]", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
@@ -1130,8 +1012,6 @@
"socks-proxy-agent": ["[email protected]", "", { "dependencies": { "agent-base": "^7.1.1", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw=="],
- "sodium-native": ["[email protected]", "", { "dependencies": { "require-addon": "^1.1.0", "which-runtime": "^1.2.1" } }, "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA=="],
-
"sonic-boom": ["[email protected]", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
"source-map": ["[email protected]", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@@ -1144,8 +1024,6 @@
"statuses": ["[email protected]", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
- "stream-shift": ["[email protected]", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="],
-
"strfry-rpc": ["[email protected]", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-jbfycgsU41pgIMHprZeMnmmgO57o7G1IRgmMgkY08BAEvzf0YFzTfSG8TWAXudAnMLHSqYLPfHwOueBn1g6roA=="],
"string-argv": ["[email protected]", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
@@ -1178,8 +1056,6 @@
"to-regex-range": ["[email protected]", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
- "toad-cache": ["[email protected]", "", {}, "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="],
-
"toidentifier": ["[email protected]", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"token-types": ["[email protected]", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
@@ -1210,8 +1086,6 @@
"undefsafe": ["[email protected]", "", {}, "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="],
- "undici": ["[email protected]", "", {}, "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ=="],
-
"unpipe": ["[email protected]", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"util-deprecate": ["[email protected]", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
@@ -1232,8 +1106,6 @@
"which": ["[email protected]", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="],
- "which-runtime": ["[email protected]", "", {}, "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw=="],
-
"which-typed-array": ["[email protected]", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="],
"wordwrap": ["[email protected]", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
@@ -1294,18 +1166,8 @@
"concat-stream/readable-stream": ["[email protected]", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
- "duplexify/readable-stream": ["[email protected]", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
-
- "express/content-disposition": ["[email protected]", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
-
- "express/cookie": ["[email protected]", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
-
"express/debug": ["[email protected]", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
- "express-handlebars/glob": ["[email protected]", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="],
-
- "fastify/pino": ["[email protected]", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA=="],
-
"finalhandler/debug": ["[email protected]", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"hash-base/readable-stream": ["[email protected]", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
@@ -1314,8 +1176,6 @@
"https-proxy-agent/debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
- "light-my-request/process-warning": ["[email protected]", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="],
-
"lightning-backends/async": ["[email protected]", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
"lnurl/async": ["[email protected]", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
@@ -1332,16 +1192,10 @@
"number-allocator/debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
- "path-scurry/lru-cache": ["[email protected]", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="],
-
- "proxy-addr/ipaddr.js": ["[email protected]", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
-
"raw-body/http-errors": ["[email protected]", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
"send/debug": ["[email protected]", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
- "send/mime": ["[email protected]", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
-
"socks-proxy-agent/debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"tsyringe/tslib": ["[email protected]", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
diff --git a/index.ts b/index.ts
index 5eba71b2..3b31835f 100644
--- a/index.ts
+++ b/index.ts
@@ -17,7 +17,7 @@ import {
import nwc from "$lib/nwc";
import { catchUp, check } from "$lib/payments";
import { getFx } from "$lib/rates";
-import { sendHeartbeat } from "$lib/sockets";
+import { sendHeartbeat, websocket } from "$lib/sockets";
import { initTigerBeetle } from "$lib/tb";
import { startZmq } from "$lib/zmq";
@@ -35,7 +35,6 @@ import shopify from "$routes/shopify";
import square from "$routes/square";
import users from "$routes/users";
-console.log("test");
try {
await initTigerBeetle();
getLocations();
@@ -74,7 +73,9 @@ app.post("/invoice", optional, invoices.create);
app.post("/invoice/:id", optional, invoices.update);
app.post("/sign", auth, invoices.sign);
-app.get("/assetlinks.json", (_, res) => res.sendFile("assetlinks.json"));
+app.get("/assetlinks.json", (c) => {
+ return new Response(Bun.file("assetlinks.json"));
+});
app.get("/nostr.json", nostr.identities);
app.get("/profile/:profile", nostr.profile);
app.get("/:pubkey/count", nostr.count);
@@ -97,7 +98,6 @@ app.get("/payments", auth, payments.list);
app.get("/payments/:hash", payments.get);
app.post("/parse", auth, payments.parse);
app.get("/fund/:id", payments.fund);
-// app.get("/fund/:name/withdraw", auth, payments.withdraw);
app.get("/fund/:name/managers", payments.managers);
app.post("/fund/managers", auth, payments.addManager);
app.post("/fund/:name/managers/delete", auth, payments.deleteManager);
@@ -125,7 +125,6 @@ app.get("/lnurlp/:username", lnurl.lnurlp);
app.get("/lnurl/:id", lnurl.lnurl);
app.get("/pay/:username", lnurl.pay);
app.get("/pay/:username/:amount", lnurl.pay);
-// app.get("/lnurlw", lnurl.withdraw);
app.post("/freeze", payments.freeze);
@@ -160,19 +159,7 @@ app.post("/superuser", users.superuser);
app.get("/verify/:code", users.verify);
app.post("/request", auth, users.request);
app.post("/forgot", users.forgot);
-app.post(
- "/login",
- {
- config: {
- rateLimit: {
- max: 5,
- timeWindow: "5 seconds",
- keyGenerator: (req) => req.headers["cf-connecting-ip"] as string, // IP-based rate limiting
- },
- },
- },
- users.login,
-);
+app.post("/login", users.login);
app.post("/flash", users.flash);
app.get("/challenge", users.challenge);
app.post("/authKeyLogin", users.authKeyLogin);
@@ -220,26 +207,27 @@ app.post("/mint", auth, ecash.mint);
app.post("/melt", auth, ecash.melt);
app.post("/ecash/:id", ecash.receive);
-app.get("/replay/:index", (req, res) => {
- replay(req.params.index);
- res.send({});
+app.get("/replay/:index", (c) => {
+ replay(c.req.param("index"));
+ return c.json({});
});
-app.post("/echo", (req, res) => {
- console.log("echo", req.body);
- res.send(req.body);
+app.post("/echo", async (c) => {
+ const body = await c.req.json();
+ console.log("echo", body);
+ return c.json(body);
});
-app.post("/test/ark/send", async (req, res) => {
+app.post("/test/ark/send", async (c) => {
try {
- const secret = req.headers["x-test-secret"] as string;
+ const secret = c.req.header("x-test-secret");
if (!config.testSecret || secret !== config.testSecret) {
- return res.code(403).send("Forbidden");
+ return c.json("Forbidden", 403);
}
- const { address, amount, iid } = req.body;
+ const body = await c.req.json();
+ const { address, amount, iid } = body;
const txid = await sendArk(address, amount);
- // Mark invoice as paid so tests don't need client-side SDK sync
if (iid) {
const invoice = await g(`invoice:${iid}`);
if (invoice) {
@@ -248,16 +236,24 @@ app.post("/test/ark/send", async (req, res) => {
}
}
- res.send({ txid, iid });
+ return c.json({ txid, iid });
} catch (e: any) {
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
});
-const host: string = process.env["HOST"] || "0.0.0.0";
+const host_: string = process.env["HOST"] || "0.0.0.0";
const port: number = Number.parseInt(process.env["PORT"]) || 3119;
-app.listen({ host, port });
+Bun.serve({
+ fetch(req, server) {
+ if (new URL(req.url).pathname === "/ws" && server.upgrade(req)) return;
+ return app.fetch(req, server);
+ },
+ websocket,
+ port,
+ hostname: host_,
+});
const logerr = (e: Error) => console.log(e);
process.on("unhandledRejection", logerr);
diff --git a/lib/app.ts b/lib/app.ts
index da29d9fe..ec810288 100644
--- a/lib/app.ts
+++ b/lib/app.ts
@@ -1,34 +1,88 @@
-import config from "$config";
-import cors from "@fastify/cors";
-import fastifyProxy from "@fastify/http-proxy";
-import fastifyMultipart from "@fastify/multipart";
-import fastifyPassport from "@fastify/passport";
-import fastifyRateLimit from "@fastify/rate-limit";
-import fastifySecureSession from "@fastify/secure-session";
-import fastifyStatic from "@fastify/static";
-import fastify from "fastify";
+import { Hono } from "hono";
+import { cors } from "hono/cors";
+import { serveStatic } from "hono/bun";
import pino from "pino";
-import * as path from "path";
+const app = new Hono();
-import { jwtStrategy } from "$lib/auth";
+const reqLogger = pino(pino.destination("req"));
+const resLogger = pino(pino.destination("res"));
+
+// CORS
+app.use(
+ "*",
+ cors({
+ origin: (origin) => origin || "*",
+ credentials: true,
+ }),
+);
+
+// Static files
+app.use("/public/*", serveStatic({ root: "/home/bun/app/data/uploads", rewriteRequestPath: (p) => p.replace("/public", "") }));
+
+// Rate limiting
+const rateLimits = new Map<string, { count: number; reset: number }>();
+const strictLimits = new Map<string, { count: number; reset: number }>();
-const app = fastify({
- logger: true,
- disableRequestLogging: true,
- routerOptions: { maxParamLength: 500 },
+app.use("*", async (c, next) => {
+ const url = c.req.path;
+
+ // Skip rate limiting for public assets
+ if (url.includes("public")) return next();
+
+ const ip = (c.req.header("cf-connecting-ip") as string) || c.env?.ip || "unknown";
+ const ua = c.req.header("user-agent") || "unknown-ua";
+ const rateLimitBy = c.req.header("rate-limit-by");
+ const key = rateLimitBy === "ua" ? ua : ip;
+ const now = Date.now();
+
+ // General rate limit: 2000 req / 2s
+ const gen = rateLimits.get(key);
+ if (gen && now < gen.reset) {
+ gen.count++;
+ if (gen.count > 2000) {
+ return c.json(
+ { statusCode: 429, error: "Too Many Requests", message: "Rate limit exceeded, retry in 2 seconds" },
+ 429,
+ );
+ }
+ } else {
+ rateLimits.set(key, { count: 1, reset: now + 2000 });
+ }
+
+ // Strict rate limit for /login and /send: 10 req / 10s
+ const isStrict = url.includes("/login") || url.includes("/send");
+ if (isStrict) {
+ const strictKey = `strict:${ua}`;
+ const s = strictLimits.get(strictKey);
+ if (s && now < s.reset) {
+ s.count++;
+ if (s.count > 10) {
+ return c.json(
+ { statusCode: 429, error: "Too Many Requests", message: "Rate limit exceeded" },
+ 429,
+ );
+ }
+ } else {
+ strictLimits.set(strictKey, { count: 1, reset: now + 10000 });
+ }
+ }
+
+ return next();
});
-const reqLogger = pino(pino.destination("req"));
-const resLogger = pino(pino.destination("res"));
+// Clean up rate limit maps periodically
+setInterval(() => {
+ const now = Date.now();
+ for (const [k, v] of rateLimits) if (now >= v.reset) rateLimits.delete(k);
+ for (const [k, v] of strictLimits) if (now >= v.reset) strictLimits.delete(k);
+}, 5000);
-// app.addHook("onRequest", async (req) => {
-// reqLogger.info({ url: req.raw.url, id: req.id });
-// });
-//
+// Request logging
+app.use("*", async (c, next) => {
+ const start = Date.now();
+ const url = c.req.path;
-app.addHook("preHandler", async (req) => {
- const url = req.raw.url;
const ignore = [
"/ws",
"/me",
@@ -42,28 +96,36 @@ app.addHook("preHandler", async (req) => {
"/accounts",
"/contacts",
];
- if (ignore.some((path) => url.startsWith(path))) return;
- if (req.method === "GET" && url.startsWith("/users")) return;
- const xff = req.headers["x-forwarded-for"];
- const forwardedIp = Array.isArray(xff) ? xff[0] : xff?.split(",")[0]?.trim();
- const ip = req.headers["cf-connecting-ip"] || forwardedIp || req.ip;
+ const shouldLog = !ignore.some((path) => url.startsWith(path)) &&
+ !(c.req.method === "GET" && url.startsWith("/users"));
- reqLogger.info({
- method: req.method,
- url,
- ip,
- headers: req.headers,
- query: req.query,
- body: req.body,
- user: (req.user as any)?.username,
- id: req.id,
- });
-});
+ if (shouldLog) {
+ const xff = c.req.header("x-forwarded-for");
+ const forwardedIp = xff?.split(",")[0]?.trim();
+ const ip = c.req.header("cf-connecting-ip") || forwardedIp || c.env?.ip || "unknown";
-app.addHook("onResponse", async (req, reply) => {
- const rawCookies = req.raw.headers.cookie || "";
+ let body;
+ // Only parse body for non-GET requests
+ if (c.req.method !== "GET" && c.req.method !== "HEAD") {
+ try {
+ body = await c.req.raw.clone().json();
+ } catch {}
+ }
+
+ reqLogger.info({
+ method: c.req.method,
+ url,
+ ip,
+ query: c.req.query(),
+ body,
+ user: c.get("user")?.username,
+ });
+ }
+ await next();
+
+ const rawCookies = c.req.header("cookie") || "";
const cookies: any = rawCookies.split(";").reduce((acc, cookie) => {
const [key, value] = cookie.split("=").map((s) => s.trim());
if (key && value) acc[key] = value;
@@ -71,84 +133,19 @@ app.addHook("onResponse", async (req, reply) => {
}, {});
resLogger.info({
- id: req.id,
- url: req.raw.url,
- statusCode: reply.raw.statusCode,
- durationMs: reply.elapsedTime,
+ url,
+ statusCode: c.res.status,
+ durationMs: Date.now() - start,
username: cookies.username,
});
});
-app.register(fastifyRateLimit, {
- allowList: (req) => req.raw.url?.includes("public"),
- max: 2000,
- timeWindow: 2000,
- keyGenerator: (req) => {
- const ip = (req.headers["cf-connecting-ip"] as string) || req.ip;
- const ua = req.headers["user-agent"] || "unknown-ua";
- return req.headers["rate-limit-by"] === "ua" ? ua : ip;
- },
- errorResponseBuilder: () => {
- return {
- statusCode: 429,
- error: "Too Many Requests",
- message: "Rate limit exceeded, retry in 2 seconds",
- };
- },
-});
-
-app.register(fastifyRateLimit, {
- allowList: (req) => {
- const url = req.raw.url || "";
- const matches = url.includes("/login") || url.includes("/send");
- return !matches;
- },
- max: 10,
- timeWindow: 10000,
- keyGenerator: (req) => (req.headers["user-agent"] as string) || "unknown-ua",
- errorResponseBuilder: () => ({
- statusCode: 429,
- error: "Too Many Requests",
- message: "Rate limit exceeded",
- }),
-});
-
-app.register(fastifyProxy, {
- upstream: "http://localhost:3120",
- prefix: "/ws",
- rewritePrefix: "/ws",
- websocket: true,
-});
-
-app.register(fastifySecureSession, {
- key: Buffer.from(config.jwt, "hex"),
-});
-
-app.register(fastifyPassport.initialize());
-app.register(fastifyPassport.secureSession());
-
-fastifyPassport.use("jwt", jwtStrategy);
-
-app.register(fastifyMultipart, { limits: { fileSize: 10 ** 7 } });
-
-app.register(fastifyStatic, {
- root: path.join("/home/bun/app/data/uploads"),
- prefix: "/public/",
-});
-
-await app.register(cors, {
- origin: true,
- credentials: true,
-});
-
-app.setErrorHandler((error, _, reply) => {
- if (error instanceof fastify.errorCodes.FST_ERR_BAD_STATUS_CODE) {
- reply.status(500).send({ ok: false });
- } else {
- reply.send(error);
- }
+// Error handler
+app.onError((err, c) => {
+ return c.json({ ok: false }, 500);
});
-app.setNotFoundHandler((_, reply) => reply.code(404).type("text/html").send("Not Found"));
+// Not found handler
+app.notFound((c) => c.text("Not Found", 404));
export default app;
diff --git a/lib/auth.ts b/lib/auth.ts
index c2eca6d4..30d5438b 100644
--- a/lib/auth.ts
+++ b/lib/auth.ts
@@ -1,58 +1,53 @@
import config from "$config";
import { fail, getUser } from "$lib/utils";
-import fastifyPassport from "@fastify/passport";
-import jwt from "passport-jwt";
+import jwt from "jsonwebtoken";
+import { getCookie } from "hono/cookie";
-export const admin = {
- preValidation: fastifyPassport.authenticate(
- "jwt",
- { session: false },
- (req, res, err, user, info) => {
- if (!user.admin) return res.code(401).send("unauthorized");
- req.user = user;
- },
- ),
+const extractToken = (c) => {
+ const authHeader = c.req.header("authorization");
+ if (authHeader?.startsWith("Bearer ")) return authHeader.slice(7);
+ return getCookie(c, "token") || null;
};
-export const optional = {
- preValidation: fastifyPassport.authenticate(
- "jwt",
- { session: false },
- (req, res, err, user, info) => {
- req.user = user;
- },
- ),
-};
+const authenticate = async (c) => {
+ const token = extractToken(c);
+ if (!token) return null;
-export const auth = {
- preValidation: fastifyPassport.authenticate("jwt", {
- authInfo: false,
- session: false,
- }),
-};
+ try {
+ const payload = jwt.verify(token, config.jwt);
+ let { id } = payload as any;
+ const url = c.req.path;
+ const method = c.req.method;
-export const requirePin = async ({ body, user }) => {
- if (!user || (user.pin && user.pin !== body.pin)) fail("Invalid pin");
+ const wl = { GET: ["/invoice", "/payments"], POST: ["/invoice"] };
+ if (id.endsWith("-ro") && wl[method]?.some((p) => url.startsWith(p))) id = id.slice(0, -3);
+
+ return await getUser(id);
+ } catch {
+ return null;
+ }
};
-export const jwtStrategy = new jwt.Strategy(
- {
- jwtFromRequest: jwt.ExtractJwt.fromExtractors([
- jwt.ExtractJwt.fromAuthHeaderAsBearerToken(),
- (req) => (req.cookies ? req.cookies.token : null),
- ]),
- secretOrKey: config.jwt,
- passReqToCallback: true,
- },
- async (req, payload, next) => {
- const { originalUrl: u, method: m } = req;
+export const auth = async (c, next) => {
+ const user = await authenticate(c);
+ if (!user) return c.json("unauthorized", 401);
+ c.set("user", user);
+ await next();
+};
- let { id } = payload;
- const wl = { GET: ["/invoice", "/payments"], POST: ["/invoice"] };
+export const optional = async (c, next) => {
+ const user = await authenticate(c);
+ if (user) c.set("user", user);
+ await next();
+};
- if (id.endsWith("-ro") && wl[m].some((p) => u.startsWith(p))) id = id.slice(0, -3);
+export const admin = async (c, next) => {
+ const user = await authenticate(c);
+ if (!user?.admin) return c.json("unauthorized", 401);
+ c.set("user", user);
+ await next();
+};
- const user = await getUser(id);
- next(null, user);
- },
-);
+export const requirePin = async ({ body, user }) => {
+ if (!user || (user.pin && user.pin !== body.pin)) fail("Invalid pin");
+};
diff --git a/lib/lightning.ts b/lib/lightning.ts
index a259b601..3b1882d4 100644
--- a/lib/lightning.ts
+++ b/lib/lightning.ts
@@ -164,7 +164,7 @@ export async function replay(index) {
}
}
-export const fixBolt12 = async (_, res) => {
+export const fixBolt12 = async (c) => {
for await (const k of scan("payment:*")) {
const p = await g(k);
if (p.type === "bolt12") {
@@ -182,5 +182,5 @@ export const fixBolt12 = async (_, res) => {
}
}
- res.send({});
+ return c.json({});
};
diff --git a/lib/sockets.ts b/lib/sockets.ts
index 88817728..51ba15fa 100644
--- a/lib/sockets.ts
+++ b/lib/sockets.ts
@@ -81,88 +81,77 @@ const track = async (ws, token) => {
ws.user = user;
};
-Bun.serve({
- hostname: "0.0.0.0",
- port: 3120,
- fetch(req, server) {
- // upgrade the request to a WebSocket
- if (server.upgrade(req)) {
- return; // do not return a Response
- }
- return new Response("Upgrade failed", { status: 500 });
- },
- websocket: {
- async message(ws: any, message: string) {
- let type;
- let data;
+export const websocket = {
+ async message(ws: any, message: string) {
+ let type;
+ let data;
- try {
- ({ type, data } = JSON.parse(message));
- } catch (e) {
- err("coudn't parse socket message");
- }
+ try {
+ ({ type, data } = JSON.parse(message));
+ } catch (e) {
+ err("coudn't parse socket message");
+ }
- switch (type) {
- case "heartbeat":
- ws.beats = 0;
- try {
- if (data) await track(ws, data);
- ws.send(JSON.stringify({ type: "id", data: ws.id }));
- } catch (e) {
- // err("Failed to send heartbeat", e.message);
- }
- break;
+ switch (type) {
+ case "heartbeat":
+ ws.beats = 0;
+ try {
+ if (data) await track(ws, data);
+ ws.send(JSON.stringify({ type: "id", data: ws.id }));
+ } catch (e) {
+ // err("Failed to send heartbeat", e.message);
+ }
+ break;
- case "login":
- try {
- if (!data || data === "null" || !jwt.decode(data))
- return ws.close(code, `bad token ${data}`);
+ case "login":
+ try {
+ if (!data || data === "null" || !jwt.decode(data))
+ return ws.close(code, `bad token ${data}`);
- await track(ws, data);
- } catch (e) {
- if (e.message.includes("not found")) ws.send(JSON.stringify({ type: "logout" }));
- setTimeout(() => ws?.close(code, `closing due to error ${e.message}`), 1000);
- }
- break;
- case "token":
- if (!ws.user) return;
- ws.token = data;
- break;
- case "lnurl":
- store.sessions[data] = ws;
- break;
- case "subscribe":
- subscriptions.push({ invoice: data, ws });
- break;
- default:
- // warn("received socket message of unknown type", type, data);
- }
- },
- open(ws: any) {
- const id = v4();
- ws.id = id;
- ws.beats = 0;
- ws.send(JSON.stringify({ type: "connected", data: id }));
- all[id] = ws;
- },
- close(ws: any) {
- const { id } = ws;
- try {
- const uid = users[id];
- if (store.sockets[uid]?.[id]) {
- delete store.sockets[uid][id];
+ await track(ws, data);
+ } catch (e) {
+ if (e.message.includes("not found")) ws.send(JSON.stringify({ type: "logout" }));
+ setTimeout(() => ws?.close(code, `closing due to error ${e.message}`), 1000);
}
+ break;
+ case "token":
+ if (!ws.user) return;
+ ws.token = data;
+ break;
+ case "lnurl":
+ store.sessions[data] = ws;
+ break;
+ case "subscribe":
+ subscriptions.push({ invoice: data, ws });
+ break;
+ default:
+ // warn("received socket message of unknown type", type, data);
+ }
+ },
+ open(ws: any) {
+ const id = v4();
+ ws.id = id;
+ ws.beats = 0;
+ ws.send(JSON.stringify({ type: "connected", data: id }));
+ all[id] = ws;
+ },
+ close(ws: any) {
+ const { id } = ws;
+ try {
+ const uid = users[id];
+ if (store.sockets[uid]?.[id]) {
+ delete store.sockets[uid][id];
+ }
- let i;
- ~(i = subscriptions.findIndex((s) => s.ws.id === id)) && subscriptions.splice(i, 1);
+ let i;
+ ~(i = subscriptions.findIndex((s) => s.ws.id === id)) && subscriptions.splice(i, 1);
- if (all[id]) delete all[id];
- } catch (e) {
- err("problem closing socket", e.message);
- }
- },
+ if (all[id]) delete all[id];
+ } catch (e) {
+ err("problem closing socket", e.message);
+ }
},
-});
+};
export const sendHeartbeat = () => {
for (const uid in store.sockets) {
diff --git a/lib/upload.ts b/lib/upload.ts
index 2ed5d905..900d49dd 100644
--- a/lib/upload.ts
+++ b/lib/upload.ts
@@ -5,14 +5,15 @@ import { bail, fail } from "$lib/utils";
import { fileTypeFromBuffer } from "file-type";
import sharp from "sharp";
-export default async (req, res) => {
+export default async (c) => {
try {
- const {
- params: { type },
- } = req;
+ const type = c.req.param("type");
- const data = await req.file();
- let buf = await data.toBuffer();
+ const body = await c.req.parseBody();
+ const file = body.file || Object.values(body).find((v) => v instanceof File);
+ if (!file) fail("no file uploaded");
+
+ let buf = Buffer.from(await (file as File).arrayBuffer());
const [format, ext] = (await fileTypeFromBuffer(buf)).mime.split("/");
@@ -26,9 +27,9 @@ export default async (req, res) => {
const filePath = `/home/bun/app/data/uploads/${hash}.webp`;
writeFileSync(filePath, buf);
- res.send({ hash });
+ return c.json({ hash });
} catch (e) {
err("problem uploading", e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
};
diff --git a/lib/utils.ts b/lib/utils.ts
index 7a3d4c13..6f032cc7 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -20,7 +20,7 @@ export const wait = async (f, s = 300, n = 50) => {
export const prod = process.env.NODE_ENV === "production";
-export const bail = (res, msg) => res.code(500).send(msg);
+export const bail = (c, msg) => c.json(msg, 500);
export const SATS = 100000000;
export const sats = (n) => Math.round(n * SATS);
diff --git a/package.json b/package.json
index b8d15c1d..864a3347 100644
--- a/package.json
+++ b/package.json
@@ -8,8 +8,7 @@
"dev": "nodemon --exec bun index.ts",
"lint": "oxlint . && oxfmt --check .",
"lint:fix": "oxlint --fix . && oxfmt .",
- "format": "oxfmt .",
- "prepare": "husky"
+ "format": "oxfmt ."
},
"dependencies": {
"@arkade-os/sdk": "0.3.13",
@@ -17,14 +16,6 @@
"@aws-sdk/client-ses": "^3.997.0",
"@cashu/cashu-ts": "^2.9.0",
"@coinos/rpc": "^0.0.6",
- "@fastify/cors": "^11.2.0",
- "@fastify/http-proxy": "^11.4.1",
- "@fastify/multipart": "^9.4.0",
- "@fastify/passport": "^3.0.2",
- "@fastify/rate-limit": "^10.3.0",
- "@fastify/secure-session": "^8.3.0",
- "@fastify/static": "^9.0.0",
- "@fastify/websocket": "^11.2.0",
"@noble/hashes": "^2.0.1",
"@scure/base": "^2.0.0",
"@scure/bip32": "^2.0.1",
@@ -33,7 +24,7 @@
"bech32": "^2.0.0",
"clightning-client": "github:asoltys/lightning-client-js#master",
"eventsource": "^4.1.0",
- "fastify": "^5.7.4",
+ "hono": "^4.7.0",
"file-type": "^21.3.0",
"fluent-ffmpeg": "^2.1.3",
"got": "^14.6.6",
@@ -44,8 +35,6 @@
"nostr": "^0.2.8",
"nostr-tools": "^2.23.1",
"otplib": "^12.0.1",
- "passport-jwt": "^4.0.1",
- "passport-local": "1.0.0",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
"pump": "^3.0.3",
@@ -61,8 +50,6 @@
},
"devDependencies": {
"@types/bun": "^1.3.9",
- "husky": "^9.1.7",
- "lint-staged": "^16.2.7",
"nodemon": "^2.0.22",
"oxfmt": "^0.35.0",
"oxlint": "^1.50.0",
diff --git a/routes/ecash.ts b/routes/ecash.ts
index b4c16404..fc6a8d3a 100644
--- a/routes/ecash.ts
+++ b/routes/ecash.ts
@@ -11,7 +11,7 @@ import { v4 } from "uuid";
import { PaymentType } from "$lib/types";
const { ecash: type } = PaymentType;
-Error.stackTraceLimit = 100; // Set this to the desired limit
+Error.stackTraceLimit = 100;
const sendCash = async ({ amount, user }) => {
const id = v4();
@@ -28,39 +28,33 @@ const sendCash = async ({ amount, user }) => {
};
export default {
- async save(req, res) {
- const {
- body: { token },
- } = req;
+ async save(c) {
+ const { token } = await c.req.json();
try {
const id = v4();
await s(`cash:${id}`, token);
- res.send({ id });
+ return c.json({ id });
} catch (e) {
err(e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async get(req, res) {
- const {
- params: { id },
- } = req;
+ async get(c) {
+ const id = c.req.param("id");
try {
const token = await get(id);
const status = await check(token);
- res.send({ token, status });
+ return c.json({ token, status });
} catch (e) {
err(e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async claim(req, res) {
- const {
- body: { token },
- user,
- } = req;
+ async claim(c) {
+ const { token } = await c.req.json();
+ const user = c.get("user");
try {
const amount = await claim(token);
@@ -79,25 +73,25 @@ export default {
await credit({ hash, amount, memo, ref: user.id, type });
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
err(e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async mint(req, res) {
- const { body, user } = req;
+ async mint(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
const amount = parseInt(body.amount);
fail("disabled");
- res.send(await sendCash({ amount, user }));
+ return c.json(await sendCash({ amount, user }));
},
- async melt(req, res) {
- let {
- body: { amount, bolt11: hash, preimage },
- user,
- } = req;
+ async melt(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
+ let { amount, bolt11: hash, preimage } = body;
try {
amount = Math.round(amount / 1000);
const ref = preimage;
@@ -133,15 +127,16 @@ export default {
l(user.username, "sent", type, amount);
emit(user.id, "payment", p);
- res.send(p);
+ return c.json(p);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async receive(req, res) {
+ async receive(c) {
try {
- const { id, proofs, mint, memo } = req.body;
+ const body = await c.req.json();
+ const { id, proofs, mint, memo } = body;
const { uid: ref } = await getInvoice(id);
const amount = await claim(
@@ -153,11 +148,11 @@ export default {
await credit({ hash: id, amount, memo, ref, type });
- res.send({ id });
+ return c.json({ id });
} catch (e) {
console.log(e);
err(e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/email.ts b/routes/email.ts
index 25c90ee7..ff6ffaeb 100644
--- a/routes/email.ts
+++ b/routes/email.ts
@@ -6,8 +6,9 @@ import { SESClient } from "@aws-sdk/client-ses";
import { SendEmailCommand } from "@aws-sdk/client-ses";
export default {
- async send({ body }, res) {
+ async send(c) {
try {
+ const body = await c.req.json();
const { email, message, username, token: response } = body;
const Charset = "UTF-8";
@@ -48,9 +49,9 @@ export default {
}),
);
- res.send({ ok: true });
+ return c.json({ ok: true });
} else {
- bail(res, "failed captcha");
+ return bail(c, "failed captcha");
}
} catch (e) {
console.log(e);
diff --git a/routes/info.ts b/routes/info.ts
index 35d1c315..adad02c6 100644
--- a/routes/info.ts
+++ b/routes/info.ts
@@ -5,23 +5,15 @@ import ln from "$lib/ln";
import { getDecodedToken } from "@cashu/cashu-ts";
export default {
- async health(_, res) {
+ async health(c) {
const status = getHealthStatus();
const httpStatus = status.healthy ? 200 : 503;
- res.status(httpStatus).send(status);
+ return c.json(status, httpStatus);
},
- async balances(_, res) {
+ async balances(c) {
let total = 0;
- // for await (const k of db.scanIterator({ MATCH: "balance:*" })) {
- // total += parseInt(await db.get(k));
- // }
- //
- // for await (const k of archive.scanIterator({ MATCH: "balance:*" })) {
- // total += parseInt(await archive.get(k));
- // }
-
const funds = await ln.listfunds();
const lnchannel = parseInt(funds.channels.reduce((a, b) => a + b.channel_sat, 0));
const lnwallet = parseInt(funds.outputs.reduce((a, b) => a + b.value, 0));
@@ -34,6 +26,6 @@ export default {
lnwallet,
};
- res.send(info);
+ return c.json(info);
},
};
diff --git a/routes/invoices.ts b/routes/invoices.ts
index 7f44a28d..6fc0b67a 100644
--- a/routes/invoices.ts
+++ b/routes/invoices.ts
@@ -6,11 +6,9 @@ import { bail, fail, fields, getAccount, getInvoice, getUser } from "$lib/utils"
import rpc from "@coinos/rpc";
export default {
- async get(req, res) {
+ async get(c) {
try {
- const {
- params: { id },
- } = req;
+ const id = c.req.param("id");
if (id === "undefined") fail("invalid id");
const invoice = await getInvoice(id);
@@ -21,36 +19,37 @@ export default {
invoice.items ||= [];
}
- if (invoice) res.send(invoice);
+ if (invoice) return c.json(invoice);
else fail("invoice not found");
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async create(req, res) {
- let { body, user } = req;
+ async create(c) {
+ const body = await c.req.json();
+ let user = c.get("user");
const { invoice } = body;
if (body.user) user = body.user;
- if (req.user.username === user.username) invoice.own = true;
+ if (c.get("user")?.username === user.username) invoice.own = true;
else invoice.own = false;
try {
const result = await generate({ invoice, user });
- res.send(result);
+ return c.json(result);
} catch (e) {
console.trace();
console.log(e);
- err("problem generating invoice", req.user?.username, body.user?.username, e.message);
- bail(res, e.message);
+ err("problem generating invoice", c.get("user")?.username, body.user?.username, e.message);
+ return bail(c, e.message);
}
},
- async update(req, res) {
+ async update(c) {
try {
- const { id } = req.params;
- const { body } = req;
+ const id = c.req.param("id");
+ const body = await c.req.json();
const { tip, webhook, secret, received } = body.invoice;
if (tip < 0) fail("Invalid tip");
@@ -61,7 +60,7 @@ export default {
if (typeof tip !== "undefined") invoice.tip = tip;
if (webhook && secret) {
- if (invoice.uid !== req.user?.id) fail("Unauthorized");
+ if (invoice.uid !== c.get("user")?.id) fail("Unauthorized");
invoice.webhook = webhook;
invoice.secret = secret;
}
@@ -70,30 +69,32 @@ export default {
await s(`invoice:${id}`, invoice);
- res.send(invoice);
+ return c.json(invoice);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async list(req, res) {
- const { id } = req.user;
+ async list(c) {
+ const user = c.get("user");
+ const { id } = user;
let invoices = await db.lRange(`${id}:invoices`, 0, -1);
invoices = (await Promise.all(invoices.map((i) => getInvoice(i)))).filter(Boolean);
- res.send(invoices);
+ return c.json(invoices);
},
- async sign(req, res) {
+ async sign(c) {
try {
- const { address, message, type = "bitcoin" } = req.body;
+ const body = await c.req.json();
+ const { address, message, type = "bitcoin" } = body;
const node = rpc(config[type]);
if (config[type].walletpass) await node.walletPassphrase(config[type].walletpass, 300);
const signature = await node.signMessage({ address, message });
- res.send({ signature });
+ return c.json({ signature });
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/items.ts b/routes/items.ts
index df1d86ea..b9706b4c 100644
--- a/routes/items.ts
+++ b/routes/items.ts
@@ -3,10 +3,8 @@ import { v4 } from "uuid";
import { bail, fail } from "$lib/utils";
export default {
- async list(req, res) {
- const {
- params: { id: uid },
- } = req;
+ async list(c) {
+ const uid = c.req.param("id");
const items = [];
for (const id of await db.lRange(`${uid}:items`, 0, -1)) {
const item = await g(`item:${id}`);
@@ -14,27 +12,24 @@ export default {
else await db.lRem(`${uid}:items`, 0, id);
}
- res.send(items);
+ return c.json(items);
},
- async get(req, res) {
- const {
- params: { id },
- } = req;
+ async get(c) {
+ const id = c.req.param("id");
try {
const item = await g(`item:${id}`);
if (!item) fail("Item not found");
- res.send(item);
+ return c.json(item);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async create(req, res) {
- const {
- body: item,
- user: { id },
- } = req;
+ async create(c) {
+ const item = await c.req.json();
+ const user = c.get("user");
+ const { id } = user;
try {
if (!item.id) {
item.id = v4();
@@ -46,33 +41,33 @@ export default {
await s(`item:${item.id}`, item);
- res.send(item);
+ return c.json(item);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async del(req, res) {
- const {
- body: { item },
- user: { id },
- } = req;
+ async del(c) {
+ const body = await c.req.json();
+ const { item } = body;
+ const user = c.get("user");
+ const { id } = user;
try {
const n = await db.lRem(`${id}:items`, 0, item.id);
if (n) db.del(`item:${item.id}`);
else fail("item not found");
- res.send({});
+ return c.json({});
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async sort(req, res) {
- const {
- body: { items },
- user: { id },
- } = req;
+ async sort(c) {
+ const body = await c.req.json();
+ const { items } = body;
+ const user = c.get("user");
+ const { id } = user;
try {
await db.del(`${id}:items`);
@@ -80,9 +75,9 @@ export default {
await db.rPush(`${id}:items`, item.id);
}
- res.send({});
+ return c.json({});
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/lnurl.ts b/routes/lnurl.ts
index cb74e2b5..9cd6e8d5 100644
--- a/routes/lnurl.ts
+++ b/routes/lnurl.ts
@@ -14,10 +14,8 @@ const host = URL.split("/").at(-1);
const fiveMinutes = 1000 * 60 * 5;
export default {
- async encode(req, res) {
- const {
- query: { address },
- } = req;
+ async encode(c) {
+ const address = c.req.query("address");
const [name, domain] = address.split("@");
const url = `https://${domain}/.well-known/lnurlp/${name.toLowerCase().replace(/\s/g, "")}`;
@@ -27,32 +25,29 @@ export default {
} catch (e) {
const m = `failed to lookup lightning address ${address}`;
warn(m);
- return bail(res, m);
+ return bail(c, m);
}
const enc = bech32.encode("lnurl", bech32.toWords(Buffer.from(url)), 20000);
- res.send(enc);
+ return c.json(enc);
},
- async decode(req, res) {
- const {
- query: { text },
- } = req;
+ async decode(c) {
+ const text = c.req.query("text");
try {
const url = Buffer.from(bech32.fromWords(bech32.decode(text, 20000).words)).toString();
const r = await got(url).json();
- res.send(r);
+ return c.json(r);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async lnurlp(req, res) {
- const {
- params: { username },
- query: { minSendable = 1000, maxSendable = 100000000000 },
- } = req;
+ async lnurlp(c) {
+ const username = c.req.param("username");
+ const minSendable = c.req.query("minSendable") || 1000;
+ const maxSendable = c.req.query("maxSendable") || 100000000000;
try {
const user = await getUser(
username.replace("lightning:", "").replace(/\s/g, "").replace("=", "").toLowerCase(),
@@ -69,7 +64,7 @@ export default {
const id = v4();
await s(`lnurl:${id}`, uid);
- res.send({
+ return c.json({
allowsNostr: true,
minSendable,
maxSendable,
@@ -82,15 +77,15 @@ export default {
} catch (e) {
if (!e.message.includes("found"))
warn("problem generating lnurlp request", username, e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async lnurl(req, res) {
- const {
- params: { id },
- query: { amount, comment, nostr },
- } = req;
+ async lnurl(c) {
+ const id = c.req.param("id");
+ const amount = c.req.query("amount");
+ const comment = c.req.query("comment");
+ const nostr = c.req.query("nostr");
try {
const iid = await g(`lnurl:${id}:invoice`);
const uid = await g(`lnurl:${id}`);
@@ -109,7 +104,6 @@ export default {
if (nostr) {
try {
const event = JSON.parse(decodeURIComponent(nostr));
- // TODO: validate the event
await s(`zap:${id}`, event);
metadata = nostr;
} catch (e) {
@@ -128,31 +122,30 @@ export default {
user,
});
- res.send({
+ return c.json({
pr: invoice.text,
routes: [],
verify: `${URL}/api/lnurl/verify/${invoice.id}`,
});
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async verify(req, res) {
- const {
- params: { id },
- } = req;
+ async verify(c) {
+ const id = c.req.param("id");
const inv = await getInvoice(id);
- if (!inv) return res.send({ status: "ERROR", reason: "Not found" });
+ if (!inv) return c.json({ status: "ERROR", reason: "Not found" });
const { hash, received, amount, preimage } = inv;
const settled = received >= amount;
- res.send({ pr: hash, status: "OK", settled, preimage: preimage || null });
+ return c.json({ pr: hash, status: "OK", settled, preimage: preimage || null });
},
- async pay(req, res) {
- const { amount, username } = req.params;
+ async pay(c) {
+ const amount = c.req.param("amount");
+ const username = c.req.param("username");
try {
const user = await getUser(username);
@@ -206,7 +199,7 @@ export default {
await s(`lnurl:${id}`, uid);
if (total > 0) await s(`lnurl:${id}:invoice`, invoice.id);
- res.send({
+ return c.json({
allowsNostr: true,
minSendable: invoice.amount ? total : 1000,
maxSendable: invoice.amount ? total : 10 * 1000 * SATS,
@@ -219,7 +212,7 @@ export default {
} catch (e) {
if (!e.message.includes("found"))
warn("problem generating lnurlp request", username, e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/locations.ts b/routes/locations.ts
index 23b0f3b4..6dd364ec 100644
--- a/routes/locations.ts
+++ b/routes/locations.ts
@@ -1,19 +1,19 @@
import { db, g } from "$lib/db";
export default {
- async list(_, res) {
+ async list(c) {
const locations = await g("locations");
- res.send({ locations });
+ return c.json({ locations });
},
- async nearby(req, res) {
- const lat = parseFloat(req.query.lat);
- const lon = parseFloat(req.query.lon);
- const radius = parseFloat(req.query.radius) || 50;
- const count = parseInt(req.query.count) || 50;
+ async nearby(c) {
+ const lat = parseFloat(c.req.query("lat"));
+ const lon = parseFloat(c.req.query("lon"));
+ const radius = parseFloat(c.req.query("radius")) || 50;
+ const count = parseInt(c.req.query("count")) || 50;
if (isNaN(lat) || isNaN(lon)) {
- return res.status(400).send({ error: "lat and lon required" });
+ return c.json({ error: "lat and lon required" }, 400);
}
try {
@@ -34,11 +34,11 @@ export default {
if (loc) locations.push(loc);
}
- res.send({ locations });
+ return c.json({ locations });
} catch (e) {
console.log("nearby search failed", e);
const locations = await g("locations");
- res.send({ locations });
+ return c.json({ locations });
}
},
};
diff --git a/routes/nostr.ts b/routes/nostr.ts
index 4436a1c7..554a230c 100644
--- a/routes/nostr.ts
+++ b/routes/nostr.ts
@@ -22,20 +22,20 @@ import type { ProfilePointer } from "nostr-tools/nip19";
import { getZapEndpoint, makeZapRequest } from "nostr-tools/nip57";
export default {
- async event(req, res) {
+ async event(c) {
try {
- let { id } = req.params;
- const full = req.url.endsWith("full");
+ let id = c.req.param("id");
+ const full = c.req.url.endsWith("full");
if (id.startsWith("nevent")) id = decode(id).data.id;
if (id.startsWith("note")) id = decode(id).data;
const k = `event:${id}${full ? ":full" : ""}`;
let event = await g(k);
- if (event) return res.send(event);
+ if (event) return c.json(event);
event = await get({ ids: [id] });
- if (!full) return res.send(event);
+ if (!full) return c.json(event);
const parts = parseContent(event);
@@ -98,15 +98,16 @@ export default {
await db.set(k, JSON.stringify(event), { EX });
- res.send(event);
+ return c.json(event);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async parse(req, res) {
- const { event } = req.body;
+ async parse(c) {
+ const body = await c.req.json();
+ const { event } = body;
const parts = parseContent(event);
const names = {};
@@ -117,12 +118,12 @@ export default {
}
}
- res.send({ parts, names });
+ return c.json({ parts, names });
},
- async thread(req, res) {
+ async thread(c) {
try {
- const { id } = req.params;
+ const id = c.req.param("id");
const event = await get({ ids: [id] });
@@ -147,16 +148,16 @@ export default {
}
}
- res.send(thread);
+ return c.json(thread);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async zaps(req, res) {
+ async zaps(c) {
try {
- let { id } = req.params;
+ let id = c.req.param("id");
if (id.startsWith("nevent")) id = decode(id).data.id;
if (id.startsWith("note")) id = decode(id).data;
const filter = { kinds: [9735], "#e": [id] };
@@ -165,7 +166,7 @@ export default {
await sync("wss://relay.primal.net", filter);
events = await scan(filter);
}
- if (!events.length) return res.send([]);
+ if (!events.length) return c.json([]);
const zaps = [];
for (const { tags } of events) {
@@ -187,17 +188,19 @@ export default {
}
}
- res.send(zaps.filter((z) => z.amount > 0));
+ return c.json(zaps.filter((z) => z.amount > 0));
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async publish(req, res) {
+ async publish(c) {
try {
- const { event } = req.body;
- const { pubkey } = req.user;
+ const body = await c.req.json();
+ const { event } = body;
+ const user = c.get("user");
+ const { pubkey } = user;
await publish(event);
@@ -206,17 +209,15 @@ export default {
db.del(`${pubkey}:follows:n`);
}
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async events(req, res) {
- const {
- params: { pubkey },
- } = req;
+ async events(c) {
+ const pubkey = c.req.param("pubkey");
try {
const events = await q({ kinds: [1], authors: [pubkey], limit: 20 });
@@ -233,25 +234,25 @@ export default {
}
}
- res.send(events);
+ return c.json(events);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async follows(req, res) {
- const {
- params: { pubkey },
- query: { limit = 20, offset = 0, pubkeysOnly },
- } = req;
+ async follows(c) {
+ const pubkey = c.req.param("pubkey");
+ const limit = parseInt(c.req.query("limit") || "20");
+ const offset = parseInt(c.req.query("offset") || "0");
+ const pubkeysOnly = c.req.query("pubkeysOnly");
try {
const k = `${pubkey}:follows${pubkeysOnly ? ":pubkeys" : ""}`;
let follows = await g(k);
- if (follows?.length) return res.send(follows);
+ if (follows?.length) return c.json(follows);
const event = await get({ authors: [pubkey], kinds: [3] });
- if (!event) return res.send([]);
+ if (!event) return c.json([]);
let pubkeys = event.tags.filter((tag) => tag[0] === "p").map((tag) => tag[1]);
@@ -273,24 +274,23 @@ export default {
await db.set(k, JSON.stringify(follows), { EX });
- res.send(follows);
+ return c.json(follows);
} catch (e) {
console.log("follows fail", e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async followers(req, res) {
- const {
- params: { pubkey },
- query: { limit = 20, offset = 0 },
- } = req;
+ async followers(c) {
+ const pubkey = c.req.param("pubkey");
+ const limit = parseInt(c.req.query("limit") || "20");
+ const offset = parseInt(c.req.query("offset") || "0");
try {
let followers = await g(`${pubkey}:followers`);
- if (followers?.length) return res.send(followers);
+ if (followers?.length) return c.json(followers);
const events = await q({ kinds: [3], "#p": [pubkey], limit });
- if (!events.length) return res.send([]);
+ if (!events.length) return c.json([]);
const pubkeys = events.map((e) => e.pubkey).slice(offset, offset + limit);
followers = (
@@ -306,28 +306,25 @@ export default {
await db.set(`${pubkey}:followers`, JSON.stringify(followers), { EX });
- res.send(followers);
+ return c.json(followers);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async count(req, res) {
+ async count(c) {
try {
- const { pubkey } = req.params;
- // res.send({ followers: 0, follows: 0 });
- res.send(await getCount(pubkey));
+ const pubkey = c.req.param("pubkey");
+ return c.json(await getCount(pubkey));
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async identities(req, res) {
- const {
- query: { name },
- } = req;
+ async identities(c) {
+ const name = c.req.query("name");
let names = {};
if (name) {
names = { [name]: (await getUser(name, fields)).pubkey };
@@ -339,25 +336,26 @@ export default {
}
}
- res.send({ names });
+ return c.json({ names });
},
- async info(_, res) {
- res.send({ pubkey: serverPubkey });
+ async info(c) {
+ return c.json({ pubkey: serverPubkey });
},
- async profile(req, res) {
- const { profile } = req.params;
+ async profile(c) {
+ const profile = c.req.param("profile");
const { data } = decode(profile);
const { pubkey, relays } = data as ProfilePointer;
const recipient = await getProfile(pubkey, relays);
recipient.relays = relays;
- res.send(recipient);
+ return c.json(recipient);
},
- async zapRequest(req, res) {
+ async zapRequest(c) {
try {
- const { amount, id } = req.body;
+ const body = await c.req.json();
+ const { amount, id } = body;
const { pubkey } = await get({ ids: [id] });
const event = await makeZapRequest({
profile: pubkey,
@@ -367,15 +365,16 @@ export default {
comment: "",
});
- res.send(event);
+ return c.json(event);
} catch (e) {
console.log(e);
}
},
- async zap(req, res) {
+ async zap(c) {
try {
- const { event } = req.body;
+ const body = await c.req.json();
+ const { event } = body;
const amount = event.tags.find((t) => t[0] === "amount")[1];
const pubkey = event.tags.find((t) => t[0] === "p")[1];
const content = JSON.stringify(await getProfile(pubkey));
@@ -386,10 +385,10 @@ export default {
const url = `${callback}?amount=${amount}&nostr=${encodedEvent}`;
const json = await got(url).json();
- res.send(json);
+ return c.json(json);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/payments.ts b/routes/payments.ts
index 33bf3381..ab7a6711 100644
--- a/routes/payments.ts
+++ b/routes/payments.ts
@@ -41,12 +41,13 @@ import got from "got";
import { v4 } from "uuid";
export default {
- async info(_, res) {
- res.send(await ln.getinfo());
+ async info(c) {
+ return c.json(await ln.getinfo());
},
- async create(req, res) {
- const { body, user } = req;
+ async create(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
let { amount, hash, fee, fund, memo, payreq, aid } = body;
const balance = await getBalance(user.id);
@@ -93,7 +94,6 @@ export default {
await s(`payment:${p.id}`, p);
await s(`payment:${txid}`, p.id);
- // Instant vault notification
try {
const vaultInfo = await g(`arkaddr:${invoice.text}`);
if (vaultInfo) {
@@ -125,7 +125,6 @@ export default {
throw e;
}
} else {
- // Check if recipient account is non-custodial (vault)
const recipientAccount = invoice?.aid ? await g(`account:${invoice.aid}`) : null;
if (recipientAccount?.pubkey || recipientAccount?.seed) {
p = await sendOnchain({
@@ -157,19 +156,24 @@ export default {
}
}
- res.send(p);
+ return c.json(p);
} catch (e) {
warn(user.username, "payment failed", amount, balance, hash, payreq);
err(e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async list(req, res) {
- let {
- user: { id },
- query: { aid, start, end, limit, offset, received },
- } = req;
+ async list(c) {
+ const user = c.get("user");
+ let { id } = user;
+ let aid = c.req.query("aid");
+ const start = c.req.query("start");
+ const end = c.req.query("end");
+ let limit = c.req.query("limit");
+ let offset = c.req.query("offset");
+ const received = c.req.query("received");
+
if (!aid || aid === "undefined") aid = id;
const index = await db.lPos(`${id}:accounts`, aid);
@@ -244,30 +248,27 @@ export default {
const { length: count } = payments;
if (limit) payments = payments.slice(offset, offset + limit);
- res.send({ payments, count, incoming, outgoing });
+ return c.json({ payments, count, incoming, outgoing });
},
- async get(req, res) {
+ async get(c) {
try {
- const {
- params: { hash },
- } = req;
+ const hash = c.req.param("hash");
const p = await getPayment(hash);
if (p?.type === PaymentType.internal) p.with = await getUser(p.ref, fields);
if (p?.type === PaymentType.fund) p.with = await getUser(p.uid, fields);
- res.send(p);
+ return c.json(p);
} catch (e) {
console.log(e);
err("failed to get payment", e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async parse(req, res) {
- const {
- body: { payreq },
- user,
- } = req;
+ async parse(c) {
+ const body = await c.req.json();
+ const { payreq } = body;
+ const user = c.get("user");
try {
const hour = 1000 * 60 * 60;
let nodes = await g("nodes");
@@ -297,20 +298,18 @@ export default {
const covered = Math.min(creditBal, ourfee) || 0;
ourfee -= covered;
- res.send({ alias, amount, ourfee });
+ return c.json({ alias, amount, ourfee });
} catch (e) {
console.log(e);
err("problem parsing", e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async fund(req, res) {
- const {
- params: { id },
- } = req;
+ async fund(c) {
+ const id = c.req.param("id");
const amount = await getFundBalance(id);
- if (amount === null) return bail(res, "fund not found");
+ if (amount === null) return bail(c, "fund not found");
let payments = (await db.lRange(`fund:${id}:payments`, 0, -1)) || [];
payments = await Promise.all(payments.map((hash) => gf(`payment:${hash}`)));
@@ -319,30 +318,14 @@ export default {
payments = payments.filter((p) => p);
const authorization = await g(`authorization:${id}`);
- res.send({ amount, authorization: authorization?.amount, payments });
+ return c.json({ amount, authorization: authorization?.amount, payments });
},
- // async withdraw(req, res) {
- // const {
- // params: { name },
- // } = req;
- // const { user } = req;
- // const balance = await g(`fund:${name}`);
- // const managers = await db.sMembers(`fund:${name}:managers`);
- // if (managers.length && !managers.includes(user.id)) fail("Unauthorized");
- // res.send({
- // tag: "withdrawRequest",
- // callback: `${URL}/api/lnurlw`,
- // k1: name,
- // defaultDescription: `Withdraw from coinos fund ${name}`,
- // minWithdrawable: balance > 0 ? 1000 : 0,
- // maxWithdrawable: balance * 1000,
- // });
- // },
-
- async authorize(req, res) {
- const { id: uid } = req.user;
- const { id, fiat, currency, amount } = req.body;
+ async authorize(c) {
+ const user = c.get("user");
+ const { id: uid } = user;
+ const body = await c.req.json();
+ const { id, fiat, currency, amount } = body;
const managers = await db.sMembers(`fund:${id}:managers`);
if (managers.length && !managers.includes(uid)) fail("Unauthorized");
@@ -355,14 +338,13 @@ export default {
};
await s(`authorization:${id}`, authorization);
- res.send({});
+ return c.json({});
},
- async take(req, res) {
- let {
- body: { id, amount, invoice: iid },
- user,
- } = req;
+ async take(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
+ let { id, amount, invoice: iid } = body;
try {
amount = Number.parseInt(amount);
if (amount <= 0) fail("Invalid amount");
@@ -421,15 +403,15 @@ export default {
await db.lPush(`fund:${id}:payments`, payment.id);
- res.send(payment);
+ return c.json(payment);
} catch (e) {
warn("problem withdrawing from fund", user.username, e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async managers(req, res) {
- const { name } = req.params;
+ async managers(c) {
+ const name = c.req.param("name");
const ids = await db.sMembers(`fund:${name}:managers`);
@@ -437,12 +419,13 @@ export default {
Boolean,
);
- res.send(managers);
+ return c.json(managers);
},
- async addManager(req, res) {
- const { id, username } = req.body;
- const { user } = req;
+ async addManager(c) {
+ const body = await c.req.json();
+ const { id, username } = body;
+ const user = c.get("user");
const k = `fund:${id}:managers`;
@@ -463,14 +446,15 @@ export default {
if (!managers.length)
managers = await Promise.all(ids.map(async (id) => await getUser(id, fields)));
- res.send(managers);
+ return c.json(managers);
},
- async deleteManager(req, res) {
+ async deleteManager(c) {
try {
- const { name } = req.params;
- const { id: uid } = req.body;
- const { user } = req;
+ const name = c.req.param("name");
+ const body = await c.req.json();
+ const { id: uid } = body;
+ const user = c.get("user");
const k = `fund:${name}:managers`;
let managers = await db.sMembers(k);
@@ -484,16 +468,15 @@ export default {
const ids = await db.sMembers(k);
managers = await Promise.all(ids.map(async (id) => await getUser(id, fields)));
- res.send(managers);
+ return c.json(managers);
} catch (e) {}
},
- async confirm(req, res) {
- const {
- body: { txid, wallet, type },
- } = req;
+ async confirm(c) {
+ const body = await c.req.json();
+ const { txid, wallet, type } = body;
- if (type !== PaymentType.liquid) return res.send({});
+ if (type !== PaymentType.liquid) return c.json({});
try {
const node = rpc({ ...config[type], wallet });
@@ -546,17 +529,18 @@ export default {
await completePayment(invoice, p, user);
}
}
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
warn(`problem processing ${txid}`);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async txWebhook(req, res) {
- const { txid, secret } = req.body || {};
- const headerSecret = req.headers["x-hook-secret"];
+ async txWebhook(c) {
+ const body = await c.req.json();
+ const { txid, secret } = body || {};
+ const headerSecret = c.req.header("x-hook-secret");
const hookSecret = secret || headerSecret;
try {
@@ -566,59 +550,59 @@ export default {
const tx = await getTx(txid);
await processWatchedTx(tx);
- res.send({});
+ return c.json({});
} catch (e) {
warn("problem processing tx webhook", e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async fee(req, res) {
- const { body, user } = req;
+ async fee(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
try {
- res.send(await build({ ...body, user }));
+ return c.json(await build({ ...body, user }));
} catch (e) {
warn("problem estimating fee", e.message, user.username, body.amount, body.address);
let msg = e.message;
if (msg.includes("500")) msg = "";
- bail(res, `Failed to prepare transaction ${msg}`);
+ return bail(c, `Failed to prepare transaction ${msg}`);
}
},
- async send(req, res) {
- const { body, user } = req;
+ async send(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
try {
await requirePin({ body, user });
const { hash: txid } = await sendOnchain({ ...body, user });
const pid = await g(`payment:${txid}`);
const p = await g(`payment:${pid}`);
- res.send(p);
+ return c.json(p);
} catch (e) {
warn(user.username, "payment failed", e.message);
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async freeze(req, res) {
- const {
- body: { secret },
- } = req;
+ async freeze(c) {
+ const body = await c.req.json();
+ const { secret } = body;
try {
if (secret !== config.adminpass) fail("unauthorized");
await s("freeze", true);
- res.send("ok");
+ return c.json("ok");
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async print(req, res) {
- const {
- body: { id },
- user,
- } = req;
+ async print(c) {
+ const body = await c.req.json();
+ const { id } = body;
+ const user = c.get("user");
try {
const p = await gf(`payment:${id}`);
if (!p) fail("Payment not found");
@@ -629,18 +613,17 @@ export default {
mqtt.publish(username, `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`);
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async lnaddress(req, res) {
- let {
- params: { lnaddress, amount },
- body,
- user,
- } = req;
+ async lnaddress(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
+ let lnaddress = c.req.param("lnaddress");
+ let amount = c.req.param("amount");
const { fee } = body;
try {
lnaddress = decodeURIComponent(lnaddress);
@@ -667,27 +650,25 @@ export default {
await credit({ hash: pr, amount, memo, ref: user.id });
} else p = await sendLightning({ user, pr, amount, fee, memo });
- res.send(p);
+ return c.json(p);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async gateway(req, res) {
- const {
- body: { short_channel_id, webhook },
- } = req;
+ async gateway(c) {
+ const body = await c.req.json();
+ const { short_channel_id, webhook } = body;
await s(short_channel_id, webhook);
- res.send({ ok: true });
+ return c.json({ ok: true });
},
- async replace(req, res) {
- const {
- body: { id },
- user,
- } = req;
+ async replace(c) {
+ const body = await c.req.json();
+ const { id } = body;
+ const user = c.get("user");
try {
const p = await gf(`payment:${id}`);
if (!p) fail("Payment not found");
@@ -724,47 +705,50 @@ export default {
if (!r[0].allowed) fail(`transaction rejected ${p.hex}`);
warn("bump", user.username, p.hex);
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
err("failed to bump payment", id, e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async internal(req, res) {
- const {
- body: { username, amount },
- user: sender,
- } = req;
+ async internal(c) {
+ const body = await c.req.json();
+ const { username, amount } = body;
+ const sender = c.get("user");
const recipient = await getUser(username);
- res.send(await sendInternal({ amount, sender, recipient }));
+ return c.json(await sendInternal({ amount, sender, recipient }));
},
- async decode(req, res) {
- const { bolt11 } = req.params;
- res.send(await ln.decode(bolt11));
+ async decode(c) {
+ const bolt11 = c.req.param("bolt11");
+ return c.json(await ln.decode(bolt11));
},
- async fetchinvoice(req, res) {
- const { amount, offer } = req.body;
- res.send(await ln.fetchinvoice(offer, amount ? amount * 1000 : null));
+ async fetchinvoice(c) {
+ const body = await c.req.json();
+ const { amount, offer } = body;
+ return c.json(await ln.fetchinvoice(offer, amount ? amount * 1000 : null));
},
- async auth(req, res) {
- console.log(req.query);
- res.send(req.query);
+ async auth(c) {
+ const query = c.req.query();
+ console.log(query);
+ return c.json(query);
},
- async order(req, res) {
- console.log(req.body);
- res.send(req.body);
+ async order(c) {
+ const body = await c.req.json();
+ console.log(body);
+ return c.json(body);
},
- async sendinvoice(req, res) {
+ async sendinvoice(c) {
try {
- const { user } = req;
- const { invreq } = req.body;
+ const user = c.get("user");
+ const body = await c.req.json();
+ const { invreq } = body;
const { amount_msat, bolt12, pay_index } = await ln.sendinvoice({
invreq,
@@ -782,14 +766,15 @@ export default {
const p = await replay(pay_index);
- res.send(p);
+ return c.json(p);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async ark(req, res) {
- const { body, user } = req;
+ async ark(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
const { address, amount, aid } = body;
try {
@@ -812,16 +797,11 @@ export default {
await s(`payment:${p.id}`, p);
await s(`payment:${txid}`, p.id);
- // Instant vault notification: if destination is a vault on our platform,
- // create the receive payment immediately so the client doesn't have to
- // wait for ASP indexer + arkSync backoff delays
try {
const vaultInfo = await g(`arkaddr:${address}`);
if (vaultInfo) {
const { aid: vaultAid, uid: vaultUid } = vaultInfo;
- // Skip notification if this is a forward (e.g. vault → lightning);
- // the funds are transient and will leave again immediately
const isForward = await g(`custodial-ark-invoice:${address}`);
const vaultOwner = await g(`user:${vaultUid}`);
@@ -848,16 +828,17 @@ export default {
throw e;
}
- res.send(p);
+ return c.json(p);
} catch (e) {
warn(user.username, "ark payment failed", e.message);
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async arkVaultSend(req, res) {
+ async arkVaultSend(c) {
try {
- const { body, user } = req;
+ const body = await c.req.json();
+ const user = c.get("user");
const { hash, amount, aid } = body;
const { rate, currency } = await getUserRate(user);
@@ -871,15 +852,16 @@ export default {
mapHashToId: true,
});
- res.send(p);
+ return c.json(p);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async arkVaultReceive(req, res) {
+ async arkVaultReceive(c) {
try {
- const { body, user } = req;
+ const body = await c.req.json();
+ const user = c.get("user");
const { amount, hash, aid } = body;
if (amount <= 0) fail("Invalid amount");
@@ -895,19 +877,20 @@ export default {
currency,
});
- res.send(p);
+ return c.json(p);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async arkAddress(_, res) {
- res.send(await getArkAddress());
+ async arkAddress(c) {
+ return c.json(await getArkAddress());
},
- async arkReceive(req, res) {
+ async arkReceive(c) {
try {
- const { body, user } = req;
+ const body = await c.req.json();
+ const user = c.get("user");
const { iid, amount, hash } = body;
const { id: uid } = user;
@@ -921,7 +904,6 @@ export default {
if (hash) {
await acquireArkLock(hash);
- // Skip VTXO verification for vault→custodial (same user, debit handled by arkVaultSend)
if (!(aid && aid !== uid)) {
if (!(await verifyArkVtxo(hash))) fail("VTXO not found in server wallet");
}
@@ -931,7 +913,6 @@ export default {
const { rates } = await getUserRate(user);
const rate = rates[currency];
- // Always credit custodial balance (Ark funds received by server)
await tbCredit(uid, uid, type, amount, false);
const p = await createArkPayment({
@@ -951,26 +932,25 @@ export default {
await completePayment(invoice, p, user);
- // Clean up custodial ark invoice index
if (invoice.text) await db.del(`custodial-ark-invoice:${invoice.text}`);
- res.send(p);
+ return c.json(p);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async arkSync(req, res) {
+ async arkSync(c) {
try {
- const { user } = req;
- const { transactions = [], aid } = req.body;
+ const user = c.get("user");
+ const body = await c.req.json();
+ const { transactions = [], aid } = body;
const { id: uid } = user;
- l("arkSync", user.username, aid, "txs:", transactions.length, "bal:", req.body.balance);
+ l("arkSync", user.username, aid, "txs:", transactions.length, "bal:", body.balance);
- // Per-account lock prevents concurrent syncs from racing
const lockKey = `arksynclock:${aid}`;
const gotLock = await db.set(lockKey, "1", { NX: true, EX: 30 });
- if (!gotLock) return res.send({ synced: 0, received: 0, payments: [] });
+ if (!gotLock) return c.json({ synced: 0, received: 0, payments: [] });
try {
const { rate, currency } = await getUserRate(user);
@@ -983,7 +963,6 @@ export default {
const hashes = [tx.arkTxid, tx.commitmentTxid, tx.hash].filter(Boolean);
if (!hashes.length) continue;
- // Check if any hash is already known
let found = false;
for (const h of hashes) {
const existingId = await g(`payment:${aid}:${h}`);
@@ -999,8 +978,6 @@ export default {
}
if (found) continue;
- // Skip sent/zero transactions not recorded via vault-send — these are
- // from internal wallet operations (settle, refresh) not real sends
if (tx.amount <= 0) continue;
const primaryHash = hashes[0];
@@ -1021,7 +998,6 @@ export default {
if (tx.amount > 0) {
received += tx.amount;
- // Find an unpaid ark invoice for this account and mark it received
const invoiceIds = await db.lRange(`${aid}:invoices`, 0, 20);
for (const iid of invoiceIds) {
const inv = await getInvoice(iid);
@@ -1038,8 +1014,7 @@ export default {
}
}
- // Reconcile expired VTXOs
- const { balance } = req.body;
+ const { balance } = body;
if (typeof balance === "number" && balance >= 0) {
const paymentIds = await db.lRange(`${aid}:payments`, 0, -1);
let expectedBalance = 0;
@@ -1049,8 +1024,6 @@ export default {
const pay = await g(`payment:${pid}`);
if (pay) {
expectedBalance += pay.amount;
- // Skip reconciliation if there are recent payments the SDK
- // may not have indexed yet (e.g. instant vault credits)
if (pay.amount > 0 && now - pay.created < 120_000) {
hasRecentPayments = true;
}
@@ -1075,7 +1048,6 @@ export default {
}
}
- // Check for pending custodial ark invoice to forward
let forward;
if (received > 0) {
const account = await g(`account:${aid}`);
@@ -1098,12 +1070,12 @@ export default {
}
}
- res.send({ synced, received, payments, forward });
+ return c.json({ synced, received, payments, forward });
} finally {
await db.del(lockKey);
}
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/rates.ts b/routes/rates.ts
index e532ec78..d4426a6e 100644
--- a/routes/rates.ts
+++ b/routes/rates.ts
@@ -2,17 +2,17 @@ import { g } from "$lib/db";
import { rate } from "$lib/rates";
export default {
- async fx(_, res) {
+ async fx(c) {
const { fx } = await g("fx");
- res.send({ fx });
+ return c.json({ fx });
},
- async last(_, res) {
- res.send(rate || (await g("rate")));
+ async last(c) {
+ return c.json(rate || (await g("rate")));
},
- async index(_, res) {
+ async index(c) {
const { date, fx, ...rates } = await g("rates");
- res.send(rates);
+ return c.json(rates);
},
};
diff --git a/routes/shopify.ts b/routes/shopify.ts
index d955d73d..abd7c81b 100644
--- a/routes/shopify.ts
+++ b/routes/shopify.ts
@@ -3,21 +3,20 @@ import { g } from "$lib/db";
import { l, err } from "$lib/logging";
import { getPayment } from "$lib/utils";
-const query = `mutation orderMarkAsPaid($input: OrderMarkAsPaidInput!) {
- orderMarkAsPaid(input: $input) {
- order { id }
- userErrors {
+const query = `mutation orderMarkAsPaid($input: OrderMarkAsPaidInput!) {
+ orderMarkAsPaid(input: $input) {
+ order { id }
+ userErrors {
field
message
}
}
}`;
-export default async (req, res) => {
- const {
- body: { hash },
- params: { id },
- } = req;
+export default async (c) => {
+ const body = await c.req.json();
+ const { hash } = body;
+ const id = c.req.param("id");
const p = await getPayment(hash);
const user = await g(`user:${p.uid}`);
@@ -39,7 +38,7 @@ export default async (req, res) => {
l("shopify success", r);
- res.send(r);
+ return c.json(r);
} catch (e) {
err("problem marking shopify order as paid", e.message);
}
diff --git a/routes/square.ts b/routes/square.ts
index f738e1ef..269cdd0d 100644
--- a/routes/square.ts
+++ b/routes/square.ts
@@ -12,11 +12,11 @@ import { v4 } from "uuid";
const { scopes, url, appId, clientSecret, environment } = config.square;
export default {
- async connect(req, res) {
- const { user } = req;
+ async connect(c) {
+ const user = c.get("user");
if (await db.exists(`${user.id}:square`)) {
- return res.send("connected");
+ return c.json("connected");
}
const codeVerifier = base64.encode(crypto.randomBytes(32));
@@ -27,13 +27,13 @@ export default {
const state = base64.encode(crypto.randomBytes(12));
const scope = scopes.join("+");
- res.send(
+ return c.json(
`${url}oauth2/authorize?client_id=${appId}&session=false&scope=${scope}&state=${state}&code_challenge=${challenge}`,
);
},
- async auth(req, res) {
- const { user } = req;
+ async auth(c) {
+ const user = c.get("user");
const codeVerifier = await g(`${user.id}:codeVerifier`);
@@ -41,7 +41,7 @@ export default {
environment,
});
- const { code } = req.query;
+ const code = c.req.query("code");
try {
const result = await client.oAuth.obtainToken({
@@ -53,15 +53,15 @@ export default {
await s(`${user.id}:square`, result);
await s(result.merchantId, user.id);
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
}
},
- async payment(req, res) {
+ async payment(c) {
try {
- const { body } = req;
+ const body = await c.req.json();
const { data, type, merchant_id } = body;
const { payment } = data.object;
@@ -86,9 +86,9 @@ export default {
await generate({ invoice, user });
}
- res.send({});
+ return c.json({});
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/users.ts b/routes/users.ts
index 07e4fb85..f3b747af 100644
--- a/routes/users.ts
+++ b/routes/users.ts
@@ -27,6 +27,7 @@ import { getPublicKey, nip19, verifyEvent } from "nostr-tools";
import { hexToBytes } from "@noble/hashes/utils.js";
import { authenticator } from "otplib";
import { v4 } from "uuid";
+import { setCookie } from "hono/cookie";
import { PaymentType } from "$lib/types";
import { createBalanceAccount, getBalance, getCredit, getPending } from "$lib/tb";
@@ -36,16 +37,14 @@ import type { ProfilePointer } from "nostr-tools/nip19";
const { host } = new URL(process.env.URL);
const relay = encodeURIComponent(config.publicRelay);
-const verifyRecaptcha = async (response, req?) => {
+const verifyRecaptcha = async (response, c?, body?) => {
const { recaptcha: secret } = config;
if (!secret) return true;
- // Skip captcha for Tor users accessing via .onion
- const host = req?.headers?.["host"] || "";
- if (host.endsWith(".onion")) return true;
+ const reqHost = c?.req?.header("host") || "";
+ if (reqHost.endsWith(".onion")) return true;
- // Skip captcha for allowlisted User-Agents (prefix match)
- const uaRaw = req?.headers?.["user-agent"] || req?.headers?.["User-Agent"];
+ const uaRaw = c?.req?.header("user-agent");
const ua = Array.isArray(uaRaw) ? uaRaw[0] : uaRaw;
if (typeof ua === "string") {
const prefixes = await db.sMembers("nocaptcha_ua");
@@ -55,20 +54,19 @@ const verifyRecaptcha = async (response, req?) => {
}
}
- // Check for valid API key to bypass captcha
- const apiKey = req?.headers?.["x-api-key"];
+ const apiKey = c?.req?.header("x-api-key");
if (apiKey && (await db.sIsMember("apikeys", apiKey))) {
return true;
}
- const { username } = req.body;
+ const { username } = body || {};
if (username && (await db.sIsMember("nocaptcha", username.toLowerCase().replace(/\s/g, ""))))
return true;
if (!response) return false;
try {
- const ip = req?.headers?.["cf-connecting-ip"] || req?.socket?.remoteAddress;
+ const ip = c?.req?.header("cf-connecting-ip") || c?.env?.ip;
const { success } = await got
.post("https://www.google.com/recaptcha/api/siteverify", {
form: {
@@ -87,8 +85,8 @@ const verifyRecaptcha = async (response, req?) => {
export default {
upload,
- async me(req, res) {
- const { user } = req;
+ async me(c) {
+ const user = c.get("user");
try {
user.balance = await getBalance(user.id);
user.locked = await ga(`balance:${user.id}`);
@@ -104,15 +102,15 @@ export default {
user.prompt = !!user.prompt;
if (user.pubkey) user.npub = nip19.npubEncode(user.pubkey);
- res.send(pick(user, whitelist));
+ return c.json(pick(user, whitelist));
} catch (e) {
console.log("problem fetching user", e);
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async list(req, res) {
- const { user } = req;
+ async list(c) {
+ const user = c.get("user");
if (!user.admin) fail("unauthorized");
const users = [];
@@ -140,13 +138,11 @@ export default {
users.push(u);
}
- res.send(users);
+ return c.json(users);
},
- async get(req, res) {
- let {
- params: { key },
- } = req;
+ async get(c) {
+ let key = c.req.param("key");
key = key.toLowerCase().replace(/\s/g, "");
try {
if (key.startsWith("npub")) {
@@ -162,43 +158,43 @@ export default {
}
const user = await getNostrUser(key);
- res.send(pick(user, fields));
+ return c.json(pick(user, fields));
} catch (e) {
err("problem getting user", key, e.message);
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async create(req, res) {
- const { body, headers } = req;
+ async create(c) {
+ const body = await c.req.json();
+ const headers = c.req.header();
try {
const ip = headers["cf-connecting-ip"];
if (!body.user) fail("no user object provided");
let { user } = body;
- const fields = ["pubkey", "password", "username", "picture", "fresh", "authPubkey"];
- user = await register(pick(user, fields), ip);
+ const flds = ["pubkey", "password", "username", "picture", "fresh", "authPubkey"];
+ user = await register(pick(user, flds), ip);
const payload = { id: user.id };
const token = jwt.sign(payload, config.jwt);
l("registered new user", user.username);
- res.send({ ...pick(user, whitelist), sk: user.sk, token });
+ return c.json({ ...pick(user, whitelist), sk: user.sk, token });
} catch (e) {
err("problem registering", e.message);
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async disable2fa(req, res) {
- const {
- user,
- body: { token },
- } = req;
+ async disable2fa(c) {
+ const user = c.get("user");
+ const body = await c.req.json();
+ const { token } = body;
const { id, twofa, username, otpsecret } = user;
if (twofa && !authenticator.check(token, otpsecret)) {
- return res.code(401).send("2fa required");
+ return c.json("2fa required", 401);
}
user.twofa = false;
@@ -206,15 +202,14 @@ export default {
emit(username, "user", user);
emit(username, "otpsecret", user.otpsecret);
l("disabled 2fa", username);
- res.send({});
+ return c.json({});
},
- async enable2fa(req, res) {
+ async enable2fa(c) {
try {
- const {
- user,
- body: { token },
- } = req;
+ const user = c.get("user");
+ const body = await c.req.json();
+ const { token } = body;
const { id, otpsecret, username } = user;
const isValid = authenticator.check(token, otpsecret);
if (isValid) {
@@ -222,21 +217,22 @@ export default {
await s(`user:${id}`, user);
emit(username, "user", user);
} else {
- return res.code(500).send("Invalid token");
+ return c.json("Invalid token", 500);
}
l("enabled 2fa", username);
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async update(req, res) {
- const { user, body } = req;
+ async update(c) {
+ const user = c.get("user");
+ const body = await c.req.json();
try {
- const { id: tokid } = jwt.verify(req.headers.authorization.split(" ")[1], config.jwt);
+ const { id: tokid } = jwt.verify(c.req.header("authorization").split(" ")[1], config.jwt);
l("updating user", user.username, tokid);
if (user.id !== tokid) fail("unauthorized");
@@ -265,8 +261,8 @@ export default {
const event = JSON.parse(body.event);
const challenge = event.tags.find((t) => t[0] === "challenge")[1];
- const c = await g(`challenge:${challenge}`);
- if (!c) fail("Invalid or expired challenge");
+ const ch = await g(`challenge:${challenge}`);
+ if (!ch) fail("Invalid or expired challenge");
if (!verifyEvent(event) || event.pubkey !== pubkey)
fail("Invalid signature or challenge mismatch.");
@@ -361,27 +357,28 @@ export default {
if (user.nip5) await db.sAdd("nip5", `${user.username}:${user.pubkey}`);
emit(user.id, "user", user);
- res.send({ user });
+ return c.json({ user });
} catch (e) {
console.log(e);
warn("failed to update", user.username, e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async login(req, res) {
+ async login(c) {
try {
- let { username, password, token: twofa, recaptcha } = req.body;
- const ip = req.headers["cf-connecting-ip"] || req.socket.remoteAddress;
+ const body = await c.req.json();
+ let { username, password, token: twofa, recaptcha } = body;
+ const ip = c.req.header("cf-connecting-ip") || c.env?.ip || "unknown";
const ipKey = `ip:${ip}:login`;
const ipCount = await db.incr(ipKey);
if (ipCount === 1) await db.expire(ipKey, 10);
- if (ipCount > 30) return res.code(429).send({});
+ if (ipCount > 30) return c.json({}, 429);
- const recaptchaOk = await verifyRecaptcha(recaptcha, req);
+ const recaptchaOk = await verifyRecaptcha(recaptcha, c, body);
if (!recaptchaOk) {
- return res.code(401).send("failed captcha");
+ return c.json("failed captcha", 401);
}
username = username.toLowerCase().replace(/\s/g, "");
@@ -391,7 +388,7 @@ export default {
const failures = await g(fk);
const ipFailKey = `ip:${ip}:login:fail`;
const ipFailures = await g(ipFailKey);
- if (ipFailures > 20) return res.code(429).send({});
+ if (ipFailures > 20) return c.json({}, 429);
let user = await getUser(username);
@@ -401,7 +398,7 @@ export default {
if (!(await db.ttl(ipFailKey))) await db.expire(ipFailKey, 600);
await db.incrBy(fk, 1);
setTimeout(() => db.decrBy(fk, 1), 120000);
- return res.code(401).send({});
+ return c.json({}, 401);
}
if (
@@ -410,56 +407,57 @@ export default {
) {
await db.incrBy(ipFailKey, 1);
if (!(await db.ttl(ipFailKey))) await db.expire(ipFailKey, 600);
- return res.code(401).send("2fa required");
+ return c.json("2fa required", 401);
}
}
- if (req.body.authPubkey && !user.authPubkey) {
- user.authPubkey = req.body.authPubkey;
+ if (body.authPubkey && !user.authPubkey) {
+ user.authPubkey = body.authPubkey;
await s(`user:${user.id}`, user);
}
- if (username !== "coinos") l("logged in", username, req.headers["cf-connecting-ip"]);
+ if (username !== "coinos") l("logged in", username, c.req.header("cf-connecting-ip"));
const payload = { id: user.id };
const token = jwt.sign(payload, config.jwt);
- res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
+ setCookie(c, "token", token, { expires: new Date(Date.now() + 432000000), path: "/" });
user = pick(user, whitelist);
- res.send({ user, token });
+ return c.json({ user, token });
} catch (e) {
console.log(e);
- err("login error", e.message, req.socket.remoteAddress);
- res.code(401).send({});
+ err("login error", e.message, c.env?.ip);
+ return c.json({}, 401);
}
},
- async challenge(req, res) {
+ async challenge(c) {
const id = v4();
await db.set(`challenge:${id}`, id, { EX: 300 });
- const username = req.query?.username;
+ const username = c.req.query("username");
let hasAuthKey = false;
if (username) {
const user = await getUser(username.toLowerCase().replace(/\s/g, ""));
if (user?.authPubkey) hasAuthKey = true;
}
- res.send({ challenge: id, hasAuthKey });
+ return c.json({ challenge: id, hasAuthKey });
},
- async authKeyLogin(req, res) {
+ async authKeyLogin(c) {
try {
- const { event, challenge, username: rawUsername, twofa, recaptcha } = req.body;
- const ip = req.headers["cf-connecting-ip"] || req.socket.remoteAddress;
+ const body = await c.req.json();
+ const { event, challenge, username: rawUsername, twofa, recaptcha } = body;
+ const ip = c.req.header("cf-connecting-ip") || c.env?.ip || "unknown";
const ipKey = `ip:${ip}:login`;
const ipCount = await db.incr(ipKey);
if (ipCount === 1) await db.expire(ipKey, 10);
- if (ipCount > 30) return res.code(429).send({});
+ if (ipCount > 30) return c.json({}, 429);
- const recaptchaOk = await verifyRecaptcha(recaptcha, req);
- if (!recaptchaOk) return res.code(401).send("failed captcha");
+ const recaptchaOk = await verifyRecaptcha(recaptcha, c, body);
+ if (!recaptchaOk) return c.json("failed captcha", 401);
- const c = await g(`challenge:${challenge}`);
- if (!c) fail("Invalid or expired challenge");
+ const ch = await g(`challenge:${challenge}`);
+ if (!ch) fail("Invalid or expired challenge");
const { kind } = event;
if (kind !== 27235) fail("Invalid event");
@@ -477,35 +475,36 @@ export default {
user.twofa &&
(typeof twofa === "undefined" || !authenticator.check(twofa, user.otpsecret))
) {
- return res.code(401).send("2fa required");
+ return c.json("2fa required", 401);
}
l("authkey login", username, ip);
const payload = { id: user.id };
const token = jwt.sign(payload, config.jwt);
- res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
+ setCookie(c, "token", token, { expires: new Date(Date.now() + 432000000), path: "/" });
user = pick(user, whitelist);
- res.send({ user, token });
+ return c.json({ user, token });
} catch (e) {
console.log(e);
- err("authkey login error", e.message, req.socket.remoteAddress);
- res.code(401).send({});
+ err("authkey login error", e.message, c.env?.ip);
+ return c.json({}, 401);
}
},
- async nostrAuth(req, res) {
+ async nostrAuth(c) {
try {
- const { event, challenge, twofa, recaptcha } = req.body;
- const ip = req.headers["cf-connecting-ip"];
- const recaptchaOk = await verifyRecaptcha(recaptcha, req);
+ const body = await c.req.json();
+ const { event, challenge, twofa, recaptcha } = body;
+ const ip = c.req.header("cf-connecting-ip");
+ const recaptchaOk = await verifyRecaptcha(recaptcha, c, body);
if (!recaptchaOk) {
- return res.code(401).send("failed captcha");
+ return c.json("failed captcha", 401);
}
- const c = await g(`challenge:${challenge}`);
+ const ch = await g(`challenge:${challenge}`);
const { pubkey: key, kind } = event;
if (kind !== 27235) fail("Invalid event");
- if (!c) fail("Invalid or expired login challenge");
+ if (!ch) fail("Invalid or expired login challenge");
if (!verifyEvent(event) || event.tags.find((t) => t[0] === "challenge")?.[1] !== challenge)
fail("Invalid signature or challenge mismatch.");
@@ -536,86 +535,89 @@ export default {
const payload = { id: user.id };
const token = jwt.sign(payload, config.jwt);
- res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
+ setCookie(c, "token", token, { expires: new Date(Date.now() + 432000000), path: "/" });
user = pick(user, whitelist);
- res.send({ user, token });
+ return c.json({ user, token });
} catch (e) {
console.log(e);
- err("nostr login error", e.message, req.socket.remoteAddress);
- res.code(401).send({});
+ err("nostr login error", e.message, c.env?.ip);
+ return c.json({}, 401);
}
},
- async subscriptions(req, res) {
+ async subscriptions(c) {
try {
- const { user } = req;
+ const user = c.get("user");
const subscriptions = await db.sMembers(`${user.id}:subscriptions`);
- res.send(subscriptions);
+ return c.json(subscriptions);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async subscription(req, res) {
+ async subscription(c) {
try {
- const { subscription } = req.body;
- const { id } = req.user;
+ const body = await c.req.json();
+ const { subscription } = body;
+ const { id } = c.get("user");
await db.sAdd(`${id}:subscriptions`, JSON.stringify(subscription));
- res.send(subscription);
+ return c.json(subscription);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async deleteSubscription(req, res) {
+ async deleteSubscription(c) {
try {
- const { subscription } = req.body;
- const { id } = req.user;
+ const body = await c.req.json();
+ const { subscription } = body;
+ const { id } = c.get("user");
await db.sRem(`${id}:subscriptions`, JSON.stringify(subscription));
- res.send(subscription);
+ return c.json(subscription);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async password(req, res) {
- const {
- body: { password },
- user,
- } = req;
- if (!user.password) return res.send(true);
+ async password(c) {
+ const body = await c.req.json();
+ const { password } = body;
+ const user = c.get("user");
+ if (!user.password) return c.json(true);
try {
if (!password) fail("password not provided");
- res.send(await Bun.password.verify(password, user.password));
+ return c.json(await Bun.password.verify(password, user.password));
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async pin(req, res) {
- const {
- body: { pin },
- user,
- } = req;
- res.send(!user.pin || user.pin === pin);
+ async pin(c) {
+ const body = await c.req.json();
+ const { pin } = body;
+ const user = c.get("user");
+ return c.json(!user.pin || user.pin === pin);
},
- async otpsecret(req, res) {
+ async otpsecret(c) {
try {
- await requirePin(req);
- const { otpsecret, username } = req.user;
- res.send({ secret: otpsecret, username });
+ const user = c.get("user");
+ const body = await c.req.json();
+ await requirePin({ body, user });
+ const { otpsecret, username } = user;
+ return c.json({ secret: otpsecret, username });
} catch (e) {
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async contacts(req, res) {
- const { params, user } = req;
+ async contacts(c) {
+ const user = c.get("user");
const { id } = user;
+ const limit = c.req.param("limit");
const lastlen = (await g(`${id}:lastlen`)) || 0;
const len = await db.lLen(`${id}:payments`);
const payments = (await db.lRange(`${id}:payments`, 0, len - lastlen)) || [];
@@ -654,24 +656,21 @@ export default {
c.trusted = true;
});
- let { limit } = params;
- limit ||= contacts.length;
+ let contactLimit = limit || contacts.length;
contacts = contacts.filter((c) => !pins.includes(c.id));
- contacts = contacts.slice(0, limit);
+ contacts = contacts.slice(0, contactLimit);
const combined = [...pinned, ...contacts];
- res.send(combined);
+ return c.json(combined);
},
- async del(req, res) {
- let {
- params: { username },
- headers: { authorization },
- } = req;
+ async del(c) {
+ let username = c.req.param("username");
+ const authorization = c.req.header("authorization");
fail("Unauthorized");
username = username.toLowerCase();
- if (!authorization?.includes(config.admin)) return res.code(401).send("unauthorized");
+ if (!authorization?.includes(config.admin)) return c.json("unauthorized", 401);
const { id, pubkey } = await g(
`user:${await g(`user:${username.replace(/\s/g, "").toLowerCase()}`)}`,
@@ -685,14 +684,13 @@ export default {
db.del(`user:${id}`);
db.del(`user:${pubkey}`);
- res.send({});
+ return c.json({});
},
- async reset(req, res) {
- const {
- body: { code, username, password },
- user: u,
- } = req;
+ async reset(c) {
+ const body = await c.req.json();
+ const { code, username, password } = body;
+ const u = c.get("user");
try {
const admin = u?.admin;
let id;
@@ -704,7 +702,7 @@ export default {
if (!user) fail("user not found");
- warn("password reset", user.username, code, req.headers["cf-connecting-ip"]);
+ warn("password reset", user.username, code, c.req.header("cf-connecting-ip"));
user.pin = null;
user.nsec = null;
@@ -718,40 +716,38 @@ export default {
await s(`user:${id}`, user);
await db.del(`reset:${code}`);
- res.send(pick(user, whitelist));
+ return c.json(pick(user, whitelist));
} catch (e) {
- err("password reset failed", e.message, req.headers["cf-connecting-ip"]);
- bail(res, e.message);
+ err("password reset failed", e.message, c.req.header("cf-connecting-ip"));
+ return bail(c, e.message);
}
},
- async printerlogin(req, res) {
- const {
- body: { username, topic },
- } = req;
- if (username === topic) res.send({ ok: true });
- else bail(res, "unauthorized");
+ async printerlogin(c) {
+ const body = await c.req.json();
+ const { username, topic } = body;
+ if (username === topic) return c.json({ ok: true });
+ else return bail(c, "unauthorized");
},
- async acl(req, res) {
- const {
- body: { username, topic },
- } = req;
- if (username === topic) res.send({ ok: true });
- else bail(res, "unauthorized");
+ async acl(c) {
+ const body = await c.req.json();
+ const { username, topic } = body;
+ if (username === topic) return c.json({ ok: true });
+ else return bail(c, "unauthorized");
},
- async superuser(req, res) {
- const {
- body: { username },
- } = req;
- if (username === config.mqtt2.username) res.send({ ok: true });
- else bail(res, "unauthorized");
+ async superuser(c) {
+ const body = await c.req.json();
+ const { username } = body;
+ if (username === config.mqtt2.username) return c.json({ ok: true });
+ else return bail(c, "unauthorized");
},
- async request(req, res) {
- const { email } = req.body;
- const { user } = req;
+ async request(c) {
+ const body = await c.req.json();
+ const { email } = body;
+ const user = c.get("user");
const { id } = user;
try {
@@ -777,16 +773,14 @@ export default {
link,
});
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async verify(req, res) {
- const {
- params: { code },
- } = req;
+ async verify(c) {
+ const code = c.req.param("code");
try {
const { id, email } = await g(`verify:${code}`);
if (!id) fail("verification failed");
@@ -796,16 +790,15 @@ export default {
await s(`user:${id}`, user);
await s(`email:${email.toLowerCase()}`, id);
- res.send(pick(user, fields));
+ return c.json(pick(user, fields));
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async forgot(req, res) {
- const {
- body: { email },
- } = req;
+ async forgot(c) {
+ const body = await c.req.json();
+ const { email } = body;
try {
const uid = await g(`email:${email.toLowerCase()}`);
const user = await g(`user:${uid}`);
@@ -821,47 +814,45 @@ export default {
});
}
- res.send({});
+ return c.json({});
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async hidepay(req, res) {
- const {
- body: { username },
- } = req;
+ async hidepay(c) {
+ const body = await c.req.json();
+ const { username } = body;
const u = await getUser(username);
u.hidepay = true;
await s(`user:${u.id}`, u);
- res.send({});
+ return c.json({});
},
- async unlimit(req, res) {
- const {
- body: { username },
- } = req;
+ async unlimit(c) {
+ const body = await c.req.json();
+ const { username } = body;
const u = await getUser(username);
u.unlimited = true;
await s(`user:${u.id}`, u);
- res.send({});
+ return c.json({});
},
- async account(req, res) {
- const { id } = req.params;
- const { id: uid } = req.user;
+ async account(c) {
+ const id = c.req.param("id");
+ const { id: uid } = c.get("user");
const pos = await db.lPos(`${uid}:accounts`, id);
if (pos == null) fail("account not found");
const account = await g(`account:${id}`);
if (account) account.balance = await getBalance(id);
- res.send(account);
+ return c.json(account);
},
- async accounts(req, res) {
+ async accounts(c) {
try {
- const { user } = req;
+ const user = c.get("user");
const accounts = [];
for (const id of await db.lRange(`${user.id}:accounts`, 0, -1)) {
@@ -886,17 +877,18 @@ export default {
}
}
- res.send(accounts.reverse());
+ return c.json(accounts.reverse());
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async createAccount(req, res) {
+ async createAccount(c) {
try {
- const { fingerprint, pubkey, name, seed, type, arkAddress, accountIndex } = req.body;
- const { user } = req;
+ const body = await c.req.json();
+ const { fingerprint, pubkey, name, seed, type, arkAddress, accountIndex } = body;
+ const user = c.get("user");
const { id: uid } = user;
const id = v4();
@@ -917,14 +909,12 @@ export default {
await createBalanceAccount(id);
- // ARK accounts don't need Bitcoin Core wallet setup
if (type === "ark") {
const accountIds = await db.lRange(`${user.id}:accounts`, 0, -1);
for (const accId of accountIds) {
const acc = await g(`account:${accId}`);
if (acc?.type === "ark") {
- bail(res, "You already have an Ark vault");
- return;
+ return bail(c, "You already have an Ark vault");
}
}
const m = db
@@ -934,11 +924,9 @@ export default {
if (arkAddress) m.set(`arkaddr:${arkAddress}`, JSON.stringify({ aid: id, uid }));
await m.exec();
- res.send(account);
- return;
+ return c.json(account);
}
- // Bitcoin on-chain wallet setup
let node = rpc(config[type]);
await node.createWallet({
@@ -976,17 +964,18 @@ export default {
.lPush(`${user.id}:accounts`, id)
.exec();
- res.send(account);
+ return c.json(account);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async updateAccount(req, res) {
- const { id } = req.params;
- const { id: uid } = req.user;
+ async updateAccount(c) {
+ const id = c.req.param("id");
+ const { id: uid } = c.get("user");
+ const body = await c.req.json();
const { name, autowithdraw, threshold, reserve, destination, currency, fingerprint, pubkey } =
- req.body;
+ body;
const pos = await db.lPos(`${uid}:accounts`, id);
if (pos == null) fail("account not found");
@@ -1017,19 +1006,19 @@ export default {
.catch((e) => console.error("importAccountHistory failed:", e.message));
}
- res.send(account);
+ return c.json(account);
},
- async deleteAccount(req, res) {
+ async deleteAccount(c) {
try {
- const { id } = req.body;
- const { id: uid } = req.user;
+ const body = await c.req.json();
+ const { id } = body;
+ const { id: uid } = c.get("user");
const account = await g(`account:${id}`);
const pos = await db.lPos(`${uid}:accounts`, id);
if (!(account && pos != null)) fail("account not found");
- // Only unload Bitcoin Core wallet for non-ARK accounts
if (account.type !== "ark") {
try {
const node = rpc({ ...config[account.type], wallet: id });
@@ -1046,25 +1035,27 @@ export default {
.del(`${id}:payments`)
.exec();
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async flash(req, res) {
- const { ssid, key, token } = req.body;
+ async flash(c) {
+ const body = await c.req.json();
+ const { ssid, key, token } = body;
const cfg = `${ssid.trim()}\n${key.trim()}\n${token.trim()}\n`;
await writeFile("./printer/config.txt", cfg, "utf8");
await $`./mklittlefs -c ./printer -p 256 -b 4096 -s 0x20000 ./littlefs.img`;
- res.header("Content-Type", "application/octet-stream");
- return res.send(createReadStream("./littlefs.img"));
+ return new Response(Bun.file("./littlefs.img"), {
+ headers: { "Content-Type": "application/octet-stream" },
+ });
},
- async app(req, res) {
- const { pubkey } = req.params;
- const { user } = req;
+ async app(c) {
+ const pubkey = c.req.param("pubkey");
+ const user = c.get("user");
const app = await g(`app:${pubkey}`);
if (app.uid !== user.id) fail("unauthorized");
@@ -1083,11 +1074,11 @@ export default {
app.nwc = `nostr+walletconnect://${serverPubkey2}?relay=${relay}&secret=${app.secret}&lud16=${lud16}`;
app.payments = payments.filter((p) => p);
- res.send(app);
+ return c.json(app);
},
- async apps(req, res) {
- const { user } = req;
+ async apps(c) {
+ const user = c.get("user");
const pubkeys = await db.sMembers(`${user.id}:apps`);
const apps = await Promise.all(pubkeys.map((p) => g(`app:${p}`)));
@@ -1112,14 +1103,15 @@ export default {
}),
);
- res.send(apps);
+ return c.json(apps);
},
- async updateApp(req, res) {
+ async updateApp(c) {
try {
- let { secret, pubkey, max_amount, max_fee, budget_renewal, name, notify } = req.body;
+ const body = await c.req.json();
+ let { secret, pubkey, max_amount, max_fee, budget_renewal, name, notify } = body;
- const { user } = req;
+ const user = c.get("user");
const uid = user.id;
let app = await g(pubkey);
@@ -1144,18 +1136,19 @@ export default {
await s(`app:${pubkey}`, app);
await db.sAdd(`${uid}:apps`, pubkey);
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async deleteApp(req, res) {
+ async deleteApp(c) {
try {
- const { user } = req;
+ const user = c.get("user");
const uid = user.id;
- const { pubkey } = req.body;
+ const body = await c.req.json();
+ const { pubkey } = body;
const app = await g(`app:${pubkey}`);
if (app && uid !== app.uid) {
warn(app.uid, uid);
@@ -1163,110 +1156,118 @@ export default {
}
await db.sRem(`${uid}:apps`, pubkey);
await db.del(`app:${pubkey}`);
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async addPin(req, res) {
- const { id: uid } = req.user;
- const { id } = req.body;
+ async addPin(c) {
+ const { id: uid } = c.get("user");
+ const body = await c.req.json();
+ const { id } = body;
await db.sAdd(`${uid}:pins`, id);
- res.send({});
+ return c.json({});
},
- async deletePin(req, res) {
- const { id: uid } = req.user;
- const { id } = req.body;
+ async deletePin(c) {
+ const { id: uid } = c.get("user");
+ const body = await c.req.json();
+ const { id } = body;
await db.sRem(`${uid}:pins`, id);
- res.send({});
+ return c.json({});
},
- async trust(req, res) {
- const { id } = req.user;
- res.send(await db.sMembers(`${id}:trust`));
+ async trust(c) {
+ const { id } = c.get("user");
+ return c.json(await db.sMembers(`${id}:trust`));
},
- async addTrust(req, res) {
- const { id: uid } = req.user;
- const { id } = req.body;
+ async addTrust(c) {
+ const { id: uid } = c.get("user");
+ const body = await c.req.json();
+ const { id } = body;
await db.sAdd(`${uid}:trust`, id);
- res.send({});
+ return c.json({});
},
- async deleteTrust(req, res) {
- const { id: uid } = req.user;
- const { id } = req.body;
+ async deleteTrust(c) {
+ const { id: uid } = c.get("user");
+ const body = await c.req.json();
+ const { id } = body;
await db.sRem(`${uid}:trust`, id);
- res.send({});
+ return c.json({});
},
- async passkeyRegisterOptions(req, res) {
+ async passkeyRegisterOptions(c) {
try {
- const { user } = req;
- const origin = req.body.origin || `https://${config.hostname}`;
+ const user = c.get("user");
+ const body = await c.req.json();
+ const origin = body.origin || `https://${config.hostname}`;
const options = await generatePasskeyRegistration(user, origin);
- res.send(options);
+ return c.json(options);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async passkeyRegisterVerify(req, res) {
+ async passkeyRegisterVerify(c) {
try {
- const { user } = req;
- const origin = req.body.origin || `https://${config.hostname}`;
- const credential = await verifyPasskeyRegistration(user, req.body, origin);
+ const user = c.get("user");
+ const body = await c.req.json();
+ const origin = body.origin || `https://${config.hostname}`;
+ const credential = await verifyPasskeyRegistration(user, body, origin);
if (!user.passkeys) user.passkeys = [];
user.passkeys.push(credential);
await s(`user:${user.id}`, user);
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async passkeyLoginOptions(req, res) {
+ async passkeyLoginOptions(c) {
try {
- const origin = req.body.origin || `https://${config.hostname}`;
+ const body = await c.req.json();
+ const origin = body.origin || `https://${config.hostname}`;
const options = await generatePasskeyLogin(origin);
- res.send(options);
+ return c.json(options);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async passkeyLoginVerify(req, res) {
+ async passkeyLoginVerify(c) {
try {
- const { credential, challengeId, origin: reqOrigin } = req.body;
+ const body = await c.req.json();
+ const { credential, challengeId, origin: reqOrigin } = body;
const origin = reqOrigin || `https://${config.hostname}`;
const user = await verifyPasskeyLogin(credential, challengeId, origin);
const payload = { id: user.id };
const token = jwt.sign(payload, config.jwt);
- res.send({ user: pick(user, whitelist), token });
+ return c.json({ user: pick(user, whitelist), token });
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async credits(req, res) {
- const { id } = req.user;
+ async credits(c) {
+ const { id } = c.get("user");
const bitcoin = await getCredit(id, "bitcoin");
const lightning = await getCredit(id, "lightning");
const liquid = await getCredit(id, "liquid");
- res.send({ bitcoin, lightning, liquid });
+ return c.json({ bitcoin, lightning, liquid });
},
- async ro(req, res) {
- const { user } = req;
+ async ro(c) {
+ const user = c.get("user");
const payload = { id: `${user.id}-ro` };
const token = jwt.sign(payload, config.jwt);
- res.send(token);
+ return c.json(token);
},
};