aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/socket.ts
blob: bd8494fa8bf01ef6c6035831fe0f35a3339c19cf (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
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
import { goto, invalidate } from "$app/navigation";
import { navigating, page } from "$app/stores";
import { PUBLIC_SOCKET } from "$env/static/public";
import { event, invoice, last, request } from "$lib/store";
import { s, sleep, success, wait } from "$lib/utils";
import cookies from "js-cookie";
import { get } from "svelte/store";

export let socket;
let token;

export const auth = () => token && send("login", token);

export const send = async (type, data) => {
	try {
		await wait(() => socket && socket.readyState === 1, 1000, 10);
	} catch (e) {
		const { message } = e as Error;
		if (message === "timeout") reconnectToWebsocket();
	}

	await wait(() => socket.readyState === 1, 1000, 10);
	socket.send(JSON.stringify({ type, data }));
};

export const messages = (data) => ({
	id() {
		last.set(Date.now());
	},
	invoice() {
		invoice.set(data);
	},
	event() {
		event.set(data);
	},

	request() {
		request.set(data);
		invalidate("app:invoice");
	},

	async payment() {
		const { amount, confirmed, iid } = data;
		invalidate("app:user");
		invalidate("app:invoice");
		invalidate("app:payments");

		const {
			url: { pathname },
		} = get(page);

		const username = cookies.get("username");

		if (amount > 0) {
			if (
				pathname.includes(`/${username}`) ||
				pathname.includes("/receive") ||
				pathname.includes("/invoice")
			) {
				await wait(() => !get(navigating));
				await goto(`/invoice/${iid}`);
			} else success(`${confirmed ? "Received" : "Detected"} ⚡️${s(amount)}!`);
		}
	},

	connected: auth,
});

const initialReconnectDelay = 1000;
const maxReconnectDelay = 16000;

let currentReconnectDelay = initialReconnectDelay;

let connecting;
export function connect(t) {
	if (connecting) return;
	connecting = true;
	setTimeout(() => {
		connecting = false;
	}, 5000);

	token = t;

	if (socket) return auth();

	socket = new WebSocket(PUBLIC_SOCKET);
	socket.addEventListener("open", onWebsocketOpen);
	socket.addEventListener("close", onWebsocketClose);
	socket.addEventListener("message", onWebsocketMessage);
}

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

async function onWebsocketMessage(msg) {
	const { type, data } = JSON.parse(msg.data);
	if (get(navigating)) await sleep(2000);

	if (messages(data)[type]) messages(data)[type]();
}

function onWebsocketOpen() {
	currentReconnectDelay = initialReconnectDelay;
	send("heartbeat", token);
}

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

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