const jwt = require("jsonwebtoken"); const uuidv4 = require("uuid/v4"); const WebSocket = require("ws"); wss = new WebSocket.Server({ clientTracking: false, noServer: true }); /* eslint-disable-next-line */ const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); emit = (username, type, data) => { if (data.username !== undefined) data = pick(data, ...whitelist); if (!sockets[username]) return; for (id in sockets[username]) { const ws = sockets[username][id]; ws.send(JSON.stringify({ type, data })); } }; broadcast = (type, data) => { for (const username in sockets) { for (const id in sockets[username]) { const ws = sockets[username][id]; ws.send(JSON.stringify({ type, data })); } } }; server.on("upgrade", function (req, socket, head) { wss.handleUpgrade(req, socket, head, function (ws) { wss.emit("connection", ws, req); }); }); const names = {}; wss.on("connection", function (ws, req) { const id = uuidv4(); ws.send(JSON.stringify({ type: "connected" })); ws.on("message", async (message) => { let type, data; try { ({ type, data } = JSON.parse(message)); } catch (e) { l.error("coudn't parse socket message"); } let token; switch (type) { case "login": token = data; if (!token || token === "null") return; try { const username = jwt.decode(token).username; if (!sockets[username]) sockets[username] = {}; const existing = Object.keys(sockets[username]); if (existing.length > 5) { l.warn("too many sockets, logging out", existing[0]); sockets[username][existing[0]].send( JSON.stringify({ type: "login", data: false }) ); } sockets[username][id] = ws; names[id] = username; let user = await getUser(username); let payments = await user.getPayments({ where: { account_id: user.account_id, }, order: [["id", "DESC"]], limit: 12, include: { model: db.Account, as: "account", }, }); let accounts = await user.getAccounts(); let keys = await user.getKeys(); user = user.get({ plain: true }); user.accounts = accounts; user.keys = keys; user.payments = payments; ws.user = user; ws.send(JSON.stringify({ type: "login", data: user })); ws.send( JSON.stringify({ type: "version", data: config.clientVersion }) ); l.info("socket open", username, id); } catch (e) { ws.close(); l.error( "problem opening socket", token, e.message, id, req.connection.remoteAddress ); } break; case "token": token = data; if (!ws.user) return; ws.token = token; break; case "lnurl": sessions[data] = ws; break; case "updateMemo": const payment = await db.Payment.findOne({ where: { user_id: ws.user.id, id: data.id, }, include: { model: db.Account, as: "account", }, }); if (payment) { payment.memo = data.memo; await payment.save(); emit(ws.user.username, "payment", payment); } break; default: l.warn("received socket message of unknown type", type, data); } }); ws.on("close", function () { try { const username = names[id]; if (sockets[username] && sockets[username][id]) delete sockets[username][id]; l.info("socket closed", username, id); } catch (e) { l.error("problem closing socket", e.message); } }); }); >diffstats
path: root/src/lib/socket.js
blob: 4898f481061ca94229a5e71e904d2be9582ea4b4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
import { get } from 'svelte/store';
import { rate, user, token, invoicePaid, preferredCurrency } from '$lib/store';

let interval, socket;

export const auth = () => {
	let t = get(token);
	if (t) send('login', t);
};

export const send = (type, data) => {
	socket?.readyState === 1 && socket.send(JSON.stringify({ type, data }));
};

export const messages = (data) => ({
	rate() {
		rate.set(data);
	},

	payment() {
		invoicePaid.set(true);
	},

	connected: auth,

	login() {
		user.set(data);
		preferredCurrency.set(data.currency);
	}
});

const initialReconnectDelay = 1000;
const maxReconnectDelay = 16000;

let currentReconnectDelay = initialReconnectDelay;

export function connect() {
	clearInterval(interval);

	if (socket) return auth();

	socket = new WebSocket('ws://localhost:3119/ws');
	socket.addEventListener('open', onWebsocketOpen);
	socket.addEventListener('close', onWebsocketClose);
	socket.addEventListener('message', onWebsocketMessage);

	interval = setInterval(() => send('heartbeat'), 5000);
}

export function close() {
	if (socket) socket.close();
}

function onWebsocketMessage(msg) {
	let { type, data } = JSON.parse(msg.data);
	messages(data)[type] && messages(data)[type]();
}

function onWebsocketOpen() {
	currentReconnectDelay = initialReconnectDelay;
}

function onWebsocketClose() {
	socket = null;
	setTimeout(() => {
		reconnectToWebsocket();
	}, currentReconnectDelay + Math.floor(Math.random() * 3000));
}

function reconnectToWebsocket() {
	if (currentReconnectDelay < maxReconnectDelay) {
		currentReconnectDelay *= 2;
	}
	connect();
}