Faster WebSocket reconnects
This commit is contained in:
parent
3cac4a19e1
commit
17e6ec468e
25 changed files with 940 additions and 677 deletions
|
@ -27,12 +27,12 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import { connection as WebSocket, IMessage } from 'websocket';
|
||||
|
||||
import { ByteBufferClass } from '../window.d';
|
||||
|
||||
import EventTarget from './EventTarget';
|
||||
|
||||
import { WebSocket } from './WebSocket';
|
||||
|
||||
class Request {
|
||||
verb: string;
|
||||
|
||||
|
@ -92,14 +92,13 @@ export class IncomingWebSocketRequest {
|
|||
this.headers = request.headers;
|
||||
|
||||
this.respond = (status, message) => {
|
||||
socket.send(
|
||||
new window.textsecure.protobuf.WebSocketMessage({
|
||||
type: window.textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
|
||||
response: { id: request.id, message, status },
|
||||
})
|
||||
.encode()
|
||||
.toArrayBuffer()
|
||||
);
|
||||
const ab = new window.textsecure.protobuf.WebSocketMessage({
|
||||
type: window.textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
|
||||
response: { id: request.id, message, status },
|
||||
})
|
||||
.encode()
|
||||
.toArrayBuffer();
|
||||
socket.sendBytes(Buffer.from(ab));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -111,20 +110,19 @@ class OutgoingWebSocketRequest {
|
|||
constructor(options: any, socket: WebSocket) {
|
||||
const request = new Request(options);
|
||||
outgoing[request.id] = request;
|
||||
socket.send(
|
||||
new window.textsecure.protobuf.WebSocketMessage({
|
||||
type: window.textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
||||
request: {
|
||||
verb: request.verb,
|
||||
path: request.path,
|
||||
body: request.body,
|
||||
headers: request.headers,
|
||||
id: request.id,
|
||||
},
|
||||
})
|
||||
.encode()
|
||||
.toArrayBuffer()
|
||||
);
|
||||
const ab = new window.textsecure.protobuf.WebSocketMessage({
|
||||
type: window.textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
||||
request: {
|
||||
verb: request.verb,
|
||||
path: request.path,
|
||||
body: request.body,
|
||||
headers: request.headers,
|
||||
id: request.id,
|
||||
},
|
||||
})
|
||||
.encode()
|
||||
.toArrayBuffer();
|
||||
socket.sendBytes(Buffer.from(ab));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,66 +147,58 @@ export default class WebSocketResource extends EventTarget {
|
|||
this.sendRequest = options => new OutgoingWebSocketRequest(options, socket);
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
socket.onmessage = socketMessage => {
|
||||
const blob = socketMessage.data;
|
||||
const handleArrayBuffer = (buffer: ArrayBuffer) => {
|
||||
const message = window.textsecure.protobuf.WebSocketMessage.decode(
|
||||
buffer
|
||||
const onMessage = ({ type, binaryData }: IMessage): void => {
|
||||
if (type !== 'binary' || !binaryData) {
|
||||
throw new Error(`Unsupported websocket message type: ${type}`);
|
||||
}
|
||||
|
||||
const message = window.textsecure.protobuf.WebSocketMessage.decode(
|
||||
binaryData
|
||||
);
|
||||
if (
|
||||
message.type ===
|
||||
window.textsecure.protobuf.WebSocketMessage.Type.REQUEST &&
|
||||
message.request
|
||||
) {
|
||||
handleRequest(
|
||||
new IncomingWebSocketRequest({
|
||||
verb: message.request.verb,
|
||||
path: message.request.path,
|
||||
body: message.request.body,
|
||||
headers: message.request.headers,
|
||||
id: message.request.id,
|
||||
socket,
|
||||
})
|
||||
);
|
||||
if (
|
||||
message.type ===
|
||||
window.textsecure.protobuf.WebSocketMessage.Type.REQUEST &&
|
||||
message.request
|
||||
) {
|
||||
handleRequest(
|
||||
new IncomingWebSocketRequest({
|
||||
verb: message.request.verb,
|
||||
path: message.request.path,
|
||||
body: message.request.body,
|
||||
headers: message.request.headers,
|
||||
id: message.request.id,
|
||||
socket,
|
||||
})
|
||||
);
|
||||
} else if (
|
||||
message.type ===
|
||||
window.textsecure.protobuf.WebSocketMessage.Type.RESPONSE &&
|
||||
message.response
|
||||
) {
|
||||
const { response } = message;
|
||||
const request = outgoing[response.id];
|
||||
if (request) {
|
||||
request.response = response;
|
||||
let callback = request.error;
|
||||
if (
|
||||
response.status &&
|
||||
response.status >= 200 &&
|
||||
response.status < 300
|
||||
) {
|
||||
callback = request.success;
|
||||
}
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback(response.message, response.status, request);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Received response for unknown request ${message.response.id}`
|
||||
);
|
||||
} else if (
|
||||
message.type ===
|
||||
window.textsecure.protobuf.WebSocketMessage.Type.RESPONSE &&
|
||||
message.response
|
||||
) {
|
||||
const { response } = message;
|
||||
const request = outgoing[response.id];
|
||||
if (request) {
|
||||
request.response = response;
|
||||
let callback = request.error;
|
||||
if (
|
||||
response.status &&
|
||||
response.status >= 200 &&
|
||||
response.status < 300
|
||||
) {
|
||||
callback = request.success;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (blob instanceof ArrayBuffer) {
|
||||
handleArrayBuffer(blob);
|
||||
} else {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
handleArrayBuffer(reader.result as ArrayBuffer);
|
||||
};
|
||||
reader.readAsArrayBuffer(blob as any);
|
||||
if (typeof callback === 'function') {
|
||||
callback(response.message, response.status, request);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Received response for unknown request ${message.response.id}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
socket.on('message', onMessage);
|
||||
|
||||
if (opts.keepalive) {
|
||||
this.keepalive = new KeepAlive(this, {
|
||||
|
@ -217,15 +207,13 @@ export default class WebSocketResource extends EventTarget {
|
|||
});
|
||||
const resetKeepAliveTimer = this.keepalive.reset.bind(this.keepalive);
|
||||
|
||||
socket.addEventListener('open', resetKeepAliveTimer);
|
||||
socket.addEventListener('message', resetKeepAliveTimer);
|
||||
socket.addEventListener(
|
||||
'close',
|
||||
this.keepalive.stop.bind(this.keepalive)
|
||||
);
|
||||
this.keepalive.reset();
|
||||
|
||||
socket.on('message', resetKeepAliveTimer);
|
||||
socket.on('close', this.keepalive.stop.bind(this.keepalive));
|
||||
}
|
||||
|
||||
socket.addEventListener('close', () => {
|
||||
socket.on('close', () => {
|
||||
this.closed = true;
|
||||
});
|
||||
|
||||
|
@ -242,7 +230,7 @@ export default class WebSocketResource extends EventTarget {
|
|||
socket.close(code, reason);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
socket.onmessage = undefined;
|
||||
socket.removeListener('message', onMessage);
|
||||
|
||||
// On linux the socket can wait a long time to emit its close event if we've
|
||||
// lost the internet connection. On the order of minutes. This speeds that
|
||||
|
@ -261,6 +249,13 @@ export default class WebSocketResource extends EventTarget {
|
|||
}, 5000);
|
||||
};
|
||||
}
|
||||
|
||||
public forceKeepAlive(): void {
|
||||
if (!this.keepalive) {
|
||||
return;
|
||||
}
|
||||
this.keepalive.send();
|
||||
}
|
||||
}
|
||||
|
||||
type KeepAliveOptionsType = {
|
||||
|
@ -269,15 +264,15 @@ type KeepAliveOptionsType = {
|
|||
};
|
||||
|
||||
class KeepAlive {
|
||||
keepAliveTimer: any;
|
||||
private keepAliveTimer: NodeJS.Timeout | undefined;
|
||||
|
||||
disconnectTimer: any;
|
||||
private disconnectTimer: NodeJS.Timeout | undefined;
|
||||
|
||||
path: string;
|
||||
private path: string;
|
||||
|
||||
disconnect: boolean;
|
||||
private disconnect: boolean;
|
||||
|
||||
wsr: WebSocketResource;
|
||||
private wsr: WebSocketResource;
|
||||
|
||||
constructor(
|
||||
websocketResource: WebSocketResource,
|
||||
|
@ -292,30 +287,46 @@ class KeepAlive {
|
|||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearTimeout(this.keepAliveTimer);
|
||||
clearTimeout(this.disconnectTimer);
|
||||
public stop(): void {
|
||||
this.clearTimers();
|
||||
}
|
||||
|
||||
reset() {
|
||||
clearTimeout(this.keepAliveTimer);
|
||||
clearTimeout(this.disconnectTimer);
|
||||
this.keepAliveTimer = setTimeout(() => {
|
||||
if (this.disconnect) {
|
||||
// automatically disconnect if server doesn't ack
|
||||
this.disconnectTimer = setTimeout(() => {
|
||||
clearTimeout(this.keepAliveTimer);
|
||||
this.wsr.close(3001, 'No response to keepalive request');
|
||||
}, 10000);
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
window.log.info('Sending a keepalive message');
|
||||
this.wsr.sendRequest({
|
||||
verb: 'GET',
|
||||
path: this.path,
|
||||
success: this.reset.bind(this),
|
||||
});
|
||||
}, 55000);
|
||||
public send(): void {
|
||||
this.clearTimers();
|
||||
|
||||
if (this.disconnect) {
|
||||
// automatically disconnect if server doesn't ack
|
||||
this.disconnectTimer = setTimeout(() => {
|
||||
this.clearTimers();
|
||||
|
||||
this.wsr.close(3001, 'No response to keepalive request');
|
||||
}, 10000);
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
window.log.info('WebSocketResources: Sending a keepalive message');
|
||||
this.wsr.sendRequest({
|
||||
verb: 'GET',
|
||||
path: this.path,
|
||||
success: this.reset.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this.clearTimers();
|
||||
|
||||
this.keepAliveTimer = setTimeout(() => this.send(), 55000);
|
||||
}
|
||||
|
||||
private clearTimers(): void {
|
||||
if (this.keepAliveTimer) {
|
||||
clearTimeout(this.keepAliveTimer);
|
||||
this.keepAliveTimer = undefined;
|
||||
}
|
||||
if (this.disconnectTimer) {
|
||||
clearTimeout(this.disconnectTimer);
|
||||
this.disconnectTimer = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue