aboutsummaryrefslogtreecommitdiffstats
path: root/lib/db.js.map
Commit message (Collapse)AuthorAgeFilesLines
* typescriptAdam Soltys2024-08-261-0/+1
'n29' href='#n29'>29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
import store from "$lib/store";
import jwt from "jsonwebtoken";
import { v4 } from "uuid";
import { err, warn } from "$lib/logging";
import { fail, getUser } from "$lib/utils";

const code = 1000;
const all = {};
const subscriptions = [];
const users = {};

export const emit = (uid, type, data) => {
  if (type === "payment" && data.amount > 0) {
    for (let i = subscriptions.length - 1; i >= 0; i--) {
      let s = subscriptions[i];

      if (s.invoice && s.invoice.hash === data.hash) {
        s.ws.send(JSON.stringify({ type, data }));
      }
    }
  }

  if (!store.sockets[uid]) return;
  for (let id in store.sockets[uid]) {
    const ws = store.sockets[uid][id];
    ws.send(JSON.stringify({ type, data }));
  }
};

export const broadcast = (type, data) => {
  const sent = [];
  for (const uid in store.sockets) {
    for (const id in store.sockets[uid]) {
      const ws = store.sockets[uid][id];
      ws.send(JSON.stringify({ type, data }));
      sent.push(ws.id);
    }
  }

  Object.values(all).map((ws) => {
    if (!sent.includes(ws.id)) ws.send(JSON.stringify({ type, data }));
  });
};

let track = async (ws, token) => {
  let { id } = ws;
  let { id: uid } = jwt.decode(token);

  if (!uid) fail("Invalid JWT token");
  let user = await getUser(uid);
  if (!user) fail(`User not found ${uid}`);

  if (!store.sockets[uid]) store.sockets[uid] = {};

  const existing = Object.keys(store.sockets[uid]);
  if (existing.length > 4) {
    const p = existing.find((sid) => sid !== uid);
    store.sockets[uid][p].close(code, "too many sockets");
  }

  store.sockets[uid][id] = ws;
  users[id] = uid;
  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, message) {
      let type, data;

      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;

        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 && 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) {
      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]) {
          delete store.sockets[uid][id];
        }

        if (all[id]) delete all[id];
      } catch (e) {
        err("problem closing socket", e.message);
      }
    },
  },
});

export let sendHeartbeat = () => {
  for (const uid in store.sockets) {
    for (const id in store.sockets[uid]) {
      let ws = store.sockets[uid][id];
      ws.beats++;
      if (ws.beats > 10) ws.close(code, `${uid} ${id} lost heartbeat`);
    }
  }
};