[mobile] add rtmt

This commit is contained in:
Halit Aksoy 2024-03-25 02:56:54 +03:00
parent 1653fd5b89
commit c330c9d4f5
8 changed files with 220 additions and 1 deletions

View File

@ -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",

View 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"

View 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;

View 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)
}

View 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
View 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;
}

View 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();
}
}

View File

@ -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"