Merge pull request #33 from jhalitaksoy/fix/rtmt-connection-lost
Fix/rtmt connection lost
This commit is contained in:
commit
b94dcb7c6c
@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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;
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user