workadventure/back/src/Services/SocketManager.ts

838 lines
32 KiB
TypeScript
Raw Normal View History

2021-06-24 10:09:10 +02:00
import { GameRoom } from "../Model/GameRoom";
import {
ItemEventMessage,
2020-10-16 19:13:26 +02:00
ItemStateMessage,
PlayGlobalMessage,
PointMessage,
RoomJoinedMessage,
ServerToClientMessage,
SilentMessage,
SubMessage,
UserMovedMessage,
UserMovesMessage,
WebRtcDisconnectMessage,
WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage,
WebRtcStartMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
SendUserMessage,
JoinRoomMessage,
Zone as ProtoZone,
BatchToPusherMessage,
SubToPusherMessage,
UserJoinedZoneMessage,
GroupUpdateZoneMessage,
GroupLeftZoneMessage,
WorldFullWarningMessage,
UserLeftZoneMessage,
EmoteEventMessage,
2021-06-24 10:09:10 +02:00
BanUserMessage,
RefreshRoomMessage,
EmotePromptMessage,
VariableMessage,
BatchToPusherRoomMessage,
SubToPusherRoomMessage,
} from "../Messages/generated/messages_pb";
2021-06-24 10:09:10 +02:00
import { User, UserSocket } from "../Model/User";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
import { Group } from "../Model/Group";
import { cpuTracker } from "./CpuTracker";
import {
GROUP_RADIUS,
JITSI_ISS,
MINIMUM_DISTANCE,
SECRET_JITSI_KEY,
2021-06-24 10:09:10 +02:00
TURN_STATIC_AUTH_SECRET,
} from "../Enum/EnvironmentVariable";
2021-06-24 10:09:10 +02:00
import { Movable } from "../Model/Movable";
import { PositionInterface } from "../Model/PositionInterface";
2020-10-16 19:13:26 +02:00
import Jwt from "jsonwebtoken";
2021-06-24 10:09:10 +02:00
import { JITSI_URL } from "../Enum/EnvironmentVariable";
import { clientEventsEmitter } from "./ClientEventsEmitter";
import { gaugeManager } from "./GaugeManager";
import { RoomSocket, ZoneSocket } from "../RoomManager";
2021-06-24 10:09:10 +02:00
import { Zone } from "_Model/Zone";
import Debug from "debug";
2021-06-24 10:09:10 +02:00
import { Admin } from "_Model/Admin";
import crypto from "crypto";
2021-06-24 10:09:10 +02:00
const debug = Debug("sockermanager");
function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void {
// TODO: should we batch those every 100ms?
const batchMessage = new BatchToPusherMessage();
batchMessage.addPayload(subMessage);
socket.write(batchMessage);
}
2020-10-20 16:39:23 +02:00
export class SocketManager {
//private rooms = new Map<string, GameRoom>();
// List of rooms in process of loading.
private roomsPromises = new Map<string, PromiseLike<GameRoom>>();
constructor() {
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
gaugeManager.incNbClientPerRoomGauge(roomId);
});
clientEventsEmitter.registerToClientLeave((clientUUid: string, roomId: string) => {
gaugeManager.decNbClientPerRoomGauge(roomId);
});
}
2020-10-20 16:39:23 +02:00
2021-06-24 10:09:10 +02:00
public async handleJoinRoom(
socket: UserSocket,
joinRoomMessage: JoinRoomMessage
): Promise<{ room: GameRoom; user: User }> {
//join new previous room
2021-06-24 10:09:10 +02:00
const { room, user } = await this.joinRoom(socket, joinRoomMessage);
if (!socket.writable) {
2021-06-24 10:09:10 +02:00
console.warn("Socket was aborted");
return {
room,
2021-06-24 10:09:10 +02:00
user,
};
}
const roomJoinedMessage = new RoomJoinedMessage();
2021-01-17 03:07:46 +01:00
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
for (const [itemId, item] of room.getItemsState().entries()) {
const itemStateMessage = new ItemStateMessage();
itemStateMessage.setItemid(itemId);
itemStateMessage.setStatejson(JSON.stringify(item));
roomJoinedMessage.addItem(itemStateMessage);
}
const variables = await room.getVariablesForTags(user.tags);
for (const [name, value] of variables.entries()) {
const variableMessage = new VariableMessage();
variableMessage.setName(name);
variableMessage.setValue(value);
roomJoinedMessage.addVariable(variableMessage);
}
roomJoinedMessage.setCurrentuserid(user.id);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
socket.write(serverToClientMessage);
return {
room,
2021-06-24 10:09:10 +02:00
user,
};
}
handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) {
2021-07-22 10:33:07 +02:00
const userMoves = userMovesMessage.toObject();
const position = userMovesMessage.getPosition();
2021-07-22 10:33:07 +02:00
// If CPU is high, let's drop messages of users moving (we will only dispatch the final position)
if (cpuTracker.isOverHeating() && userMoves.position?.moving === true) {
return;
}
2021-07-22 10:33:07 +02:00
if (position === undefined) {
throw new Error("Position not found in message");
}
const viewport = userMoves.viewport;
if (viewport === undefined) {
throw new Error("Viewport not found in message");
}
2021-07-22 10:33:07 +02:00
// update position in the world
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
//room.setViewport(client, client.viewport);
}
// Useless now, will be useful again if we allow editing details in game
/*handleSetPlayerDetails(client: UserSocket, playerDetailsMessage: SetPlayerDetailsMessage) {
const playerDetails = {
name: playerDetailsMessage.getName(),
characterLayers: playerDetailsMessage.getCharacterlayersList()
};
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
if (!isSetPlayerDetailsMessage(playerDetails)) {
emitError(client, 'Invalid SET_PLAYER_DETAILS message received: ');
return;
}
client.name = playerDetails.name;
2020-10-20 16:39:23 +02:00
client.characterLayers = SocketManager.mergeCharacterLayersAndCustomTextures(playerDetails.characterLayers, client.textures);
}*/
handleSilentMessage(room: GameRoom, user: User, silentMessage: SilentMessage) {
2021-07-22 10:33:07 +02:00
room.setSilent(user, silentMessage.getSilent());
}
handleItemEvent(room: GameRoom, user: User, itemEventMessage: ItemEventMessage) {
const itemEvent = ProtobufUtils.toItemEvent(itemEventMessage);
2021-07-22 10:33:07 +02:00
const subMessage = new SubMessage();
subMessage.setItemeventmessage(itemEventMessage);
2021-07-22 10:33:07 +02:00
// Let's send the event without using the SocketIO room.
// TODO: move this in the GameRoom class.
for (const user of room.getUsers().values()) {
user.emitInBatch(subMessage);
}
2021-07-22 10:33:07 +02:00
room.setItemState(itemEvent.itemId, itemEvent.state);
}
2021-07-22 10:33:07 +02:00
handleVariableEvent(room: GameRoom, user: User, variableMessage: VariableMessage): Promise<void> {
return room.setVariable(variableMessage.getName(), variableMessage.getValue(), user);
2021-07-06 15:30:49 +02:00
}
emitVideo(room: GameRoom, user: User, data: WebRtcSignalToServerMessage): void {
//send only at user
const remoteUser = room.getUsers().get(data.getReceiverid());
if (remoteUser === undefined) {
2021-06-24 10:09:10 +02:00
console.warn(
"While exchanging a WebRTC signal: client with id ",
data.getReceiverid(),
" does not exist. This might be a race condition."
);
return;
}
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
webrtcSignalToClient.setUserid(user.id);
webrtcSignalToClient.setSignal(data.getSignal());
// TODO: only compute credentials if data.signal.type === "offer"
2021-06-24 10:09:10 +02:00
if (TURN_STATIC_AUTH_SECRET !== "") {
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
webrtcSignalToClient.setWebrtcusername(username);
webrtcSignalToClient.setWebrtcpassword(password);
}
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
//if (!client.disconnecting) {
2021-06-24 10:09:10 +02:00
remoteUser.socket.write(serverToClientMessage);
//}
}
emitScreenSharing(room: GameRoom, user: User, data: WebRtcSignalToServerMessage): void {
//send only at user
const remoteUser = room.getUsers().get(data.getReceiverid());
if (remoteUser === undefined) {
2021-06-24 10:09:10 +02:00
console.warn(
"While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ",
data.getReceiverid(),
" does not exist. This might be a race condition."
);
return;
}
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
webrtcSignalToClient.setUserid(user.id);
webrtcSignalToClient.setSignal(data.getSignal());
// TODO: only compute credentials if data.signal.type === "offer"
2021-06-24 10:09:10 +02:00
if (TURN_STATIC_AUTH_SECRET !== "") {
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
webrtcSignalToClient.setWebrtcusername(username);
webrtcSignalToClient.setWebrtcpassword(password);
}
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
//if (!client.disconnecting) {
2021-06-24 10:09:10 +02:00
remoteUser.socket.write(serverToClientMessage);
//}
}
2021-06-24 10:09:10 +02:00
leaveRoom(room: GameRoom, user: User) {
// leave previous room and world
try {
//user leave previous world
room.leave(user);
if (room.isEmpty()) {
this.roomsPromises.delete(room.roomUrl);
gaugeManager.decNbRoomGauge();
debug('Room is empty. Deleting room "%s"', room.roomUrl);
}
} finally {
clientEventsEmitter.emitClientLeave(user.uuid, room.roomUrl);
2021-06-24 10:09:10 +02:00
console.log("A user left");
}
}
async getOrCreateRoom(roomId: string): Promise<GameRoom> {
//check and create new room
let roomPromise = this.roomsPromises.get(roomId);
if (roomPromise === undefined) {
2021-07-22 10:41:45 +02:00
roomPromise = GameRoom.create(
roomId,
(user: User, group: Group) => this.joinWebRtcRoom(user, group),
(user: User, group: Group) => this.disConnectedUser(user, group),
MINIMUM_DISTANCE,
GROUP_RADIUS,
(thing: Movable, fromZone: Zone | null, listener: ZoneSocket) =>
this.onZoneEnter(thing, fromZone, listener),
(thing: Movable, position: PositionInterface, listener: ZoneSocket) =>
this.onClientMove(thing, position, listener),
(thing: Movable, newZone: Zone | null, listener: ZoneSocket) =>
this.onClientLeave(thing, newZone, listener),
(emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) =>
this.onEmote(emoteEventMessage, listener)
)
.then((gameRoom) => {
gaugeManager.incNbRoomGauge();
return gameRoom;
})
.catch((e) => {
this.roomsPromises.delete(roomId);
throw e;
});
this.roomsPromises.set(roomId, roomPromise);
}
return roomPromise;
}
2021-06-24 10:09:10 +02:00
private async joinRoom(
socket: UserSocket,
joinRoomMessage: JoinRoomMessage
): Promise<{ room: GameRoom; user: User }> {
const roomId = joinRoomMessage.getRoomid();
const room = await socketManager.getOrCreateRoom(roomId);
//join world
const user = room.join(socket, joinRoomMessage);
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
2021-06-24 10:09:10 +02:00
console.log(new Date().toISOString() + " A user joined");
return { room, user };
}
2021-06-24 10:09:10 +02:00
private onZoneEnter(thing: Movable, fromZone: Zone | null, listener: ZoneSocket) {
if (thing instanceof User) {
const userJoinedZoneMessage = new UserJoinedZoneMessage();
if (!Number.isInteger(thing.id)) {
2021-06-24 10:09:10 +02:00
throw new Error("clientUser.userId is not an integer " + thing.id);
}
userJoinedZoneMessage.setUserid(thing.id);
userJoinedZoneMessage.setUseruuid(thing.uuid);
userJoinedZoneMessage.setName(thing.name);
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone));
if (thing.visitCardUrl) {
userJoinedZoneMessage.setVisitcardurl(thing.visitCardUrl);
}
2021-04-02 21:21:11 +02:00
userJoinedZoneMessage.setCompanion(thing.companion);
const subMessage = new SubToPusherMessage();
subMessage.setUserjoinedzonemessage(userJoinedZoneMessage);
emitZoneMessage(subMessage, listener);
//listener.emitInBatch(subMessage);
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(listener, fromZone, thing);
} else {
2021-06-24 10:09:10 +02:00
console.error("Unexpected type for Movable.");
}
}
2021-06-24 10:09:10 +02:00
private onClientMove(thing: Movable, position: PositionInterface, listener: ZoneSocket): void {
if (thing instanceof User) {
const userMovedMessage = new UserMovedMessage();
userMovedMessage.setUserid(thing.id);
userMovedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
const subMessage = new SubToPusherMessage();
subMessage.setUsermovedmessage(userMovedMessage);
emitZoneMessage(subMessage, listener);
//listener.emitInBatch(subMessage);
//console.log("Sending USER_MOVED event");
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(listener, null, thing);
} else {
2021-06-24 10:09:10 +02:00
console.error("Unexpected type for Movable.");
}
}
2021-06-24 10:09:10 +02:00
private onClientLeave(thing: Movable, newZone: Zone | null, listener: ZoneSocket) {
if (thing instanceof User) {
this.emitUserLeftEvent(listener, thing.id, newZone);
} else if (thing instanceof Group) {
this.emitDeleteGroupEvent(listener, thing.getId(), newZone);
} else {
2021-06-24 10:09:10 +02:00
console.error("Unexpected type for Movable.");
}
}
private onEmote(emoteEventMessage: EmoteEventMessage, client: ZoneSocket) {
const subMessage = new SubToPusherMessage();
subMessage.setEmoteeventmessage(emoteEventMessage);
emitZoneMessage(subMessage, client);
}
2021-06-24 10:09:10 +02:00
private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone | null, group: Group): void {
const position = group.getPosition();
const pointMessage = new PointMessage();
pointMessage.setX(Math.floor(position.x));
pointMessage.setY(Math.floor(position.y));
const groupUpdateMessage = new GroupUpdateZoneMessage();
groupUpdateMessage.setGroupid(group.getId());
groupUpdateMessage.setPosition(pointMessage);
groupUpdateMessage.setGroupsize(group.getSize);
groupUpdateMessage.setFromzone(this.toProtoZone(fromZone));
const subMessage = new SubToPusherMessage();
subMessage.setGroupupdatezonemessage(groupUpdateMessage);
emitZoneMessage(subMessage, client);
//client.emitInBatch(subMessage);
}
2021-06-24 10:09:10 +02:00
private emitDeleteGroupEvent(client: ZoneSocket, groupId: number, newZone: Zone | null): void {
const groupDeleteMessage = new GroupLeftZoneMessage();
groupDeleteMessage.setGroupid(groupId);
groupDeleteMessage.setTozone(this.toProtoZone(newZone));
const subMessage = new SubToPusherMessage();
subMessage.setGroupleftzonemessage(groupDeleteMessage);
emitZoneMessage(subMessage, client);
//user.emitInBatch(subMessage);
}
2021-06-24 10:09:10 +02:00
private emitUserLeftEvent(client: ZoneSocket, userId: number, newZone: Zone | null): void {
const userLeftMessage = new UserLeftZoneMessage();
userLeftMessage.setUserid(userId);
userLeftMessage.setTozone(this.toProtoZone(newZone));
const subMessage = new SubToPusherMessage();
subMessage.setUserleftzonemessage(userLeftMessage);
emitZoneMessage(subMessage, client);
}
2021-06-24 10:09:10 +02:00
private toProtoZone(zone: Zone | null): ProtoZone | undefined {
if (zone !== null) {
const zoneMessage = new ProtoZone();
zoneMessage.setX(zone.x);
zoneMessage.setY(zone.y);
return zoneMessage;
}
return undefined;
}
private joinWebRtcRoom(user: User, group: Group) {
for (const otherUser of group.getUsers()) {
if (user === otherUser) {
continue;
}
// Let's send 2 messages: one to the user joining the group and one to the other user
const webrtcStartMessage1 = new WebRtcStartMessage();
webrtcStartMessage1.setUserid(otherUser.id);
webrtcStartMessage1.setInitiator(true);
2021-06-24 10:09:10 +02:00
if (TURN_STATIC_AUTH_SECRET !== "") {
const { username, password } = this.getTURNCredentials("" + otherUser.id, TURN_STATIC_AUTH_SECRET);
webrtcStartMessage1.setWebrtcusername(username);
webrtcStartMessage1.setWebrtcpassword(password);
}
const serverToClientMessage1 = new ServerToClientMessage();
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
2021-06-24 10:09:10 +02:00
user.socket.write(serverToClientMessage1);
const webrtcStartMessage2 = new WebRtcStartMessage();
webrtcStartMessage2.setUserid(user.id);
webrtcStartMessage2.setInitiator(false);
2021-06-24 10:09:10 +02:00
if (TURN_STATIC_AUTH_SECRET !== "") {
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
webrtcStartMessage2.setWebrtcusername(username);
webrtcStartMessage2.setWebrtcpassword(password);
}
const serverToClientMessage2 = new ServerToClientMessage();
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
2021-06-24 10:09:10 +02:00
otherUser.socket.write(serverToClientMessage2);
}
}
/**
* Computes a unique user/password for the TURN server, using a shared secret between the WorkAdventure API server
* and the Coturn server.
* The Coturn server should be initialized with parameters: `--use-auth-secret --static-auth-secret=MySecretKey`
*/
2021-06-24 10:09:10 +02:00
private getTURNCredentials(name: string, secret: string): { username: string; password: string } {
const unixTimeStamp = Math.floor(Date.now() / 1000) + 4 * 3600; // this credential would be valid for the next 4 hours
const username = [unixTimeStamp, name].join(":");
const hmac = crypto.createHmac("sha1", secret);
hmac.setEncoding("base64");
hmac.write(username);
hmac.end();
const password = hmac.read();
return {
username: username,
2021-06-24 10:09:10 +02:00
password: password,
};
}
//disconnect user
private disConnectedUser(user: User, group: Group) {
// Most of the time, sending a disconnect event to one of the players is enough (the player will close the connection
// which will be shut for the other player).
// However! In the rare case where the WebRTC connection is not yet established, if we close the connection on one of the player,
// the other player will try connecting until a timeout happens (during this time, the connection icon will be displayed for nothing).
// So we also send the disconnect event to the other player.
for (const otherUser of group.getUsers()) {
if (user === otherUser) {
continue;
}
const webrtcDisconnectMessage1 = new WebRtcDisconnectMessage();
webrtcDisconnectMessage1.setUserid(user.id);
const serverToClientMessage1 = new ServerToClientMessage();
serverToClientMessage1.setWebrtcdisconnectmessage(webrtcDisconnectMessage1);
//if (!otherUser.socket.disconnecting) {
2021-06-24 10:09:10 +02:00
otherUser.socket.write(serverToClientMessage1);
//}
const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage();
webrtcDisconnectMessage2.setUserid(otherUser.id);
const serverToClientMessage2 = new ServerToClientMessage();
serverToClientMessage2.setWebrtcdisconnectmessage(webrtcDisconnectMessage2);
//if (!user.socket.disconnecting) {
2021-06-24 10:09:10 +02:00
user.socket.write(serverToClientMessage2);
//}
}
}
emitPlayGlobalMessage(room: GameRoom, playGlobalMessage: PlayGlobalMessage) {
2021-07-22 10:33:07 +02:00
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setPlayglobalmessage(playGlobalMessage);
2021-07-22 10:33:07 +02:00
for (const [id, user] of room.getUsers().entries()) {
user.socket.write(serverToClientMessage);
}
}
public getWorlds(): Map<string, PromiseLike<GameRoom>> {
return this.roomsPromises;
}
public handleQueryJitsiJwtMessage(user: User, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
2020-10-16 19:13:26 +02:00
const room = queryJitsiJwtMessage.getJitsiroom();
const tag = queryJitsiJwtMessage.getTag(); // FIXME: this is not secure. We should load the JSON for the current room and check rights associated to room instead.
2021-06-24 10:09:10 +02:00
if (SECRET_JITSI_KEY === "") {
throw new Error("You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.");
2020-10-16 19:13:26 +02:00
}
// Let's see if the current client has
const isAdmin = user.tags.includes(tag);
2020-10-16 19:13:26 +02:00
2021-06-24 10:09:10 +02:00
const jwt = Jwt.sign(
{
aud: "jitsi",
iss: JITSI_ISS,
sub: JITSI_URL,
room: room,
moderator: isAdmin,
},
SECRET_JITSI_KEY,
{
expiresIn: "1d",
algorithm: "HS256",
header: {
alg: "HS256",
typ: "JWT",
},
}
);
2020-10-16 19:13:26 +02:00
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
sendJitsiJwtMessage.setJitsiroom(room);
sendJitsiJwtMessage.setJwt(jwt);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendjitsijwtmessage(sendJitsiJwtMessage);
user.socket.write(serverToClientMessage);
2020-10-16 19:13:26 +02:00
}
2021-06-24 10:09:10 +02:00
public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage) {
2021-01-17 03:07:46 +01:00
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(sendUserMessageToSend.getMessage());
sendUserMessage.setType(sendUserMessageToSend.getType());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
user.socket.write(serverToClientMessage);
}
2021-06-24 10:09:10 +02:00
public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage) {
2021-01-17 03:07:46 +01:00
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(banUserMessageToSend.getMessage());
banUserMessage.setType(banUserMessageToSend.getType());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(banUserMessage);
user.socket.write(serverToClientMessage);
setTimeout(() => {
// Let's leave the room now.
room.leave(user);
// Let's close the connection when the user is banned.
user.socket.end();
}, 10000);
}
public async addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): Promise<void> {
const room = await this.roomsPromises.get(roomId);
if (!room) {
throw new Error("In addZoneListener, could not find room with id '" + roomId + "'");
}
const things = room.addZoneListener(call, x, y);
const batchMessage = new BatchToPusherMessage();
for (const thing of things) {
if (thing instanceof User) {
const userJoinedMessage = new UserJoinedZoneMessage();
userJoinedMessage.setUserid(thing.id);
userJoinedMessage.setUseruuid(thing.uuid);
userJoinedMessage.setName(thing.name);
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
if (thing.visitCardUrl) {
userJoinedMessage.setVisitcardurl(thing.visitCardUrl);
}
2021-04-02 21:21:11 +02:00
userJoinedMessage.setCompanion(thing.companion);
const subMessage = new SubToPusherMessage();
subMessage.setUserjoinedzonemessage(userJoinedMessage);
batchMessage.addPayload(subMessage);
} else if (thing instanceof Group) {
const groupUpdateMessage = new GroupUpdateZoneMessage();
groupUpdateMessage.setGroupid(thing.getId());
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
const subMessage = new SubToPusherMessage();
subMessage.setGroupupdatezonemessage(groupUpdateMessage);
batchMessage.addPayload(subMessage);
} else {
console.error("Unexpected type for Movable returned by setViewport");
}
}
call.write(batchMessage);
}
async removeZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): Promise<void> {
const room = await this.roomsPromises.get(roomId);
if (!room) {
throw new Error("In removeZoneListener, could not find room with id '" + roomId + "'");
}
room.removeZoneListener(call, x, y);
}
async addRoomListener(call: RoomSocket, roomId: string) {
const room = await this.getOrCreateRoom(roomId);
if (!room) {
throw new Error("In addRoomListener, could not find room with id '" + roomId + "'");
}
room.addRoomListener(call);
const batchMessage = new BatchToPusherRoomMessage();
call.write(batchMessage);
}
async removeRoomListener(call: RoomSocket, roomId: string) {
const room = await this.roomsPromises.get(roomId);
if (!room) {
throw new Error("In removeRoomListener, could not find room with id '" + roomId + "'");
}
room.removeRoomListener(call);
}
public async handleJoinAdminRoom(admin: Admin, roomId: string): Promise<GameRoom> {
const room = await socketManager.getOrCreateRoom(roomId);
room.adminJoin(admin);
return room;
}
2021-06-24 10:09:10 +02:00
public leaveAdminRoom(room: GameRoom, admin: Admin) {
room.adminLeave(admin);
if (room.isEmpty()) {
this.roomsPromises.delete(room.roomUrl);
gaugeManager.decNbRoomGauge();
debug('Room is empty. Deleting room "%s"', room.roomUrl);
}
}
public async sendAdminMessage(roomId: string, recipientUuid: string, message: string): Promise<void> {
const room = await this.roomsPromises.get(roomId);
2020-12-11 12:23:50 +01:00
if (!room) {
2021-06-24 10:09:10 +02:00
console.error(
"In sendAdminMessage, could not find room with id '" +
roomId +
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
);
2020-12-11 12:23:50 +01:00
return;
}
const recipients = room.getUsersByUuid(recipientUuid);
if (recipients.length === 0) {
2021-06-24 10:09:10 +02:00
console.error(
"In sendAdminMessage, could not find user with id '" +
recipientUuid +
"'. Maybe the user left the room a few milliseconds ago and there was a race condition?"
);
2020-12-11 12:23:50 +01:00
return;
}
for (const recipient of recipients) {
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message);
sendUserMessage.setType("ban"); //todo: is the type correct?
2020-12-11 12:23:50 +01:00
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
2020-12-11 12:23:50 +01:00
recipient.socket.write(serverToClientMessage);
}
2020-12-11 12:23:50 +01:00
}
public async banUser(roomId: string, recipientUuid: string, message: string): Promise<void> {
const room = await this.roomsPromises.get(roomId);
2020-12-11 12:23:50 +01:00
if (!room) {
2021-06-24 10:09:10 +02:00
console.error(
"In banUser, could not find room with id '" +
roomId +
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
);
2020-12-11 12:23:50 +01:00
return;
}
const recipients = room.getUsersByUuid(recipientUuid);
if (recipients.length === 0) {
2021-06-24 10:09:10 +02:00
console.error(
"In banUser, could not find user with id '" +
recipientUuid +
"'. Maybe the user left the room a few milliseconds ago and there was a race condition?"
);
2020-12-11 12:23:50 +01:00
return;
}
for (const recipient of recipients) {
// Let's leave the room now.
room.leave(recipient);
2020-12-11 12:23:50 +01:00
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(message);
banUserMessage.setType("banned");
2020-12-11 12:23:50 +01:00
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setBanusermessage(banUserMessage);
2020-12-11 12:23:50 +01:00
// Let's close the connection when the user is banned.
recipient.socket.write(serverToClientMessage);
recipient.socket.end();
}
2020-12-11 12:23:50 +01:00
}
async sendAdminRoomMessage(roomId: string, message: string) {
const room = await this.roomsPromises.get(roomId);
if (!room) {
//todo: this should cause the http call to return a 500
2021-06-24 10:09:10 +02:00
console.error(
"In sendAdminRoomMessage, could not find room with id '" +
roomId +
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
);
return;
}
room.getUsers().forEach((recipient) => {
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message);
2021-06-24 10:09:10 +02:00
sendUserMessage.setType("message");
const clientMessage = new ServerToClientMessage();
clientMessage.setSendusermessage(sendUserMessage);
recipient.socket.write(clientMessage);
});
}
async dispatchWorldFullWarning(roomId: string): Promise<void> {
const room = await this.roomsPromises.get(roomId);
if (!room) {
//todo: this should cause the http call to return a 500
2021-06-24 10:09:10 +02:00
console.error(
Squashed commit of the following: commit 41748a403686cffd5008be966f7cb04e6ca3e45a Merge: 3b1d4d63 4991a70b Author: grégoire parant <g.parant@thecodingmachine.com> Date: Mon Aug 2 21:38:37 2021 +0200 Merge pull request #1327 from thecodingmachine/hotFixErrorCardBack Fix error generated commit 4991a70bba40121b14650bc0baba3552cca1a09e Author: Gregoire Parant <g.parant@thecodingmachine.com> Date: Mon Aug 2 21:34:03 2021 +0200 Fix error generated Don't generate error if file is Invalid commit 3b1d4d630cc1c7ce21ac50e169463aeb7d5d3811 Merge: f52b4598 02e5860e Author: grégoire parant <g.parant@thecodingmachine.com> Date: Mon Aug 2 21:03:18 2021 +0200 Merge pull request #1326 from thecodingmachine/HotFixCreateMapFeature Hot fix create map feature commit 02e5860e43eba0283f7c055d12bbf2e916e10b9a Author: Gregoire Parant <g.parant@thecodingmachine.com> Date: Mon Aug 2 20:59:13 2021 +0200 HotFix redirect on production domain of WorkAdventure - Update domain `ADMIN_URL` by `workadventu.re` commit f52b4598723331ff68d7526cdc32f1eff15a0d8a Merge: 3d657b4a 3ab069d6 Author: grégoire parant <g.parant@thecodingmachine.com> Date: Mon Aug 2 11:23:16 2021 +0200 Merge pull request #1324 from thecodingmachine/develop Release v1.4.11 commit 3ab069d650d94aecd42bebea8b27bf8ac93ed81b Merge: 2b748138 9d4ffe54 Author: Kharhamel <Kharhamel@users.noreply.github.com> Date: Fri Jul 30 15:51:07 2021 +0200 Merge pull request #1323 from thecodingmachine/openIDPoc FIX: bomp the node version of pusher commit 9d4ffe542c9c0a094a78e1d6c8c8b11641052a10 Author: kharhamel <oognic@gmail.com> Date: Fri Jul 30 15:50:30 2021 +0200 FIX: bomp the node version of pusher commit 2b7481383f6f3f085c4c145394b3d63b75eed239 Merge: 74975ac9 9c803a69 Author: Kharhamel <Kharhamel@users.noreply.github.com> Date: Fri Jul 30 15:48:56 2021 +0200 Merge pull request #1251 from thecodingmachine/openIDPoc POC for the openID connect commit 9c803a69ffb8a1a06a1cdd4d86f15d2a7cdfcd08 Author: kharhamel <oognic@gmail.com> Date: Tue Jul 27 16:37:01 2021 +0200 FEATURE: users can now login via an openID client commit 74975ac9d81f224d30fe3b3eec180b3ef895cbeb Merge: 315fe7ca ebdcf880 Author: Kharhamel <Kharhamel@users.noreply.github.com> Date: Fri Jul 30 14:54:33 2021 +0200 Merge pull request #1322 from thecodingmachine/improveCapacityWarning FEATURE: improved the room capacity warning visuals commit ebdcf8804d7ab72a51bac10eeb476467caa16f43 Author: kharhamel <oognic@gmail.com> Date: Fri Jul 30 14:08:27 2021 +0200 added admin link to the warning container commit 41ac51f2918b743da445a2ba3b89bd8f66e08e06 Author: kharhamel <oognic@gmail.com> Date: Thu Jul 29 18:02:36 2021 +0200 FEATURE: improved the room capacity warning visuals commit 315fe7ca82b3674d07136d7a96233d827804d177 Author: David Négrier <d.negrier@thecodingmachine.com> Date: Thu Jul 29 17:49:51 2021 +0200 Adding a "font-family" property for text objects. (#1311) - Tiled displays your system fonts. - Computers have different sets of fonts. Therefore, browsers never rely on system fonts - Which means if you select a font in Tiled, it is quite unlikely it will render properly in WorkAdventure To circumvent this problem, in your text object in Tiled, you can now add an additional property: `font-family`. The `font-family` property can contain any "web-font" that can be loaded by your browser. This allows us to use the "Press Start 2P" 8px font in text objects, which renders way better than the default "Sans serif" font of your browser. commit 7ffe564e8eddcfdc0ef8c20d09f43405934e83f9 Author: GRL78 <80678534+GRL78@users.noreply.github.com> Date: Thu Jul 29 17:42:16 2021 +0200 Graphic upgrade of the global message console (#1287) * Graphic upgrade of the global message console Fix: error if LoginScene doesn't exist * Rework graphic of global message console * Rework graphic of global message console * Remove console.log commit 2a1af2a131f72ad5a00b6f4a4990a12fcedb0342 Author: grégoire parant <g.parant@thecodingmachine.com> Date: Thu Jul 29 16:42:31 2021 +0200 PWA service workers (#1319) * PWA services worker - [x] Register service worker of PWA to install WorkAdventure application on desktop and mobile - [x] Create webpage specifique for PWA - [ ] Add register service to save and redirect on a card - [ ] Add possibilities to install PWA for one World (with register token if existing) * Finish PWA strategy to load last map visited * Fix feedback @Kharhamel * Fix feedback @Kharhamel
2021-08-02 22:06:24 +02:00
"In dispatchWorldFullWarning, could not find room with id '" +
2021-06-24 10:09:10 +02:00
roomId +
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
);
return;
}
2021-06-24 10:09:10 +02:00
room.getUsers().forEach((recipient) => {
const worldFullMessage = new WorldFullWarningMessage();
const clientMessage = new ServerToClientMessage();
clientMessage.setWorldfullwarningmessage(worldFullMessage);
recipient.socket.write(clientMessage);
});
}
async dispatchRoomRefresh(roomId: string): Promise<void> {
const room = await this.roomsPromises.get(roomId);
if (!room) {
return;
}
2021-06-24 10:09:10 +02:00
const versionNumber = room.incrementVersion();
room.getUsers().forEach((recipient) => {
const worldFullMessage = new RefreshRoomMessage();
2021-06-24 10:09:10 +02:00
worldFullMessage.setRoomid(roomId);
worldFullMessage.setVersionnumber(versionNumber);
const clientMessage = new ServerToClientMessage();
clientMessage.setRefreshroommessage(worldFullMessage);
recipient.socket.write(clientMessage);
});
}
handleEmoteEventMessage(room: GameRoom, user: User, emotePromptMessage: EmotePromptMessage) {
const emoteEventMessage = new EmoteEventMessage();
emoteEventMessage.setEmote(emotePromptMessage.getEmote());
emoteEventMessage.setActoruserid(user.id);
room.emitEmoteEvent(user, emoteEventMessage);
}
}
2020-10-16 19:13:26 +02:00
export const socketManager = new SocketManager();