add reconnect action to rtmt

This commit is contained in:
Halit Aksoy 2022-09-05 08:25:12 +03:00
parent c8beb839af
commit 39aaa07d07
5 changed files with 110 additions and 82 deletions

View File

@ -16,6 +16,7 @@ import { Theme } from './theme/Theme';
import LobyPage from './routes/LobyPage'; import LobyPage from './routes/LobyPage';
import swal from 'sweetalert'; import swal from 'sweetalert';
import { ConnectionState } from './rtmt/rtmt'; import { ConnectionState } from './rtmt/rtmt';
import Util from './util/Util';
const context = initContext(); const context = initContext();
@ -27,56 +28,42 @@ const MancalaApp: FunctionComponent = () => {
const [theme, setTheme] = useState<Theme>(context.themeManager.theme); const [theme, setTheme] = useState<Theme>(context.themeManager.theme);
const onConnectionDone = () => { const onConnectionError = (event: Event) => console.error("(RTMT) Connection Error: ", event);
setConnetionState("connected");
}; const onConnectionChange = (_connectionState: ConnectionState) => setConnetionState(_connectionState);
const onConnectionLost = () => {
setConnetionState("reconnecting"); const onThemeChange = (theme: Theme) => setTheme(theme);
connectToServer();
}; const connectRTMT = (userKey: string) => {
const onConnectionError = (event: Event) => { const rtmt = context.rtmt as RTMTWS;
setConnetionState("error"); rtmt.on("error", onConnectionError);
connectToServer(); rtmt.on("connectionchange", onConnectionChange)
}; rtmt.connectWebSocket(userKey)
const onThemeChange = (theme: Theme) => { return rtmt;
setTheme(theme)
} }
const connectToServer = async () => {
try { const loadUserKeyAndConnectServer = () => {
const userKey = await context.userKeyStore.getUserKey(); context.userKeyStore.getUserKey().then((userKey: string) => {
setUserKey(userKey); setUserKey(userKey);
(context.rtmt as RTMTWS).initWebSocket(userKey); connectRTMT(userKey);
} catch (error) { }).catch((error) => {
//TODO: check if it is network error! //TODO: check if it is network error!
swal(context.texts.Error + "!", context.texts.ErrorWhenRetrievingInformation, "error"); swal(context.texts.Error + "!", context.texts.ErrorWhenRetrievingInformation, "error");
console.error(error); console.error(error);
});
}
const disposeApp = () => {
context.rtmt?.dispose();
context.themeManager.off("themechange", onThemeChange);
} }
};
React.useEffect(() => { React.useEffect(() => {
context.rtmt.on("open", onConnectionDone); loadUserKeyAndConnectServer();
context.rtmt.on("close", onConnectionLost);
context.rtmt.on("error", onConnectionError);
context.themeManager.on("themechange", onThemeChange); context.themeManager.on("themechange", onThemeChange);
setConnetionState("connecting"); return () => disposeApp();
connectToServer();
return () => {
context.rtmt.dispose();
context.themeManager.off("themechange", onThemeChange);
};
}, []); }, []);
const showConnectionState = connectionState != "connected";
const floatingPanelColor = context.themeManager.theme.boardColor;
const connectionStateText = () => {
const map: { [key: string]: string } = {
connecting: context.texts.Connecting,
connected: context.texts.Connected,
error: context.texts.CannotConnect,
reconnecting: context.texts.ConnectingAgain,
};
return map[connectionState];
};
const textColorOnBoard = getColorByBrightness( const textColorOnBoard = getColorByBrightness(
context.themeManager.theme.boardColor, context.themeManager.theme.boardColor,
context.themeManager.theme.textColor, context.themeManager.theme.textColor,
@ -96,8 +83,8 @@ const MancalaApp: FunctionComponent = () => {
</Route> </Route>
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
<FloatingPanel context={context} color={floatingPanelColor} visible={showConnectionState}> <FloatingPanel context={context} color={context.themeManager.theme.boardColor} visible={connectionState != "connected"}>
<span style={{ color: textColorOnBoard, transition: 'color 0.5s' }}>{connectionStateText()}</span> <span style={{ color: textColorOnBoard, transition: 'color 0.5s' }}>{Util.getTextByConnectionState(context, connectionState)}</span>
</FloatingPanel> </FloatingPanel>
</> </>
); );

View File

@ -17,3 +17,4 @@ export const server: Server = useLocalServer ? {
wsServerAdress: "wss://segin.one/mancala-backend-beta/", wsServerAdress: "wss://segin.one/mancala-backend-beta/",
}; };
export const RTMT_WS_PING_INTERVAL = 1000, RTMT_WS_PING_INTERVAL_BUFFER_TIME = 2000;

View File

@ -5,7 +5,7 @@ export type OnMessage = (message : Object) => any
export type ConnectionState = "none" | "connecting" | "error" | "connected" | "closed" | "reconnecting"; export type ConnectionState = "none" | "connecting" | "error" | "connected" | "closed" | "reconnecting";
export type RtmtEventTypes = "open" | "close" | "connected" | "error" | "disconnected" | "message"; export type RtmtEventTypes = "open" | "close" | "connected" | "error" | "disconnected" | "message" | "connectionchange";
export interface RTMT extends EventEmitter2 { export interface RTMT extends EventEmitter2 {
get connectionState() : ConnectionState; get connectionState() : ConnectionState;

View File

@ -1,63 +1,91 @@
import { decode, encode } from "./encode_decode_message"; import { decode, encode } from "./encode_decode_message";
import { channel_ping, channel_pong } from "../const/channel_names"; import { channel_ping, channel_pong } from "../const/channel_names";
import { server } from "../const/config"; import { RTMT_WS_PING_INTERVAL, RTMT_WS_PING_INTERVAL_BUFFER_TIME, server } from "../const/config";
import EventEmitter2, { Listener } from "eventemitter2"; import EventEmitter2, { Listener } from "eventemitter2";
import { Bytes, ConnectionState, RTMT, RtmtEventTypes } from "./rtmt"; import { Bytes, ConnectionState, RTMT, RtmtEventTypes } from "./rtmt";
const PING_INTERVAL = 15000, PING_INTERVAL_BUFFER_TIME = 1000;
const MESSAGE_CHANNEL_PREFIX = "message_channel"; const MESSAGE_CHANNEL_PREFIX = "message_channel";
export class RTMTWS extends EventEmitter2 implements RTMT { export class RTMTWS extends EventEmitter2 implements RTMT {
private webSocket: WebSocket; private webSocket?: WebSocket;
private pingTimeout?: number = undefined; private pingTimeout?: number = undefined;
private _connectionState: ConnectionState = "none"; private _connectionState: ConnectionState = "none";
private userKey: string;
constructor() {
super();
}
get connectionState(): ConnectionState { get connectionState(): ConnectionState {
return this._connectionState; return this._connectionState;
} }
public initWebSocket(userKey: string) { protected setConnectionState(connectionState: ConnectionState) {
this._connectionState = this._connectionState !== "none" ? "reconnecting" : "connecting"; this._connectionState = connectionState;
const url = server.wsServerAdress + "?userKey=" + userKey; this.emit("connectionchange", this._connectionState);
}
private createWebSocket() {
const url = server.wsServerAdress + "?userKey=" + this.userKey;
const webSocket = new WebSocket(url); const webSocket = new WebSocket(url);
webSocket.onopen = () => { webSocket.onopen = () => this.onWebSocketOpen(webSocket);
console.info("(RTMT) WebSocket has opened"); webSocket.onclose = () => this.onWebSocketClose(webSocket);
webSocket.onmessage = (event: MessageEvent) => this.onWebSocketMessage(webSocket, event);
webSocket.onerror = (error: any) => this.onWebSocketError(webSocket, error);
}
private disposeWebSocket() {
if (!this.webSocket) return;
this.webSocket.onopen = () => { };
this.webSocket.onclose = () => { };
this.webSocket.onmessage = (event: MessageEvent) => { };
this.webSocket.onerror = (error: any) => { };
this.webSocket = undefined;
}
public connectWebSocket(userKey: string) {
this.setConnectionState("connecting");
this.userKey = userKey;
this.createWebSocket();
}
private reconnectWebSocket() {
this.setConnectionState("reconnecting");
this.disposeWebSocket();
setTimeout(() => this.createWebSocket(), 1000);
}
protected onWebSocketOpen(webSocket: WebSocket) {
this.webSocket = webSocket; this.webSocket = webSocket;
this.setConnectionState("connected");
console.info("(RTMT) WebSocket has opened");
this.heartbeat(); this.heartbeat();
this._connectionState = "connected";
this.emit("open"); this.emit("open");
}; }
webSocket.onclose = () => {
protected onWebSocketMessage(webSocket: WebSocket, event: MessageEvent) {
const { channel, message } = decode(event.data);
this.onMessage(channel, message);
}
protected onWebSocketError(webSocket: WebSocket, error: any) {
this.setConnectionState("error");
console.error(error);
this.emit("error", error);
}
protected onWebSocketClose(webSocket: WebSocket) {
this.setConnectionState("closed");
console.info("(RTMT) WebSocket has closed"); console.info("(RTMT) WebSocket has closed");
//this.WebSocket = undefined //this.WebSocket = undefined
clearTimeout(this.pingTimeout); clearTimeout(this.pingTimeout);
this._connectionState = "closed";
this.emit("close"); this.emit("close");
}; this.reconnectWebSocket();
webSocket.onmessage = (event: MessageEvent) => {
const { channel, message } = decode(event.data);
this.onMessage(channel, message);
};
webSocket.onerror = (error) => {
console.error(error);
this._connectionState = "error";
this.emit("error", error);
}
} }
private heartbeat() { private heartbeat() {
clearTimeout(this.pingTimeout); clearTimeout(this.pingTimeout);
// Use `WebSocket#terminate()`, which immediately destroys the connection,
// instead of `WebSocket#close()`, which waits for the close timer.
// Delay should be equal to the interval at which your server
// sends out pings plus a conservative assumption of the latency.
this.pingTimeout = setTimeout(() => { this.pingTimeout = setTimeout(() => {
if (!this.webSocket) return;
this.webSocket.close(); this.webSocket.close();
}, PING_INTERVAL + PING_INTERVAL_BUFFER_TIME); this.onWebSocketClose(this.webSocket);
}, RTMT_WS_PING_INTERVAL + RTMT_WS_PING_INTERVAL_BUFFER_TIME);
} }
public sendMessage(channel: string, message: Object) { public sendMessage(channel: string, message: Object) {
@ -96,7 +124,7 @@ export class RTMTWS extends EventEmitter2 implements RTMT {
} }
public dispose() { public dispose() {
this.webSocket.close(); this.disposeWebSocket();
this.removeAllListeners(); this.removeAllListeners();
} }
} }

View File

@ -1,4 +1,5 @@
import { Context } from "../context/context"; import { Context } from "../context/context";
import { ConnectionState } from "../rtmt/rtmt";
import notyf from "./Notyf"; import notyf from "./Notyf";
export default class Util { export default class Util {
@ -17,4 +18,15 @@ export default class Util {
} }
return false; return false;
} }
public static getTextByConnectionState(context: Context, connectionState: ConnectionState): string {
const map: { [key: string]: string } = {
connecting: context.texts.Connecting,
connected: context.texts.Connected,
error: context.texts.CannotConnect,
closed: context.texts.ConnectingAgain,
reconnecting: context.texts.ConnectingAgain,
};
return map[connectionState];
}
} }