masto
Version:
Mastodon API client for JavaScript, TypeScript, Node.js, browsers
82 lines (81 loc) • 2.63 kB
JavaScript
import WebSocket from "isomorphic-ws";
import { createPromiseWithResolvers, ExponentialBackoff, } from "../../utils/index.js";
import { MastoWebSocketError } from "../errors/index.js";
import { waitForClose, waitForOpen } from "./wait-for-events.js";
export class WebSocketConnectorImpl {
props;
logger;
ws;
killed = false;
queue = [];
backoff;
constructor(props, logger) {
this.props = props;
this.logger = logger;
this.backoff = new ExponentialBackoff({
maxAttempts: this.props.maxAttempts,
});
this.spawn();
}
acquire() {
if (this.killed) {
throw new MastoWebSocketError("WebSocket closed");
}
if (this.ws) {
return Promise.resolve(this.ws);
}
const promiseWithResolvers = createPromiseWithResolvers();
this.queue.push(promiseWithResolvers);
return promiseWithResolvers.promise;
}
async *[Symbol.asyncIterator]() {
while (!this.killed) {
yield await this.acquire();
}
}
kill() {
this.killed = true;
this.ws?.close();
this.backoff.clear();
for (const { reject } of this.queue) {
reject(new MastoWebSocketError("WebSocket closed"));
}
this.queue = [];
}
async spawn() {
while (!this.killed) {
try {
await this.backoff.sleep();
}
catch {
break;
}
try {
this.logger?.log("info", "Connecting to WebSocket...");
{
const ctor = (this.props.implementation ??
WebSocket);
const ws = new ctor(...this.props.constructorParameters);
await waitForOpen(ws);
this.ws = ws;
}
this.logger?.log("info", "Connected to WebSocket");
for (const { resolve } of this.queue) {
resolve(this.ws);
}
this.queue = [];
await waitForClose(this.ws);
this.logger?.log("info", "WebSocket closed");
this.backoff.clear();
}
catch (error) {
this.logger?.log("error", "WebSocket error:", error);
}
this.ws = undefined;
}
for (const { reject } of this.queue) {
reject(new MastoWebSocketError(`Failed to connect to WebSocket after ${this.props.maxAttempts} attempts`));
}
this.queue = [];
}
}