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