Merge pull request #33 from jhalitaksoy/fix/rtmt-connection-lost

Fix/rtmt connection lost
This commit is contained in:
Halit Aksoy 2022-09-05 18:24:09 +03:00 committed by GitHub
commit b94dcb7c6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 113 additions and 85 deletions

View File

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

View File

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

View File

@ -161,6 +161,7 @@ const GamePage: FunctionComponent<{
if (!game || isSpectator || !userKey) {
return;
}
if (Util.checkConnectionAndMaybeAlert(context)) return;
if (game.mancalaGame.getPlayerIdByIndex(index) !== userKey) {
notyf.error(context.texts.UCanOnlyPlayYourOwnPits);
return;
@ -174,7 +175,6 @@ const GamePage: FunctionComponent<{
notyf.error(context.texts.UMustWaitUntilCurrentMoveComplete);
return;
}
if(Util.checkConnectionAndMaybeAlert(context)) return;
if (!boardViewModel) return;
//TODO: this check should be in mancala.js
if (pit.stoneCount === 0) {

View File

@ -5,7 +5,7 @@ export type OnMessage = (message : Object) => any
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 {
get connectionState() : ConnectionState;

View File

@ -1,63 +1,91 @@
import { decode, encode } from "./encode_decode_message";
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 { Bytes, ConnectionState, RTMT, RtmtEventTypes } from "./rtmt";
const PING_INTERVAL = 15000, PING_INTERVAL_BUFFER_TIME = 1000;
const MESSAGE_CHANNEL_PREFIX = "message_channel";
export class RTMTWS extends EventEmitter2 implements RTMT {
private webSocket: WebSocket;
private webSocket?: WebSocket;
private pingTimeout?: number = undefined;
private _connectionState: ConnectionState = "none";
constructor() {
super();
}
private userKey: string;
get connectionState(): ConnectionState {
return this._connectionState;
}
public initWebSocket(userKey: string) {
this._connectionState = this._connectionState !== "none" ? "reconnecting" : "connecting";
const url = server.wsServerAdress + "?userKey=" + userKey;
protected setConnectionState(connectionState: ConnectionState) {
this._connectionState = connectionState;
this.emit("connectionchange", this._connectionState);
}
private createWebSocket() {
const url = server.wsServerAdress + "?userKey=" + this.userKey;
const webSocket = new WebSocket(url);
webSocket.onopen = () => {
console.info("(RTMT) WebSocket has opened");
webSocket.onopen = () => this.onWebSocketOpen(webSocket);
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.setConnectionState("connected");
console.info("(RTMT) WebSocket has opened");
this.heartbeat();
this._connectionState = "connected";
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");
//this.WebSocket = undefined
clearTimeout(this.pingTimeout);
this._connectionState = "closed";
this.emit("close");
};
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);
}
this.reconnectWebSocket();
}
private heartbeat() {
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(() => {
if (!this.webSocket) return;
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) {
@ -96,7 +124,7 @@ export class RTMTWS extends EventEmitter2 implements RTMT {
}
public dispose() {
this.webSocket.close();
this.disposeWebSocket();
this.removeAllListeners();
}
}

View File

@ -1,4 +1,5 @@
import { Context } from "../context/context";
import { ConnectionState } from "../rtmt/rtmt";
import notyf from "./Notyf";
export default class Util {
@ -17,4 +18,15 @@ export default class Util {
}
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];
}
}