Skip to content

Commit

Permalink
add reconnection strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
Nasar165 committed May 4, 2024
1 parent de22cb5 commit c066bfa
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 62 deletions.
17 changes: 6 additions & 11 deletions app/components/socket.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
'use client';

import React, { useEffect, useState } from 'react';
import { Websocket } from '../lib/websocket/socket';
import { ConState, ISocket } from '../model/websocket';
import React from 'react';
import { CloseEvent, ConState } from '../model/websocket';
import WebSocketHook from '../hook/socketHook';

type Url = { url: string; online: boolean; state: ConState };

export default function Socket({ url, online, state }: Url): React.JSX.Element {
const [socket, setSocket] = useState<ISocket>();
const [socket] = WebSocketHook({ state: state });

const start = () => {
try {
if (!socket?.Alive()) {
socket?.Start(url, state);
setSocket(socket);
} else socket?.Stop(1000);
socket?.Connect(url, state);
} else socket?.Disconnect(CloseEvent.NORMAL);
} catch (err) {
console.error(err);
}
};

useEffect(() => {
setSocket(new Websocket(state));
}, []);

return (
<div>
<button
Expand Down
16 changes: 16 additions & 0 deletions app/hook/socketHook.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect, useRef, useState } from 'react';
import { ConState, IWebSocket } from '../model/websocket';
import { Websocket } from '../lib/websocket/websocket';

type Url = { state: ConState };

export default function WebSocketHook({ state }: Url): [IWebSocket] {
const [socket, setSocket] = useState<IWebSocket>();
const ref = useRef(state);

useEffect(() => {
setSocket(new Websocket(ref.current));
}, []);

return [socket!];
}
51 changes: 4 additions & 47 deletions app/lib/websocket/socket.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,24 @@
import { ConState, DEAD, ISocket, LIVE } from '@/app/model/websocket';
import { CloseEvent, DEAD, ISocket, LIVE } from '@/app/model/websocket';

export class Websocket implements ISocket {
private socket?: WebSocket;
private stateChange: ConState;

constructor(event: ConState) {
this.stateChange = event;
}
export class Socket implements ISocket {
protected socket?: WebSocket;

Alive(): boolean {
return this.socket != null && this.socket.readyState == this.socket.OPEN;
}

Start(url: string): void {
if (this.Alive()) throw Error(LIVE);

this.socket = new WebSocket(url, ['ocpp1.6']);
this.stateChange(true);
this.setEventListeners();

console.info('Websocket connection was successful');
}

Stop(code?: number): void {
if (this.socket == null) throw new Error(DEAD);

try {
this.stateChange(false);
this.socket!.close(code ?? 1000);
} catch (err) {
throw err;
} finally {
this.removeEventListeners();
}

this.socket!.close(code ?? CloseEvent.NORMAL);
console.info('socket closed');
}

protected setEventListeners(): void {
this.socket!.addEventListener('close', this.close.bind(this));
this.socket!.addEventListener('error', this.error.bind(this));
this.socket!.addEventListener('open', this.open.bind(this));
}

protected removeEventListeners(): void {
this.socket!.removeEventListener('close', this.close.bind(this));
this.socket!.removeEventListener('error', this.error.bind(this));
this.socket!.addEventListener('open', this.open.bind(this));
}

protected close(reason: CloseEvent): void {
console.info(reason);
this.Stop(1000);
}

protected error(ev: Event): void {
console.error('an error has occurred', ev);
}

protected open(): void {
this.stateChange(true);
}

Send(message: string) {
if (!this.Alive()) throw new Error(DEAD);
this.socket!.send(message);
Expand Down
75 changes: 75 additions & 0 deletions app/lib/websocket/websocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { CloseEvent, ConState, DEFAULT_TIMER } from '@/app/model/websocket';
import { Socket } from './socket';

let id: ReturnType<typeof setTimeout>;

export class Websocket extends Socket {
private url: string = '';
private timer = 0;
private stateChange: ConState;

constructor(event: ConState) {
super();
this.timer = DEFAULT_TIMER;
this.stateChange = event;
}

Connect(url: string): void {
this.stateChange(true);
this.url = url;
this.Start(url);
this.setEventListeners();
}

Disconnect(code: number): void {
try {
this.stateChange(false);
this.Stop(code);
} catch (error) {
throw error;
} finally {
this.removeEventListeners();
}
}

protected setEventListeners(): void {
this.socket!.addEventListener('close', this.close.bind(this));
this.socket!.addEventListener('error', this.error.bind(this));
this.socket!.addEventListener('open', this.open.bind(this));
}

protected removeEventListeners(): void {
this.socket!.removeEventListener('close', this.close.bind(this));
this.socket!.removeEventListener('error', this.error.bind(this));
this.socket!.addEventListener('open', this.open.bind(this));
}

protected retry(): void {
clearTimeout(id);
console.log('connection lost retrying');
id = setTimeout(() => {
this.Start(this.url);
}, this.timer);
}

protected error(ev: Event): void {
console.error('websocket an error has occurred', ev);
const socket = ev.currentTarget as WebSocket;

if (socket.readyState == socket.CLOSED) {
this.stateChange(false);
this.removeEventListeners();
this.retry();
}
}

protected close(reason: CloseEvent): void {
if (this.socket?.readyState != this.socket?.OPEN) return;
console.info(reason);
this.Stop(CloseEvent.NORMAL);
}

protected open(): void {
this.stateChange(true);
}
}
22 changes: 18 additions & 4 deletions app/model/websocket.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
const CloseEvent = Object.freeze({
NORMAL: 1000,
INTERNALERROR: 1011,
});

const DEFAULT_TIMER = 3600;
const LIVE = 'web socket client is already initialized';
const DEAD = 'web socket client is dead, open a new connection';

type ConState = (connected: boolean) => void;

interface ISocket {
Alive(): boolean;
interface ISocket extends IAlive {
Start(url: string, event: ConState): void;
Stop(code: number): void;
}

export type { ISocket, ConState };
export { LIVE, DEAD };
interface IWebSocket extends IAlive {
Connect(url: string, event: ConState): void;
Disconnect(code: number): void;
}

interface IAlive {
Alive(): boolean;
}

export type { IWebSocket, ISocket, ConState };
export { LIVE, DEAD, DEFAULT_TIMER, CloseEvent };

0 comments on commit c066bfa

Please sign in to comment.