[mobile] add rtmt
This commit is contained in:
parent
1653fd5b89
commit
c330c9d4f5
@ -17,7 +17,8 @@
|
|||||||
"react-i18next": "^14.1.0",
|
"react-i18next": "^14.1.0",
|
||||||
"react-native": "0.73.6",
|
"react-native": "0.73.6",
|
||||||
"react-native-safe-area-context": "^4.9.0",
|
"react-native-safe-area-context": "^4.9.0",
|
||||||
"react-native-screens": "^3.29.0"
|
"react-native-screens": "^3.29.0",
|
||||||
|
"tiny-emitter": "^2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
|
|||||||
13
mobile/src/const/channel_names.ts
Normal file
13
mobile/src/const/channel_names.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export const channel_new_game = "new_game"
|
||||||
|
export const channel_on_game_start = "on_game_start"
|
||||||
|
export const channel_game_move = "game_move"
|
||||||
|
export const channel_on_game_update = "on_game_update"
|
||||||
|
export const channel_leave_game = "leave_game"
|
||||||
|
export const channel_on_game_end = "on_game_end"
|
||||||
|
export const channel_on_game_crashed = "on_game_crashed"
|
||||||
|
export const channel_on_game_user_leave = "on_game_user_leave"
|
||||||
|
export const channel_ping = "ping"
|
||||||
|
export const channel_pong = "pong"
|
||||||
|
export const channel_on_user_connection_change = "channel_on_user_connection_change"
|
||||||
|
export const channel_listen_game_events = "channel_listen_game_events"
|
||||||
|
export const channel_unlisten_game_events = "channel_unlisten_game_events"
|
||||||
17
mobile/src/const/config.ts
Normal file
17
mobile/src/const/config.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export const useLocalServer = false;
|
||||||
|
export const isAlpha = true;
|
||||||
|
|
||||||
|
export type Server = {
|
||||||
|
serverAdress: string;
|
||||||
|
wsServerAdress: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const server: Server = useLocalServer ? {
|
||||||
|
serverAdress: "http://localhost:5005",
|
||||||
|
wsServerAdress: "ws://localhost:5005",
|
||||||
|
} : {
|
||||||
|
serverAdress: "https://mancala.segin.one",
|
||||||
|
wsServerAdress: "wss://mancala.segin.one",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RTMT_WS_PING_INTERVAL = 1000, RTMT_WS_PING_INTERVAL_BUFFER_TIME = 2000;
|
||||||
12
mobile/src/rtmt/byte_util.ts
Normal file
12
mobile/src/rtmt/byte_util.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Bytes } from "./rtmt"
|
||||||
|
const textEncoder = new TextEncoder()
|
||||||
|
const textDecoder = new TextDecoder("utf-8")
|
||||||
|
|
||||||
|
export function encodeText(text : string) {
|
||||||
|
const bytes = textEncoder.encode(text)
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeText(bytes : Bytes) {
|
||||||
|
return textDecoder.decode(bytes)
|
||||||
|
}
|
||||||
20
mobile/src/rtmt/encode_decode_message.ts
Normal file
20
mobile/src/rtmt/encode_decode_message.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { decodeText, encodeText } from "./byte_util";
|
||||||
|
import { Bytes } from "./rtmt";
|
||||||
|
|
||||||
|
const headerLenght = 4
|
||||||
|
//
|
||||||
|
// channel is string, message is byte array
|
||||||
|
//
|
||||||
|
export function encode(channel : string, message : Object) {
|
||||||
|
return JSON.stringify({
|
||||||
|
channel,
|
||||||
|
message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// return { channel : string, message : byte array}
|
||||||
|
//
|
||||||
|
export function decode(bytes : string) {
|
||||||
|
return JSON.parse(bytes);
|
||||||
|
}
|
||||||
18
mobile/src/rtmt/rtmt.ts
Normal file
18
mobile/src/rtmt/rtmt.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { TinyEmitter } from "tiny-emitter"
|
||||||
|
|
||||||
|
export type Bytes = Uint8Array
|
||||||
|
export type OnMessage = (message: Object) => any
|
||||||
|
|
||||||
|
export type ConnectionState = "none" | "connecting" | "error" | "connected" | "closed" | "reconnecting";
|
||||||
|
|
||||||
|
export type RtmtEventTypes = "open" | "close" | "connected" | "error" | "disconnected" | "message" | "connectionchange";
|
||||||
|
|
||||||
|
export interface RTMT extends TinyEmitter {
|
||||||
|
get connectionState(): ConnectionState;
|
||||||
|
sendMessage: (channel: string, message: Object) => void;
|
||||||
|
addMessageListener(channel: string, callback: (message: any) => void): any;
|
||||||
|
removeMessageListener(channel: string, callback: (message: any) => void): any;
|
||||||
|
on(event: RtmtEventTypes, callback: (...value: any[]) => void): this;
|
||||||
|
off(event: RtmtEventTypes, callback: (...value: any[]) => void): this;
|
||||||
|
dispose(): any;
|
||||||
|
}
|
||||||
133
mobile/src/rtmt/rtmt_websocket.ts
Normal file
133
mobile/src/rtmt/rtmt_websocket.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { decode, encode } from "./encode_decode_message";
|
||||||
|
import { RTMT_WS_PING_INTERVAL, RTMT_WS_PING_INTERVAL_BUFFER_TIME, server } from "../const/config";
|
||||||
|
import { Bytes, ConnectionState, RTMT, RtmtEventTypes } from "./rtmt";
|
||||||
|
import { channel_ping, channel_pong } from "../const/channel_names";
|
||||||
|
import { TinyEmitter } from "tiny-emitter";
|
||||||
|
|
||||||
|
const MESSAGE_CHANNEL_PREFIX = "message_channel";
|
||||||
|
|
||||||
|
export class RTMTWS extends TinyEmitter implements RTMT {
|
||||||
|
private webSocket?: WebSocket;
|
||||||
|
private pingTimeout?: number = undefined;
|
||||||
|
private _connectionState: ConnectionState = "none";
|
||||||
|
private userKey: string = "";
|
||||||
|
|
||||||
|
get connectionState(): ConnectionState {
|
||||||
|
return this._connectionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = () => this.onWebSocketOpen(webSocket);
|
||||||
|
webSocket.onclose = () => this.onWebSocketClose(webSocket);
|
||||||
|
webSocket.onmessage = (event: WebSocketMessageEvent) => 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: WebSocketMessageEvent) => { };
|
||||||
|
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.emit("open");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onWebSocketMessage(webSocket: WebSocket, event: WebSocketMessageEvent) {
|
||||||
|
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.emit("close");
|
||||||
|
this.reconnectWebSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
private heartbeat() {
|
||||||
|
clearTimeout(this.pingTimeout);
|
||||||
|
// @ts-ignore
|
||||||
|
this.pingTimeout = setTimeout(() => {
|
||||||
|
if (!this.webSocket) return;
|
||||||
|
console.log("(RTMT) WebSocket self closed");
|
||||||
|
this.webSocket.close();
|
||||||
|
this.onWebSocketClose(this.webSocket);
|
||||||
|
}, RTMT_WS_PING_INTERVAL + RTMT_WS_PING_INTERVAL_BUFFER_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendMessage(channel: string, message: Object) {
|
||||||
|
if (this.webSocket === undefined) {
|
||||||
|
console.error("(RTMT) WebSocket is undefined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = encode(channel, message);
|
||||||
|
this.webSocket.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMessage(channel: string, message: Bytes) {
|
||||||
|
if (channel === channel_ping) {
|
||||||
|
this.heartbeat();
|
||||||
|
this.sendMessage(channel_pong, {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: Maybe we should warn if there is not any listener for channel
|
||||||
|
this.emit(MESSAGE_CHANNEL_PREFIX + channel, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public on(event: RtmtEventTypes, callback: (...value: any[]) => void): Listener | this {
|
||||||
|
return super.on(event, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public off(event: RtmtEventTypes, callback: (...value: any[]) => void): this {
|
||||||
|
return super.off(event, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addMessageListener(channel: string, callback: (message: any) => void) {
|
||||||
|
super.on(MESSAGE_CHANNEL_PREFIX + channel, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeMessageListener(channel: string, callback: (message: any) => void) {
|
||||||
|
super.off(MESSAGE_CHANNEL_PREFIX + channel, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
this.disposeWebSocket();
|
||||||
|
// TODO
|
||||||
|
//this.removeAllListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6377,6 +6377,11 @@ through2@^2.0.1:
|
|||||||
readable-stream "~2.3.6"
|
readable-stream "~2.3.6"
|
||||||
xtend "~4.0.1"
|
xtend "~4.0.1"
|
||||||
|
|
||||||
|
tiny-emitter@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
|
||||||
|
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
|
||||||
|
|
||||||
tmpl@1.0.5:
|
tmpl@1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
|
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user