[mobile] add rtmt
This commit is contained in:
parent
1653fd5b89
commit
c330c9d4f5
@ -17,7 +17,8 @@
|
||||
"react-i18next": "^14.1.0",
|
||||
"react-native": "0.73.6",
|
||||
"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": {
|
||||
"@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"
|
||||
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:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user