UNPKG

open-collaboration-protocol

Version:

Open Collaboration Protocol implementation, part of the Open Collaboration Tools project

99 lines (86 loc) 3.42 kB
// ****************************************************************************** // Copyright 2024 TypeFox GmbH // This program and the accompanying materials are made available under the // terms of the MIT License, which is available in the project root. // ****************************************************************************** import { Emitter, Event } from '../utils/event.js'; import { Deferred } from '../utils/promise.js'; import { MessageTransport, MessageTransportProvider } from './transport.js'; import { io, Socket } from 'socket.io-client'; export const SocketIoTransportProvider: MessageTransportProvider = { id: 'socket.io', createTransport: (url, headers) => { const parsedUrl = new URL(url); let path = parsedUrl.pathname; if (path && !path.endsWith('/')) { path += '/'; } // Path always ends in .../socket.io path += 'socket.io'; const socket = io(url, { path, extraHeaders: headers }); const transport = new SocketIoTransport(socket); return transport; } }; export class SocketIoTransport implements MessageTransport { readonly id = 'socket.io'; private onReconnectEmitter = new Emitter<void>(); private onDisconnectEmitter = new Emitter<void>(); private onErrorEmitter = new Emitter<string>(); private disconnectTimeout?: NodeJS.Timeout; private ready = new Deferred(); get onDisconnect(): Event<void> { return this.onDisconnectEmitter.event; } get onReconnect(): Event<void> { return this.onReconnectEmitter.event; } get onError(): Event<string> { return this.onErrorEmitter.event; } constructor(protected socket: Socket) { this.socket.on('disconnect', (_reason, _description) => { this.ready.reject(); this.ready = new Deferred(); // Give it 30 seconds to reconnect before firing the disconnect event this.disconnectTimeout = setTimeout(() => { this.onDisconnectEmitter.fire(); this.disconnectTimeout = undefined; }, 30_000); }); this.socket.io.on('reconnect', () => { if (this.disconnectTimeout) { clearTimeout(this.disconnectTimeout); this.disconnectTimeout = undefined; this.ready.resolve(); } this.onReconnectEmitter.fire(); }); const timeout = setTimeout(() => { this.onErrorEmitter.fire('Websocket connection timed out.'); this.ready.reject(); }, 30_000); this.socket.on('error', () => { this.onErrorEmitter.fire('Websocket connection closed unexpectedly.'); this.ready.reject(); clearTimeout(timeout); }); this.socket.on('connect', () => { this.ready.resolve(); clearTimeout(timeout); }); } async write(data: Uint8Array): Promise<void> { await this.ready.promise.then(() => this.socket.send(data)); } read(cb: (data: Uint8Array) => void): void { this.socket.on('message', data => cb(data)); } dispose(): void { this.onDisconnectEmitter.dispose(); this.socket.close(); } }