Add socket reconnect logic
This commit is contained in:
parent
d79fe6982f
commit
d280a91115
3 changed files with 113 additions and 22 deletions
|
@ -34,11 +34,6 @@
|
||||||
Databases
|
Databases
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<router-link :to="{ name: 'server-network' }">
|
|
||||||
Networking
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +44,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="fixed pin-r pin-b m-6 max-w-sm" v-show="connectionError">
|
<div class="fixed pin-r pin-b m-6 max-w-sm" v-show="connectionError">
|
||||||
<div class="alert error">
|
<div class="alert error">
|
||||||
There was an error while attempting to connect to the Daemon websocket. Error reported was: "{{connectionError.message}}"
|
There was an error while attempting to connect to the Daemon websocket. Error: {{connectionError}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -24,6 +24,21 @@ export default class SocketioConnector {
|
||||||
*/
|
*/
|
||||||
store: Store<any> | undefined;
|
store: Store<any> | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks a reconnect attempt for the websocket. Will gradually back off on attempts
|
||||||
|
* after a certain period of time has elapsed.
|
||||||
|
*/
|
||||||
|
private reconnectTimeout: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the number of reconnect attempts which is used to determine the backoff
|
||||||
|
* throttle for connections.
|
||||||
|
*/
|
||||||
|
private reconnectAttempts: number = 0;
|
||||||
|
|
||||||
|
private socketProtocol?: string;
|
||||||
|
private socketUrl?: string;
|
||||||
|
|
||||||
constructor(store: Store<any> | undefined) {
|
constructor(store: Store<any> | undefined) {
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
|
@ -32,15 +47,23 @@ export default class SocketioConnector {
|
||||||
/**
|
/**
|
||||||
* Initialize a new Socket connection.
|
* Initialize a new Socket connection.
|
||||||
*/
|
*/
|
||||||
connect(url: string, protocols?: string | string[]): void {
|
public connect(url: string, protocol?: string): void {
|
||||||
this.socket = new WebSocket(url, protocols);
|
this.socketUrl = url;
|
||||||
|
this.socketProtocol = protocol;
|
||||||
|
|
||||||
|
this.connectToSocket()
|
||||||
|
.then(socket => {
|
||||||
|
this.socket = socket;
|
||||||
|
this.emitAndPassToStore(SOCKET_CONNECT);
|
||||||
this.registerEventListeners();
|
this.registerEventListeners();
|
||||||
|
})
|
||||||
|
.catch(() => this.reconnectToSocket());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the socket instance we are working with.
|
* Return the socket instance we are working with.
|
||||||
*/
|
*/
|
||||||
instance(): WebSocket | null {
|
public instance(): WebSocket | null {
|
||||||
return this.socket;
|
return this.socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +71,7 @@ export default class SocketioConnector {
|
||||||
* Sends an event along to the websocket. If there is no active connection, a void
|
* Sends an event along to the websocket. If there is no active connection, a void
|
||||||
* result is returned.
|
* result is returned.
|
||||||
*/
|
*/
|
||||||
emit(event: string, payload?: string | Array<string>): void | false {
|
public emit(event: string, payload?: string | Array<string>): void | false {
|
||||||
if (!this.socket) {
|
if (!this.socket) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -62,23 +85,23 @@ export default class SocketioConnector {
|
||||||
* Register the event listeners for this socket including user-defined ones in the store as
|
* Register the event listeners for this socket including user-defined ones in the store as
|
||||||
* well as global system events from Socekt.io.
|
* well as global system events from Socekt.io.
|
||||||
*/
|
*/
|
||||||
registerEventListeners() {
|
protected registerEventListeners() {
|
||||||
if (!this.socket) {
|
if (!this.socket) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.socket.onopen = () => this.emitAndPassToStore(SOCKET_CONNECT);
|
this.socket.onclose = () => {
|
||||||
this.socket.onclose = () => this.emitAndPassToStore(SOCKET_DISCONNECT);
|
this.reconnectToSocket();
|
||||||
|
this.emitAndPassToStore(SOCKET_DISCONNECT);
|
||||||
|
};
|
||||||
|
|
||||||
this.socket.onerror = () => {
|
this.socket.onerror = () => {
|
||||||
// @todo reconnect?
|
if (this.socket && this.socket.readyState !== WebSocket.OPEN) {
|
||||||
if (this.socket && this.socket.readyState !== 1) {
|
|
||||||
this.emitAndPassToStore(SOCKET_ERROR, ['Failed to connect to websocket.']);
|
this.emitAndPassToStore(SOCKET_ERROR, ['Failed to connect to websocket.']);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onmessage = (wse): void => {
|
this.socket.onmessage = (wse): void => {
|
||||||
console.log('Socket message:', wse.data);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let {event, args}: WingsWebsocketResponse = JSON.parse(wse.data);
|
let {event, args}: WingsWebsocketResponse = JSON.parse(wse.data);
|
||||||
|
|
||||||
|
@ -91,10 +114,83 @@ export default class SocketioConnector {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an actual socket connection, wrapped as a Promise for an easier interface.
|
||||||
|
*/
|
||||||
|
protected connectToSocket(): Promise<WebSocket> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let hasReturned = false;
|
||||||
|
const socket = new WebSocket(this.socketUrl!, this.socketProtocol);
|
||||||
|
|
||||||
|
socket.onopen = () => {
|
||||||
|
if (hasReturned) {
|
||||||
|
socket && socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
hasReturned = true;
|
||||||
|
this.resetConnectionAttempts();
|
||||||
|
resolve(socket);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejectFunc = () => {
|
||||||
|
if (!hasReturned) {
|
||||||
|
hasReturned = true;
|
||||||
|
this.emitAndPassToStore(SOCKET_ERROR, ['Failed to connect to websocket.']);
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = rejectFunc;
|
||||||
|
socket.onclose = rejectFunc;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to reconnect to the socket instance if it becomes disconnected.
|
||||||
|
*/
|
||||||
|
private reconnectToSocket() {
|
||||||
|
const { socket } = this;
|
||||||
|
if (!socket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the existing timeout if one exists for some reason.
|
||||||
|
this.reconnectTimeout && clearTimeout(this.reconnectTimeout);
|
||||||
|
|
||||||
|
this.reconnectTimeout = setTimeout(() => {
|
||||||
|
console.warn(`Attempting to reconnect to websocket [${this.reconnectAttempts}]...`);
|
||||||
|
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
this.connect(this.socketUrl!, this.socketProtocol);
|
||||||
|
}, this.getIntervalTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetConnectionAttempts() {
|
||||||
|
this.reconnectTimeout && clearTimeout(this.reconnectTimeout);
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the amount of time we should wait before attempting to reconnect to the socket.
|
||||||
|
*/
|
||||||
|
private getIntervalTimeout(): number {
|
||||||
|
if (this.reconnectAttempts < 10) {
|
||||||
|
return 50;
|
||||||
|
} else if (this.reconnectAttempts < 25) {
|
||||||
|
return 500;
|
||||||
|
} else if (this.reconnectAttempts < 50) {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 2500;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits the event over the event emitter and also passes it along to the vuex store.
|
* Emits the event over the event emitter and also passes it along to the vuex store.
|
||||||
*/
|
*/
|
||||||
emitAndPassToStore(event: string, payload?: Array<string>) {
|
private emitAndPassToStore(event: string, payload?: Array<string>) {
|
||||||
payload ? SocketEmitter.emit(event, ...payload) : SocketEmitter.emit(event);
|
payload ? SocketEmitter.emit(event, ...payload) : SocketEmitter.emit(event);
|
||||||
this.passToStore(event, payload);
|
this.passToStore(event, payload);
|
||||||
}
|
}
|
||||||
|
@ -102,7 +198,7 @@ export default class SocketioConnector {
|
||||||
/**
|
/**
|
||||||
* Pass event calls off to the Vuex store if there is a corresponding function.
|
* Pass event calls off to the Vuex store if there is a corresponding function.
|
||||||
*/
|
*/
|
||||||
passToStore(event: string, payload?: Array<string>) {
|
private passToStore(event: string, payload?: Array<string>) {
|
||||||
if (!this.store) {
|
if (!this.store) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +222,7 @@ export default class SocketioConnector {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrap(args: Array<string>) {
|
private unwrap(args: Array<string>) {
|
||||||
return (args && args.length <= 1) ? args[0] : args;
|
return (args && args.length <= 1) ? args[0] : args;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue