1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2024-12-23 18:23:27 +01:00
ntfy/web/src/app/Connection.js

119 lines
4.1 KiB
JavaScript
Raw Normal View History

/* eslint-disable max-classes-per-file */
2023-05-24 01:29:47 +02:00
import { basicAuth, bearerAuth, encodeBase64Url, topicShortUrl, topicUrlWs } from "./utils";
const retryBackoffSeconds = [5, 10, 20, 30, 60, 120];
export class ConnectionState {
static Connected = "connected";
static Connecting = "connecting";
}
2022-03-11 21:17:12 +01:00
/**
* A connection contains a single WebSocket connection for one topic. It handles its connection
* status itself, including reconnect attempts and backoff.
*
* Incoming messages and state changes are forwarded via listeners.
*/
class Connection {
2023-05-24 01:29:47 +02:00
constructor(connectionId, subscriptionId, baseUrl, topic, user, since, onNotification, onStateChanged) {
2023-05-23 21:13:01 +02:00
this.connectionId = connectionId;
this.subscriptionId = subscriptionId;
this.baseUrl = baseUrl;
this.topic = topic;
this.user = user;
this.since = since;
this.shortUrl = topicShortUrl(baseUrl, topic);
this.onNotification = onNotification;
this.onStateChanged = onStateChanged;
this.ws = null;
this.retryCount = 0;
this.retryTimeout = null;
}
2023-05-23 21:13:01 +02:00
start() {
// Don't fetch old messages; we do that as a poll() when adding a subscription;
// we don't want to re-trigger the main view re-render potentially hundreds of times.
2023-05-23 21:13:01 +02:00
const wsUrl = this.wsUrl();
2023-05-24 01:29:47 +02:00
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Opening connection to ${wsUrl}`);
2023-05-23 21:13:01 +02:00
this.ws = new WebSocket(wsUrl);
this.ws.onopen = (event) => {
2023-05-24 01:29:47 +02:00
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection established`, event);
2023-05-23 21:13:01 +02:00
this.retryCount = 0;
this.onStateChanged(this.subscriptionId, ConnectionState.Connected);
};
this.ws.onmessage = (event) => {
2023-05-24 01:29:47 +02:00
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Message received from server: ${event.data}`);
2023-05-23 21:13:01 +02:00
try {
const data = JSON.parse(event.data);
if (data.event === "open") {
return;
}
2023-05-24 01:29:47 +02:00
const relevantAndValid = data.event === "message" && "id" in data && "time" in data && "message" in data;
2023-05-23 21:13:01 +02:00
if (!relevantAndValid) {
2023-05-24 01:29:47 +02:00
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Unexpected message. Ignoring.`);
2023-05-23 21:13:01 +02:00
return;
}
2023-05-23 21:13:01 +02:00
this.since = data.id;
this.onNotification(this.subscriptionId, data);
} catch (e) {
2023-05-24 01:29:47 +02:00
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Error handling message: ${e}`);
2023-05-23 21:13:01 +02:00
}
};
this.ws.onclose = (event) => {
if (event.wasClean) {
2023-05-24 02:16:29 +02:00
console.log(
`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`
);
this.ws = null;
2023-05-23 21:13:01 +02:00
} else {
2023-05-24 01:29:47 +02:00
const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length - 1)];
this.retryCount += 1;
2023-05-24 01:29:47 +02:00
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection died, retrying in ${retrySeconds} seconds`);
2023-05-23 21:13:01 +02:00
this.retryTimeout = setTimeout(() => this.start(), retrySeconds * 1000);
this.onStateChanged(this.subscriptionId, ConnectionState.Connecting);
}
};
this.ws.onerror = (event) => {
2023-05-24 01:29:47 +02:00
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Error occurred: ${event}`, event);
2023-05-23 21:13:01 +02:00
};
}
close() {
2023-05-24 01:29:47 +02:00
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Closing connection`);
2023-05-23 21:13:01 +02:00
const socket = this.ws;
2023-05-24 09:03:28 +02:00
const { retryTimeout } = this;
2023-05-23 21:13:01 +02:00
if (socket !== null) {
socket.close();
}
2023-05-23 21:13:01 +02:00
if (retryTimeout !== null) {
clearTimeout(retryTimeout);
}
this.retryTimeout = null;
this.ws = null;
}
2023-05-23 21:13:01 +02:00
wsUrl() {
const params = [];
if (this.since) {
params.push(`since=${this.since}`);
}
2023-05-23 21:13:01 +02:00
if (this.user) {
params.push(`auth=${this.authParam()}`);
}
const wsUrl = topicUrlWs(this.baseUrl, this.topic);
return params.length === 0 ? wsUrl : `${wsUrl}?${params.join("&")}`;
}
2023-05-23 21:13:01 +02:00
authParam() {
if (this.user.password) {
return encodeBase64Url(basicAuth(this.user.username, this.user.password));
}
2023-05-23 21:13:01 +02:00
return encodeBase64Url(bearerAuth(this.user.token));
}
}
export default Connection;