diff --git a/src/consts/channel_names.ts b/src/consts/channel_names.ts index 55250cf..789d501 100644 --- a/src/consts/channel_names.ts +++ b/src/consts/channel_names.ts @@ -7,4 +7,5 @@ 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" \ No newline at end of file +export const channel_pong = "pong" +export const channel_on_user_connection_change = "channel_on_user_connection_change" \ No newline at end of file diff --git a/src/game/GameManager.ts b/src/game/GameManager.ts index a604419..f3bc1a8 100644 --- a/src/game/GameManager.ts +++ b/src/game/GameManager.ts @@ -9,17 +9,19 @@ import { channel_on_game_crashed, channel_on_game_start, channel_on_game_update, - channel_on_game_user_leave + channel_on_game_user_leave, + channel_on_user_connection_change } from "../consts/channel_names"; import { GameMove } from "../models/GameMove"; import { GameCrashManager } from "./GameCrashManager"; import { MatchMaker } from "../matchmaker/MatchMaker"; +import { UserConnectionInfo } from "../models/UserConnectionInfo"; export class GameManager { gameStore: GameStore; rtmt: RTMT; matchMaker: MatchMaker; - + constructor(params: { gameStore: GameStore, rtmt: RTMT, matchMaker: MatchMaker }) { this.gameStore = params.gameStore; this.rtmt = params.rtmt; @@ -30,6 +32,7 @@ export class GameManager { private initialize() { this.listenOnPlayersPaired(); this.listenRtmtMessages(); + this.listenUserConnectionChange(); } private listenRtmtMessages() { @@ -43,7 +46,7 @@ export class GameManager { this.rtmt.listenMessage(channel_leave_game, (userKey: string, message: Object) => { this.onPlayerLeave(userKey) - }); + }); } private onGameMove(userKey: string, gameMove: GameMove) { @@ -108,6 +111,16 @@ export class GameManager { public startGame(game: MancalaGame) { this.rtmt.sendMessage(game.player1Id, channel_on_game_start, game); this.rtmt.sendMessage(game.player2Id, channel_on_game_start, game); + this.sendUserConnectionInfo(game, game.player1Id); + this.sendUserConnectionInfo(game, game.player2Id); + } + + public sendUserConnectionInfo(game: MancalaGame, playerId: string, isOnline?: boolean) { + //todo: reimplement when watch game feature added + const _isOnline = isOnline === undefined ? this.rtmt.isClientOnline(playerId) : isOnline; + const otherUser = game.player1Id === playerId ? game.player2Id : game.player1Id; + const userConnectionInfo: UserConnectionInfo = { userId: playerId, isOnline: _isOnline }; + this.rtmt.sendMessage(otherUser, channel_on_user_connection_change, userConnectionInfo); } public deleteGame(game: MancalaGame) { @@ -116,4 +129,17 @@ export class GameManager { this.gameStore.remove(game.player2Id); } } + + private listenUserConnectionChange() { + this.rtmt.listenOnClientConnectionChange((clientId: string, isOnline: boolean) => { + const game = this.gameStore.get(clientId); + if (game) { + this.sendUserConnectionInfo(game, clientId, isOnline); + if (isOnline) { + const otherUser = game.player1Id === clientId ? game.player2Id : game.player1Id; + this.sendUserConnectionInfo(game, otherUser); + } + } + }); + } } \ No newline at end of file diff --git a/src/models/User.ts b/src/models/User.ts new file mode 100644 index 0000000..e5ab1ae --- /dev/null +++ b/src/models/User.ts @@ -0,0 +1,6 @@ +export interface User { + id: string; + name: string; + isOnline: boolean; + isAnonymous: boolean; +} \ No newline at end of file diff --git a/src/models/UserConnectionInfo.ts b/src/models/UserConnectionInfo.ts new file mode 100644 index 0000000..3ce426f --- /dev/null +++ b/src/models/UserConnectionInfo.ts @@ -0,0 +1,4 @@ +export interface UserConnectionInfo { + userId: string; + isOnline: boolean; +} \ No newline at end of file diff --git a/src/rtmt/rtmt.ts b/src/rtmt/rtmt.ts index aa8ccdf..238cad0 100644 --- a/src/rtmt/rtmt.ts +++ b/src/rtmt/rtmt.ts @@ -1,8 +1,10 @@ export type Bytes = Buffer export type OnMessage = (clientID: string, message: Object) => any +export type OnClientConnectionChange = (clientID: string, isOnline : boolean) => any export interface RTMT { sendMessage: (clientID: string, channel: string, message: Object) => any; listenMessage: (channel: string, callback: OnMessage) => any; isClientOnline(clientID: string): boolean; + listenOnClientConnectionChange(onUserConnectionChange: OnClientConnectionChange): void; } \ No newline at end of file diff --git a/src/rtmt/rtmt_websocket.ts b/src/rtmt/rtmt_websocket.ts index 45d8529..5f63494 100644 --- a/src/rtmt/rtmt_websocket.ts +++ b/src/rtmt/rtmt_websocket.ts @@ -1,5 +1,5 @@ import { decode, encode } from "./encode_decode_message" -import { Bytes, OnMessage, RTMT } from "./rtmt" +import { Bytes, OnClientConnectionChange, OnMessage, RTMT } from "./rtmt" import WebSocket from "ws" import * as http from 'http'; @@ -17,6 +17,8 @@ export class RTMTWS implements RTMT { private pingInterval?: NodeJS.Timeout = undefined; + onUserConnectionChange?: OnClientConnectionChange; + constructor() { this.messageChannels = new Map() this.wsServer = null @@ -32,6 +34,7 @@ export class RTMTWS implements RTMT { const regexResult = req.url.split(RegExp("\/\?userKey=")); const clientID = regexResult[1] this.clients.set(clientID, ws) + this.onUserConnectionChange?.(clientID, true); ws.on("message", (messageBytes: string) => { this.onWebSocketMessage(clientID, messageBytes) }) @@ -39,6 +42,7 @@ export class RTMTWS implements RTMT { ws.on("close", (code: number, reason: string) => { console.log("WS Closed! code : " + code + " reason : " + reason); this.clients.delete(clientID) + this.onUserConnectionChange?.(clientID, false); }) ws.on("error", (err: Error) => { @@ -130,4 +134,8 @@ export class RTMTWS implements RTMT { isClientOnline(clientID: string): boolean { return this.clients.has(clientID); } + + listenOnClientConnectionChange(onUserConnectionChange: OnClientConnectionChange) { + this.onUserConnectionChange = onUserConnectionChange; + } } \ No newline at end of file