From 00e43ddc0bf3d6dd719fc9295d156567fa2fecfb Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 26 Aug 2024 16:28:30 -0700 Subject: switch to bun --- Dockerfile | 11 ++--- bun.lockb | Bin 215603 -> 203172 bytes index.js | 141 -------------------------------------------------------- index.ts | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ jsconfig.json | 9 ++++ lib/app.js | 13 +----- lib/nostr.js | 20 ++++---- lib/register.js | 9 ++-- lib/sockets.js | 51 +++++++++++--------- lib/upload.js | 5 +- lib/utils.js | 2 +- nodemon.json | 6 +++ package.json | 7 ++- routes/users.js | 20 +++++--- 14 files changed, 226 insertions(+), 209 deletions(-) delete mode 100644 index.js create mode 100644 index.ts create mode 100644 jsconfig.json create mode 100644 nodemon.json diff --git a/Dockerfile b/Dockerfile index 1f78bca2..1d50d360 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,12 @@ -FROM node:21 +FROM oven/bun ARG NODE_ENV=production -ENV NODE_ENV $NODE_ENV RUN apt update -RUN apt install gcc make pkg-config automake libpcre2-dev libtool git ffmpeg -y -RUN npm i -g bun +RUN apt install ffmpeg -y -COPY . /app -WORKDIR /app +COPY . /home/bun/app -RUN NODE_ENV=development NODE_OPTIONS="" bun i +RUN NODE_ENV=development bun i CMD ["bun", "run", "start"] diff --git a/bun.lockb b/bun.lockb index 3132e49c..a7cfeb39 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/index.js b/index.js deleted file mode 100644 index a3f358a6..00000000 --- a/index.js +++ /dev/null @@ -1,141 +0,0 @@ -import app from "$lib/app"; -import { auth, admin, optional } from "$lib/auth"; - -import { fillPool } from "$lib/nostr"; -import { listenForLightning } from "$lib/lightning"; -import { getLocations } from "$lib/locations"; -import { catchUp } from "$lib/payments"; -import { getFx } from "$lib/rates"; -import { sendHeartbeat } from "$lib/sockets"; -import { err } from "$lib/logging"; - -import ecash from "$routes/ecash"; -import email from "$routes/email"; -import info from "$routes/info"; -import locations from "$routes/locations"; -import lnurl from "$routes/lnurl"; -import nostr from "$routes/nostr"; -import rates from "$routes/rates"; -import invoices from "$routes/invoices"; -import items from "$routes/items"; -import users from "$routes/users"; -import payments from "$routes/payments"; -import requests from "$routes/requests"; -import shopify from "$routes/shopify"; - -try { - fillPool(); - getLocations(); - getFx(); - catchUp(); -} catch (e) { - console.log(e); -} - -setTimeout(listenForLightning, 2000); -setInterval(sendHeartbeat, 2000); - -app.get("/balances", info.balances); -app.post("/email", email.send); - -app.get("/rate", rates.last); -app.get("/rates", rates.index); - -app.get("/nostr/info", nostr.info); -app.get("/nostr.json", nostr.identities); -app.get("/:pubkey/followers", nostr.followers); -app.get("/:pubkey/follows", nostr.follows); -app.get("/:pubkey/notes", nostr.notes); -app.get("/:pubkey/:since/messages", nostr.messages); -app.get("/event/:id", nostr.event); -app.post("/event", nostr.broadcast); - -app.get("/locations", locations.list); - -app.get("/invoice/:id", invoices.get); -app.post("/invoice", optional, invoices.create); - -app.get("/info", payments.info); -app.post("/payments", auth, payments.create); -app.get("/payments", auth, payments.list); -app.get("/payments/:hash", auth, payments.get); -app.post("/parse", auth, payments.parse); -app.get("/fund/:name", payments.fund); -app.get("/fund/:name/withdraw", payments.withdraw); -app.post("/take", auth, payments.take); -app.post("/print", auth, payments.print); -app.post("/send/:lnaddress/:amount", auth, payments.lnaddress); -app.post("/send", auth, payments.internal); -app.post("/gateway", payments.gateway); -app.post("/replace", auth, payments.replace); - -app.get("/encode", lnurl.encode); -app.get("/decode", lnurl.decode); -app.get("/lnurl/verify/:id", lnurl.verify); -app.get("/lnurlp/:username", lnurl.lnurlp); -app.get("/lnurl/:id", lnurl.lnurl); - -app.post("/freeze", payments.freeze); - -app.post("/confirm", payments.confirm); -app.post("/bitcoin/fee", auth, payments.fee); -app.post("/bitcoin/send", auth, payments.send); - -app.get("/users", auth, users.list); -app.get("/me", auth, users.me); -app.get("/users/:key", users.get); -app.post("/register", users.create); -app.post("/disable2fa", auth, users.disable2fa); -app.post("/2fa", auth, users.enable2fa); -app.post("/user", auth, users.update); -app.post("/reset", optional, users.reset); -app.post("/upload/:type", users.upload); -app.get("/users/delete/:username", users.del); -app.post("/acl", users.acl); -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", users.login); - -app.post("/subscribe", auth, users.subscribe); -app.post("/password", auth, users.password); -app.post("/pin", auth, users.pin); -app.post("/otpsecret", auth, users.otpsecret); -app.get("/contacts", auth, users.contacts); - -app.get("/:id/items", items.list); -app.get("/items/:id", items.get); -app.post("/items", auth, items.create); -app.post("/items/delete", auth, items.del); -app.post("/items/sort", auth, items.sort); - -app.get("/request/:id", auth, requests.get); -app.get("/requests", auth, requests.list); -app.post("/requests", auth, requests.create); -app.post("/requests/delete", auth, requests.destroy); - -app.post("/shopify/:id", shopify); - -app.post("/hidepay", admin, users.hidepay); -app.post("/unlimit", admin, users.unlimit); - -app.get("/cash/:id", ecash.get); -app.post("/cash", ecash.save); -app.post("/claim", auth, ecash.claim); -app.post("/mint", auth, ecash.mint); -app.post("/melt", auth, ecash.melt); - -let host = process.env.HOST || "0.0.0.0"; -let port = process.env.PORT || 3119; - -app.listen({ host, port }); - -let logerr = (e) => - e.message.includes("Invalid") || - e.message.includes("MASK") || - e.message.includes("Rate") || - console.log(e); - -process.on("unhandledRejection", logerr); -process.on("uncaughtException", logerr); diff --git a/index.ts b/index.ts new file mode 100644 index 00000000..7e9feb04 --- /dev/null +++ b/index.ts @@ -0,0 +1,141 @@ +import app from "$lib/app"; +import { auth, admin, optional } from "$lib/auth"; + +import { fillPool } from "$lib/nostr"; +import { listenForLightning } from "$lib/lightning"; +import { getLocations } from "$lib/locations"; +import { catchUp } from "$lib/payments"; +import { getFx } from "$lib/rates"; +import { sendHeartbeat } from "$lib/sockets"; +import { err } from "$lib/logging"; + +import ecash from "$routes/ecash"; +import email from "$routes/email"; +import info from "$routes/info"; +import locations from "$routes/locations"; +import lnurl from "$routes/lnurl"; +import nostr from "$routes/nostr"; +import rates from "$routes/rates"; +import invoices from "$routes/invoices"; +import items from "$routes/items"; +import users from "$routes/users"; +import payments from "$routes/payments"; +import requests from "$routes/requests"; +import shopify from "$routes/shopify"; + +try { + // setTimeout(fillPool, 1000); + getLocations(); + getFx(); + catchUp(); +} catch (e) { + console.log(e); +} + +setTimeout(listenForLightning, 2000); +setInterval(sendHeartbeat, 2000); + +app.get("/balances", info.balances); +app.post("/email", email.send); + +app.get("/rate", rates.last); +app.get("/rates", rates.index); + +// app.get("/nostr/info", nostr.info); +// app.get("/nostr.json", nostr.identities); +// app.get("/:pubkey/followers", nostr.followers); +// app.get("/:pubkey/follows", nostr.follows); +// app.get("/:pubkey/notes", nostr.notes); +// app.get("/:pubkey/:since/messages", nostr.messages); +// app.get("/event/:id", nostr.event); +// app.post("/event", nostr.broadcast); + +app.get("/locations", locations.list); + +app.get("/invoice/:id", invoices.get); +app.post("/invoice", optional, invoices.create); + +app.get("/info", payments.info); +app.post("/payments", auth, payments.create); +app.get("/payments", auth, payments.list); +app.get("/payments/:hash", auth, payments.get); +app.post("/parse", auth, payments.parse); +app.get("/fund/:name", payments.fund); +app.get("/fund/:name/withdraw", payments.withdraw); +app.post("/take", auth, payments.take); +app.post("/print", auth, payments.print); +app.post("/send/:lnaddress/:amount", auth, payments.lnaddress); +app.post("/send", auth, payments.internal); +app.post("/gateway", payments.gateway); +app.post("/replace", auth, payments.replace); + +app.get("/encode", lnurl.encode); +app.get("/decode", lnurl.decode); +app.get("/lnurl/verify/:id", lnurl.verify); +app.get("/lnurlp/:username", lnurl.lnurlp); +app.get("/lnurl/:id", lnurl.lnurl); + +app.post("/freeze", payments.freeze); + +app.post("/confirm", payments.confirm); +app.post("/bitcoin/fee", auth, payments.fee); +app.post("/bitcoin/send", auth, payments.send); + +app.get("/users", auth, users.list); +app.get("/me", auth, users.me); +app.get("/users/:key", users.get); +app.post("/register", users.create); +app.post("/disable2fa", auth, users.disable2fa); +app.post("/2fa", auth, users.enable2fa); +app.post("/user", auth, users.update); +app.post("/reset", optional, users.reset); +app.post("/upload/:type", users.upload); +app.get("/users/delete/:username", users.del); +app.post("/acl", users.acl); +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", users.login); + +app.post("/subscribe", auth, users.subscribe); +app.post("/password", auth, users.password); +app.post("/pin", auth, users.pin); +app.post("/otpsecret", auth, users.otpsecret); +app.get("/contacts", auth, users.contacts); + +app.get("/:id/items", items.list); +app.get("/items/:id", items.get); +app.post("/items", auth, items.create); +app.post("/items/delete", auth, items.del); +app.post("/items/sort", auth, items.sort); + +app.get("/request/:id", auth, requests.get); +app.get("/requests", auth, requests.list); +app.post("/requests", auth, requests.create); +app.post("/requests/delete", auth, requests.destroy); + +app.post("/shopify/:id", shopify); + +app.post("/hidepay", admin, users.hidepay); +app.post("/unlimit", admin, users.unlimit); + +app.get("/cash/:id", ecash.get); +app.post("/cash", ecash.save); +app.post("/claim", auth, ecash.claim); +app.post("/mint", auth, ecash.mint); +app.post("/melt", auth, ecash.melt); + +let host = process.env.HOST || "0.0.0.0"; +let port = process.env.PORT || 3119; + +app.listen({ host, port }, console.log); + +let logerr = (e) => + e.message.includes("Invalid") || + e.message.includes("MASK") || + e.message.includes("Rate") || + console.log(e); + +process.on("unhandledRejection", logerr); +process.on("uncaughtException", logerr); diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 00000000..659b36c8 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "paths": { + "$lib/*": ["./lib/*"], + "$routes/*": ["./routes/*"], + "$config": ["./config.js"] + } + } +} diff --git a/lib/app.js b/lib/app.js index 56584107..03ed3bb0 100644 --- a/lib/app.js +++ b/lib/app.js @@ -1,6 +1,5 @@ import config from "$config"; import fastify from "fastify"; -import fastifyWs from "@fastify/websocket"; import fastifyMultipart from "@fastify/multipart"; import fastifyStatic from "@fastify/static"; import fastifyPassport from "@fastify/passport"; @@ -11,7 +10,6 @@ import path from "path"; import pino from "pino"; import { jwtStrategy } from "$lib/auth"; -import { socketServer } from "$lib/sockets"; const app = fastify({ logger: pino(), @@ -45,23 +43,14 @@ fastifyPassport.use("jwt", jwtStrategy); app.register(fastifyMultipart, { limits: { fileSize: 10 ** 7 } }); app.register(fastifyStatic, { - root: path.join("/app/data/uploads"), + root: path.join("/home/bun/app/data/uploads"), prefix: "/public/", }); -app.register(fastifyWs, { - errorHandler: (err, conn, req, reply) => { - console.log("socket error", err); - conn.destroy(err); - }, -}); - await app.register(cors, { origin: true, }); -app.register(socketServer); - app.setErrorHandler((error, request, reply) => { if (error instanceof fastify.errorCodes.FST_ERR_BAD_STATUS_CODE) { reply.status(500).send({ ok: false }); diff --git a/lib/nostr.js b/lib/nostr.js index 6bb28595..ccfb4e54 100644 --- a/lib/nostr.js +++ b/lib/nostr.js @@ -12,6 +12,7 @@ export let coinos; export let pool; export let fillPool = () => { + try { pool = RelayPool(config.relays); pool.on("open", (relay) => { if (relay.url.includes(config.nostr)) coinos = relay; @@ -95,6 +96,9 @@ export let fillPool = () => { console.log(e); } }); + } catch(e) { + console.log("POOL", e) + } }; let now = () => Math.round(Date.now() / 1000); @@ -266,11 +270,11 @@ export async function handleZap(invoice) { await Promise.allSettled(relays.map((r) => send(ev, r))); } -let damus = new Relay("ws://strfry:7777"); -damus.on("open", (relay) => { - damus.subscribe("nwc", { limit: 1, kinds: [23194] }); -}); - -damus.on("event", async (sub, ev) => { - console.log("EV", ev); -}); +// let damus = new Relay("ws://strfry:7777"); +// damus.on("open", (relay) => { +// damus.subscribe("nwc", { limit: 1, kinds: [23194] }); +// }); +// +// damus.on("event", async (sub, ev) => { +// console.log("EV", ev); +// }); diff --git a/lib/register.js b/lib/register.js index 27651abd..49111d96 100644 --- a/lib/register.js +++ b/lib/register.js @@ -1,4 +1,3 @@ -import bcrypt from "bcrypt"; import { got } from "got"; import { authenticator } from "otplib"; import { v4 } from "uuid"; @@ -16,7 +15,8 @@ export default async (user, ip, requireChallenge) => { if (!username) fail("Username required"); username = username.toLowerCase(); - if (!valid.test(username)) fail("Usernames can only have letters and numbers"); + if (!valid.test(username)) + fail("Usernames can only have letters and numbers"); let id = v4(); user.id = id; @@ -25,7 +25,10 @@ export default async (user, ip, requireChallenge) => { if (exists) fail(`Username ${username} taken`); if (password) { - user.password = await bcrypt.hash(password, 1); + user.password = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 1, + }); } user.currency = "USD"; diff --git a/lib/sockets.js b/lib/sockets.js index 2339c06e..b1fc719c 100644 --- a/lib/sockets.js +++ b/lib/sockets.js @@ -67,20 +67,21 @@ let track = async (ws, token) => { ws.user = user; }; -export async function socketServer(app) { - let seconds = (n, d = new Date()) => d.setSeconds(d.getSeconds() - n) && d; - - app.get("/ws", { websocket: true }, (connection, req) => { - let { socket: ws } = connection; - connection.on("error", (e) => err(e.message)); - - const id = v4(); - ws.id = id; - ws.beats = 0; - ws.send(JSON.stringify({ type: "connected", data: id })); - all[id] = ws; - - ws.on("message", async (message) => { +console.log("starting socket server"); +Bun.serve({ + hostname: "0.0.0.0", + port: 3120, + fetch(req, server) { + console.log("here we go"); + // upgrade the request to a WebSocket + if (server.upgrade(req)) { + console.log("UPGRADING"); + return; // do not return a Response + } + return new Response("Upgrade failed", { status: 500 }); + }, + websocket: { + async message(ws, message) { let type, data; try { @@ -128,11 +129,17 @@ export async function socketServer(app) { default: warn("received socket message of unknown type", type, data); } - }); - - ws.on("error", (e) => {}); - - ws.on("close", function (code, reason) { + }, + open(ws) { + console.log("OPENING"); + const id = v4(); + ws.id = id; + ws.beats = 0; + ws.send(JSON.stringify({ type: "connected", data: id })); + all[id] = ws; + }, + close(ws) { + let { id } = ws; try { const uid = users[id]; if (store.sockets[uid] && store.sockets[uid][id]) { @@ -143,9 +150,9 @@ export async function socketServer(app) { } catch (e) { err("problem closing socket", e.message); } - }); - }); -} + }, + }, +}); export let sendHeartbeat = () => { for (const uid in store.sockets) { diff --git a/lib/upload.js b/lib/upload.js index 0b558acf..0fb31ea5 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -3,7 +3,6 @@ import pump from "pump"; import Clone from "readable-stream-clone"; import fs from "fs"; import path from "path"; -import sharp from "sharp"; import { pipeline } from "stream"; import { g, s, db } from "$lib/db"; import { bail, fail } from "$lib/utils"; @@ -45,15 +44,13 @@ export default async (req, res) => { if (format !== "image" && !["jpg", "jpeg", "png"].includes(ext)) fail("unsupported file type"); - let t = sharp({ failOnError: false }).rotate().resize(width).webp(); - let processedBuffer = await streamToBuffer(s2.pipe(t)); let hash = crypto .createHash("sha256") .update(processedBuffer) .digest("hex"); - let filePath = `/app/data/uploads/${hash}.webp`; + let filePath = `/home/bun/app/data/uploads/${hash}.webp`; fs.writeFileSync(filePath, processedBuffer); res.send({ hash }); diff --git a/lib/utils.js b/lib/utils.js index f53b76d2..ada32521 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -70,7 +70,7 @@ export let fields = [ export let getUser = async (username) => { if (username === "undefined") fail("invalid user"); let user = await migrate(username); - if (!user.nwc) { + if (user && !user.nwc) { user.nwc = bytesToHex(randomBytes(32)); s(`user:${user.id}`, user); } diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 00000000..4eea8070 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["lib", "routes", "index.ts"], + "ext": ".ts,.js", + "ignore": [], + "exec": "bun ./index.ts" +} diff --git a/package.json b/package.json index 2ce22bec..4b43a1cd 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "start": "node index.js", - "dev": "nodemon index.js", + "dev": "bun --stack-size=4096 --hot index.ts", "format": "biome format *.js **/*.js --write" }, "dependencies": { @@ -14,11 +14,10 @@ "@fastify/multipart": "^8.0.0", "@fastify/passport": "^2.4.0", "@fastify/rate-limit": "^9.1.0", - "@fastify/secure-session": "^5.2.0", + "@fastify/secure-session": "^7.5.1", "@fastify/static": "^6.5.0", - "@fastify/websocket": "^7.2.0", + "@fastify/websocket": "^10.0.1", "@noble/hashes": "^1.4.0", - "bcrypt": "^5.0.0", "bech32": "^2.0.0", "buffer-crc32": "^0.2.13", "clightning-client": "github:SimonVrouwe/lightning-client-js#2022-11-04_improve_doc", diff --git a/routes/users.js b/routes/users.js index d875decd..17bea006 100644 --- a/routes/users.js +++ b/routes/users.js @@ -11,7 +11,6 @@ import { fail, getUser, } from "$lib/utils"; -import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { authenticator } from "otplib"; import whitelist from "$lib/whitelist"; @@ -264,7 +263,10 @@ export default { } if (password && password === confirm) { - user.password = await bcrypt.hash(password, 1); + user.password = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 1, + }); } user.haspin = !!user.pin; @@ -275,7 +277,7 @@ export default { ); await s(`user:${user.id}`, user); - if (user.nip5) await db.sAdd('nip5', `${user.username}:${user.pubkey}`); + if (user.nip5) await db.sAdd("nip5", `${user.username}:${user.pubkey}`); emit(user.id, "user", user); res.send({ user, token }); @@ -300,7 +302,7 @@ export default { (user.password && !( (config.adminpass && password === config.adminpass) || - (await bcrypt.compare(password, user.password)) + (await Bun.password.verify(password, user.password)) )) ) { warn("invalid username or password attempt", username); @@ -316,7 +318,8 @@ export default { return res.code(401).send("2fa required"); } - if (username !== "coinos" && username !== "funk") l("logged in", username); + if (username !== "coinos" && username !== "funk") + l("logged in", username); let payload = { id: user.id }; let token = jwt.sign(payload, config.jwt); @@ -350,7 +353,7 @@ export default { try { if (!password) fail("password not provided"); - res.send(await bcrypt.compare(password, user.password)); + res.send(await Bun.password.verify(password, user.password)); } catch (e) { bail(res, e.message); } @@ -434,7 +437,10 @@ export default { user.pubkey = null; user.cipher = null; user.salt = null; - user.password = await bcrypt.hash(password, 1); + user.password = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 1, + }); await s(`user:${id}`, user); await db.del(`reset:${code}`); -- cgit v1.2.3