open-collaboration-protocol
Version:
Open Collaboration Protocol implementation, part of the Open Collaboration Tools project
99 lines (86 loc) • 3.42 kB
text/typescript
// ******************************************************************************
// 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();
}
}