Migrating userId to "int32" to save some space and adding userMoves message in protobuf

This commit is contained in:
David Négrier 2020-09-18 13:57:38 +02:00
parent 4b55b54a07
commit e9ca8721a6
31 changed files with 295 additions and 445 deletions

View File

@ -6,7 +6,7 @@ import { uuid } from 'uuidv4';
export interface TokenInterface { export interface TokenInterface {
name: string, name: string,
userId: string userUuid: string
} }
export class AuthenticateController { export class AuthenticateController {
@ -28,12 +28,12 @@ export class AuthenticateController {
}); });
}*/ }*/
//TODO check user email for The Coding Machine game //TODO check user email for The Coding Machine game
const userId = uuid(); const userUuid = uuid();
const token = Jwt.sign({name: param.name, userId: userId} as TokenInterface, SECRET_KEY, {expiresIn: '24h'}); const token = Jwt.sign({name: param.name, userUuid: userUuid} as TokenInterface, SECRET_KEY, {expiresIn: '24h'});
return res.status(OK).send({ return res.status(OK).send({
token: token, token: token,
mapUrlStart: URL_ROOM_STARTED, mapUrlStart: URL_ROOM_STARTED,
userId: userId, userId: userUuid,
}); });
}); });
} }

View File

@ -20,13 +20,14 @@ import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMes
import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface"; import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface";
import {isItemEventMessageInterface} from "../Model/Websocket/ItemEventMessage"; import {isItemEventMessageInterface} from "../Model/Websocket/ItemEventMessage";
import {uuid} from 'uuidv4'; import {uuid} from 'uuidv4';
import {isUserMovesInterface} from "../Model/Websocket/UserMovesMessage";
import {isViewport} from "../Model/Websocket/ViewportMessage"; import {isViewport} from "../Model/Websocket/ViewportMessage";
import {GroupUpdateInterface} from "_Model/Websocket/GroupUpdateInterface"; import {GroupUpdateInterface} from "_Model/Websocket/GroupUpdateInterface";
import {Movable} from "../Model/Movable"; import {Movable} from "../Model/Movable";
import {SetPlayerDetailsMessage} from "../../../messages/generated/src/proto/messages_pb"; import {PositionMessage, SetPlayerDetailsMessage} from "../../../messages/generated/messages_pb";
import {UserMovesMessage} from "../../../messages/generated/messages_pb";
import Direction = PositionMessage.Direction;
enum SockerIoEvent { enum SocketIoEvent {
CONNECTION = "connection", CONNECTION = "connection",
DISCONNECT = "disconnect", DISCONNECT = "disconnect",
JOIN_ROOM = "join-room", // bi-directional JOIN_ROOM = "join-room", // bi-directional
@ -52,7 +53,7 @@ function emitInBatch(socket: ExSocketInterface, event: string | symbol, payload:
if (socket.batchTimeout === null) { if (socket.batchTimeout === null) {
socket.batchTimeout = setTimeout(() => { socket.batchTimeout = setTimeout(() => {
socket.emit(SockerIoEvent.BATCH, socket.batchedMessages); socket.emit(SocketIoEvent.BATCH, socket.batchedMessages);
socket.batchedMessages = []; socket.batchedMessages = [];
socket.batchTimeout = null; socket.batchTimeout = null;
}, 100); }, 100);
@ -62,9 +63,10 @@ function emitInBatch(socket: ExSocketInterface, event: string | symbol, payload:
export class IoSocketController { export class IoSocketController {
public readonly Io: socketIO.Server; public readonly Io: socketIO.Server;
private Worlds: Map<string, World> = new Map<string, World>(); private Worlds: Map<string, World> = new Map<string, World>();
private sockets: Map<string, ExSocketInterface> = new Map<string, ExSocketInterface>(); private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
private nbClientsGauge: Gauge<string>; private nbClientsGauge: Gauge<string>;
private nbClientsPerRoomGauge: Gauge<string>; private nbClientsPerRoomGauge: Gauge<string>;
private nextUserId: number = 1;
constructor(server: http.Server) { constructor(server: http.Server) {
this.Io = socketIO(server); this.Io = socketIO(server);
@ -90,7 +92,9 @@ export class IoSocketController {
if(socket.handshake.query.token === 'test'){ if(socket.handshake.query.token === 'test'){
if (ALLOW_ARTILLERY) { if (ALLOW_ARTILLERY) {
(socket as ExSocketInterface).token = socket.handshake.query.token; (socket as ExSocketInterface).token = socket.handshake.query.token;
(socket as ExSocketInterface).userId = uuid(); (socket as ExSocketInterface).userId = this.nextUserId;
(socket as ExSocketInterface).userUuid = uuid();
this.nextUserId++;
(socket as ExSocketInterface).isArtillery = true; (socket as ExSocketInterface).isArtillery = true;
console.log((socket as ExSocketInterface).userId); console.log((socket as ExSocketInterface).userId);
next(); next();
@ -116,7 +120,9 @@ export class IoSocketController {
} }
(socket as ExSocketInterface).token = socket.handshake.query.token; (socket as ExSocketInterface).token = socket.handshake.query.token;
(socket as ExSocketInterface).userId = tokenDecoded.userId; (socket as ExSocketInterface).userId = this.nextUserId;
(socket as ExSocketInterface).userUuid = tokenDecoded.userUuid;
this.nextUserId++;
next(); next();
}); });
}); });
@ -125,7 +131,7 @@ export class IoSocketController {
} }
private isValidToken(token: object): token is TokenInterface { private isValidToken(token: object): token is TokenInterface {
if (typeof((token as TokenInterface).userId) !== 'string') { if (typeof((token as TokenInterface).userUuid) !== 'string') {
return false; return false;
} }
if (typeof((token as TokenInterface).name) !== 'string') { if (typeof((token as TokenInterface).name) !== 'string') {
@ -151,7 +157,7 @@ export class IoSocketController {
} }
ioConnection() { ioConnection() {
this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => { this.Io.on(SocketIoEvent.CONNECTION, (socket: Socket) => {
const client : ExSocketInterface = socket as ExSocketInterface; const client : ExSocketInterface = socket as ExSocketInterface;
client.batchedMessages = []; client.batchedMessages = [];
client.batchTimeout = null; client.batchTimeout = null;
@ -176,11 +182,11 @@ export class IoSocketController {
x: user x position on map x: user x position on map
y: user y position on map y: user y position on map
*/ */
socket.on(SockerIoEvent.JOIN_ROOM, (message: unknown, answerFn): void => { socket.on(SocketIoEvent.JOIN_ROOM, (message: unknown, answerFn): void => {
console.log(SockerIoEvent.JOIN_ROOM, message); console.log(SocketIoEvent.JOIN_ROOM, message);
try { try {
if (!isJoinRoomMessageInterface(message)) { if (!isJoinRoomMessageInterface(message)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid JOIN_ROOM message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid JOIN_ROOM message.'});
console.warn('Invalid JOIN_ROOM message received: ', message); console.warn('Invalid JOIN_ROOM message received: ', message);
return; return;
} }
@ -244,11 +250,11 @@ export class IoSocketController {
} }
}); });
socket.on(SockerIoEvent.SET_VIEWPORT, (message: unknown): void => { socket.on(SocketIoEvent.SET_VIEWPORT, (message: unknown): void => {
try { try {
//console.log('SET_VIEWPORT') //console.log('SET_VIEWPORT')
if (!isViewport(message)) { if (!isViewport(message)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_VIEWPORT message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_VIEWPORT message.'});
console.warn('Invalid SET_VIEWPORT message received: ', message); console.warn('Invalid SET_VIEWPORT message received: ', message);
return; return;
} }
@ -268,20 +274,47 @@ export class IoSocketController {
} }
}); });
socket.on(SockerIoEvent.USER_POSITION, (userMovesMessage: unknown): void => { socket.on(SocketIoEvent.USER_POSITION, (message: unknown): void => {
//console.log(SockerIoEvent.USER_POSITION, userMovesMessage); //console.log(SockerIoEvent.USER_POSITION, userMovesMessage);
try { try {
if (!isUserMovesInterface(userMovesMessage)) { const userMovesMessage = UserMovesMessage.deserializeBinary(new Uint8Array(message as ArrayBuffer));
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid USER_POSITION message.'}); const userMoves = userMovesMessage.toObject();
console.warn('Invalid USER_POSITION message received: ', userMovesMessage);
return; const position = userMoves.position;
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');
}
let direction: string;
switch (position.direction) {
case Direction.UP:
direction = 'up';
break;
case Direction.DOWN:
direction = 'down';
break;
case Direction.LEFT:
direction = 'left';
break;
case Direction.RIGHT:
direction = 'right';
break;
} }
const Client = (socket as ExSocketInterface); const Client = (socket as ExSocketInterface);
// sending to all clients in room except sender // sending to all clients in room except sender
Client.position = userMovesMessage.position; Client.position = {
Client.viewport = userMovesMessage.viewport; x: position.x,
y: position.y,
direction,
moving: position.moving,
};
Client.viewport = viewport;
// update position in the world // update position in the world
const world = this.Worlds.get(Client.roomId); const world = this.Worlds.get(Client.roomId);
@ -297,15 +330,15 @@ export class IoSocketController {
} }
}); });
socket.on(SockerIoEvent.WEBRTC_SIGNAL, (data: unknown) => { socket.on(SocketIoEvent.WEBRTC_SIGNAL, (data: unknown) => {
this.emitVideo((socket as ExSocketInterface), data); this.emitVideo((socket as ExSocketInterface), data);
}); });
socket.on(SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => { socket.on(SocketIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => {
this.emitScreenSharing((socket as ExSocketInterface), data); this.emitScreenSharing((socket as ExSocketInterface), data);
}); });
socket.on(SockerIoEvent.DISCONNECT, () => { socket.on(SocketIoEvent.DISCONNECT, () => {
const Client = (socket as ExSocketInterface); const Client = (socket as ExSocketInterface);
try { try {
//leave room //leave room
@ -335,16 +368,16 @@ export class IoSocketController {
}); });
// Let's send the user id to the user // Let's send the user id to the user
socket.on(SockerIoEvent.SET_PLAYER_DETAILS, (message: any, answerFn) => { socket.on(SocketIoEvent.SET_PLAYER_DETAILS, (message: any, answerFn) => {
console.log(SockerIoEvent.SET_PLAYER_DETAILS, message); console.log(SocketIoEvent.SET_PLAYER_DETAILS, message);
const playerDetailsMessage = SetPlayerDetailsMessage.deserializeBinary(new Uint8Array(message)); const playerDetailsMessage = SetPlayerDetailsMessage.deserializeBinary(new Uint8Array(message));
const playerDetails = { const playerDetails = {
name: playerDetailsMessage.getName(), name: playerDetailsMessage.getName(),
characterLayers: playerDetailsMessage.getCharacterlayersList() characterLayers: playerDetailsMessage.getCharacterlayersList()
}; };
console.log(SockerIoEvent.SET_PLAYER_DETAILS, playerDetails); console.log(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
if (!isSetPlayerDetailsMessage(playerDetails)) { if (!isSetPlayerDetailsMessage(playerDetails)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_PLAYER_DETAILS message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_PLAYER_DETAILS message.'});
console.warn('Invalid SET_PLAYER_DETAILS message received: ', playerDetails); console.warn('Invalid SET_PLAYER_DETAILS message received: ', playerDetails);
return; return;
} }
@ -357,10 +390,10 @@ export class IoSocketController {
} }
}); });
socket.on(SockerIoEvent.SET_SILENT, (silent: unknown) => { socket.on(SocketIoEvent.SET_SILENT, (silent: unknown) => {
console.log(SockerIoEvent.SET_SILENT, silent); console.log(SocketIoEvent.SET_SILENT, silent);
if (typeof silent !== "boolean") { if (typeof silent !== "boolean") {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_SILENT message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_SILENT message.'});
console.warn('Invalid SET_SILENT message received: ', silent); console.warn('Invalid SET_SILENT message received: ', silent);
return; return;
} }
@ -381,16 +414,16 @@ export class IoSocketController {
} }
}); });
socket.on(SockerIoEvent.ITEM_EVENT, (itemEvent: unknown) => { socket.on(SocketIoEvent.ITEM_EVENT, (itemEvent: unknown) => {
if (!isItemEventMessageInterface(itemEvent)) { if (!isItemEventMessageInterface(itemEvent)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid ITEM_EVENT message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid ITEM_EVENT message.'});
console.warn('Invalid ITEM_EVENT message received: ', itemEvent); console.warn('Invalid ITEM_EVENT message received: ', itemEvent);
return; return;
} }
try { try {
const Client = (socket as ExSocketInterface); const Client = (socket as ExSocketInterface);
socket.to(Client.roomId).emit(SockerIoEvent.ITEM_EVENT, itemEvent); socket.to(Client.roomId).emit(SocketIoEvent.ITEM_EVENT, itemEvent);
const world = this.Worlds.get(Client.roomId); const world = this.Worlds.get(Client.roomId);
if (!world) { if (!world) {
@ -408,7 +441,7 @@ export class IoSocketController {
emitVideo(socket: ExSocketInterface, data: unknown){ emitVideo(socket: ExSocketInterface, data: unknown){
if (!isWebRtcSignalMessageInterface(data)) { if (!isWebRtcSignalMessageInterface(data)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'});
console.warn('Invalid WEBRTC_SIGNAL message received: ', data); console.warn('Invalid WEBRTC_SIGNAL message received: ', data);
return; return;
} }
@ -418,7 +451,7 @@ export class IoSocketController {
console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition.");
return; return;
} }
return client.emit(SockerIoEvent.WEBRTC_SIGNAL, { return client.emit(SocketIoEvent.WEBRTC_SIGNAL, {
userId: socket.userId, userId: socket.userId,
signal: data.signal signal: data.signal
}); });
@ -426,7 +459,7 @@ export class IoSocketController {
emitScreenSharing(socket: ExSocketInterface, data: unknown){ emitScreenSharing(socket: ExSocketInterface, data: unknown){
if (!isWebRtcSignalMessageInterface(data)) { if (!isWebRtcSignalMessageInterface(data)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SCREEN_SHARING message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SCREEN_SHARING message.'});
console.warn('Invalid WEBRTC_SCREEN_SHARING message received: ', data); console.warn('Invalid WEBRTC_SCREEN_SHARING message received: ', data);
return; return;
} }
@ -436,13 +469,13 @@ export class IoSocketController {
console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.receiverId, " does not exist. This might be a race condition.");
return; return;
} }
return client.emit(SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, { return client.emit(SocketIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, {
userId: socket.userId, userId: socket.userId,
signal: data.signal signal: data.signal
}); });
} }
searchClientByIdOrFail(userId: string): ExSocketInterface { searchClientByIdOrFail(userId: number): ExSocketInterface {
const client: ExSocketInterface|undefined = this.sockets.get(userId); const client: ExSocketInterface|undefined = this.sockets.get(userId);
if (client === undefined) { if (client === undefined) {
throw new Error("Could not find user with id " + userId); throw new Error("Could not find user with id " + userId);
@ -481,9 +514,9 @@ export class IoSocketController {
//check and create new world for a room //check and create new world for a room
let world = this.Worlds.get(roomId) let world = this.Worlds.get(roomId)
if(world === undefined){ if(world === undefined){
world = new World((user1: string, group: Group) => { world = new World((user1: number, group: Group) => {
this.connectedUser(user1, group); this.connectedUser(user1, group);
}, (user1: string, group: Group) => { }, (user1: number, group: Group) => {
this.disConnectedUser(user1, group); this.disConnectedUser(user1, group);
}, MINIMUM_DISTANCE, GROUP_RADIUS, (thing: Movable, listener: User) => { }, MINIMUM_DISTANCE, GROUP_RADIUS, (thing: Movable, listener: User) => {
const clientListener = this.searchClientByIdOrFail(listener.id); const clientListener = this.searchClientByIdOrFail(listener.id);
@ -491,9 +524,9 @@ export class IoSocketController {
const clientUser = this.searchClientByIdOrFail(thing.id); const clientUser = this.searchClientByIdOrFail(thing.id);
const messageUserJoined = new MessageUserJoined(clientUser.userId, clientUser.name, clientUser.characterLayers, clientUser.position); const messageUserJoined = new MessageUserJoined(clientUser.userId, clientUser.name, clientUser.characterLayers, clientUser.position);
clientListener.emit(SockerIoEvent.JOIN_ROOM, messageUserJoined); clientListener.emit(SocketIoEvent.JOIN_ROOM, messageUserJoined);
} else if (thing instanceof Group) { } else if (thing instanceof Group) {
clientListener.emit(SockerIoEvent.GROUP_CREATE_UPDATE, { clientListener.emit(SocketIoEvent.GROUP_CREATE_UPDATE, {
position: thing.getPosition(), position: thing.getPosition(),
groupId: thing.getId() groupId: thing.getId()
} as GroupUpdateInterface); } as GroupUpdateInterface);
@ -505,10 +538,10 @@ export class IoSocketController {
if (thing instanceof User) { if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id); const clientUser = this.searchClientByIdOrFail(thing.id);
clientListener.emitInBatch(SockerIoEvent.USER_MOVED, new MessageUserMoved(clientUser.userId, clientUser.position)); clientListener.emitInBatch(SocketIoEvent.USER_MOVED, new MessageUserMoved(clientUser.userId, clientUser.position));
//console.log("Sending USER_MOVED event"); //console.log("Sending USER_MOVED event");
} else if (thing instanceof Group) { } else if (thing instanceof Group) {
clientListener.emit(SockerIoEvent.GROUP_CREATE_UPDATE, { clientListener.emit(SocketIoEvent.GROUP_CREATE_UPDATE, {
position: thing.getPosition(), position: thing.getPosition(),
groupId: thing.getId() groupId: thing.getId()
} as GroupUpdateInterface); } as GroupUpdateInterface);
@ -519,10 +552,10 @@ export class IoSocketController {
const clientListener = this.searchClientByIdOrFail(listener.id); const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) { if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id); const clientUser = this.searchClientByIdOrFail(thing.id);
clientListener.emit(SockerIoEvent.USER_LEFT, clientUser.userId); clientListener.emit(SocketIoEvent.USER_LEFT, clientUser.userId);
//console.log("Sending USER_LEFT event"); //console.log("Sending USER_LEFT event");
} else if (thing instanceof Group) { } else if (thing instanceof Group) {
clientListener.emit(SockerIoEvent.GROUP_DELETE, thing.getId()); clientListener.emit(SocketIoEvent.GROUP_DELETE, thing.getId());
} else { } else {
console.error('Unexpected type for Movable.'); console.error('Unexpected type for Movable.');
} }
@ -533,7 +566,7 @@ export class IoSocketController {
// Dispatch groups position to newly connected user // Dispatch groups position to newly connected user
world.getGroups().forEach((group: Group) => { world.getGroups().forEach((group: Group) => {
Client.emit(SockerIoEvent.GROUP_CREATE_UPDATE, { Client.emit(SocketIoEvent.GROUP_CREATE_UPDATE, {
position: group.getPosition(), position: group.getPosition(),
groupId: group.getId() groupId: group.getId()
} as GroupUpdateInterface); } as GroupUpdateInterface);
@ -578,7 +611,7 @@ export class IoSocketController {
return tabs; return tabs;
}, []); }, []);
client.emit(SockerIoEvent.WEBRTC_START, {clients: peerClients, roomId: roomId}); client.emit(SocketIoEvent.WEBRTC_START, {clients: peerClients, roomId: roomId});
}); });
} }
@ -600,19 +633,19 @@ export class IoSocketController {
**/ **/
//connected user //connected user
connectedUser(userId: string, group: Group) { connectedUser(userId: number, group: Group) {
/*let Client = this.sockets.get(userId); /*let Client = this.sockets.get(userId);
if (Client === undefined) { if (Client === undefined) {
return; return;
}*/ }*/
const Client = this.searchClientByIdOrFail(userId); const Client = this.searchClientByIdOrFail(userId);
this.joinWebRtcRoom(Client, group.getId()); this.joinWebRtcRoom(Client, "webrtcroom"+group.getId());
} }
//disconnect user //disconnect user
disConnectedUser(userId: string, group: Group) { disConnectedUser(userId: number, group: Group) {
const Client = this.searchClientByIdOrFail(userId); const Client = this.searchClientByIdOrFail(userId);
Client.to(group.getId()).emit(SockerIoEvent.WEBRTC_DISCONNECT, { Client.to("webrtcroom"+group.getId()).emit(SocketIoEvent.WEBRTC_DISCONNECT, {
userId: userId userId: userId
}); });
@ -622,7 +655,7 @@ export class IoSocketController {
// the other player will try connecting until a timeout happens (during this time, the connection icon will be displayed for nothing). // 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. // So we also send the disconnect event to the other player.
for (const user of group.getUsers()) { for (const user of group.getUsers()) {
Client.emit(SockerIoEvent.WEBRTC_DISCONNECT, { Client.emit(SocketIoEvent.WEBRTC_DISCONNECT, {
userId: user.id userId: user.id
}); });
} }

View File

@ -7,7 +7,9 @@ import {Movable} from "_Model/Movable";
export class Group implements Movable { export class Group implements Movable {
static readonly MAX_PER_GROUP = 4; static readonly MAX_PER_GROUP = 4;
private id: string; private static nextId: number = 1;
private id: number;
private users: Set<User>; private users: Set<User>;
private connectCallback: ConnectCallback; private connectCallback: ConnectCallback;
private disconnectCallback: DisconnectCallback; private disconnectCallback: DisconnectCallback;
@ -17,7 +19,8 @@ export class Group implements Movable {
this.users = new Set<User>(); this.users = new Set<User>();
this.connectCallback = connectCallback; this.connectCallback = connectCallback;
this.disconnectCallback = disconnectCallback; this.disconnectCallback = disconnectCallback;
this.id = uuid(); this.id = Group.nextId;
Group.nextId++;
users.forEach((user: User) => { users.forEach((user: User) => {
this.join(user); this.join(user);
@ -28,7 +31,7 @@ export class Group implements Movable {
return Array.from(this.users.values()); return Array.from(this.users.values());
} }
getId() : string{ getId() : number {
return this.id; return this.id;
} }

View File

@ -9,7 +9,7 @@ export class User implements Movable {
public group?: Group; public group?: Group;
public constructor( public constructor(
public id: string, public id: number,
public position: PointInterface, public position: PointInterface,
public silent: boolean, public silent: boolean,

View File

@ -8,7 +8,8 @@ export interface ExSocketInterface extends Socket, Identificable {
token: string; token: string;
roomId: string; roomId: string;
webRtcRoomId: string; webRtcRoomId: string;
userId: string; userId: number; // A temporary (autoincremented) identifier for this user
userUuid: string; // A unique identifier for this user
name: string; name: string;
characterLayers: string[]; characterLayers: string[];
position: PointInterface; position: PointInterface;

View File

@ -2,5 +2,5 @@ import {PositionInterface} from "_Model/PositionInterface";
export interface GroupUpdateInterface { export interface GroupUpdateInterface {
position: PositionInterface, position: PositionInterface,
groupId: string, groupId: number,
} }

View File

@ -1,3 +1,3 @@
export interface Identificable { export interface Identificable {
userId: string; userId: number;
} }

View File

@ -1,6 +1,6 @@
import {PointInterface} from "_Model/Websocket/PointInterface"; import {PointInterface} from "_Model/Websocket/PointInterface";
export class MessageUserJoined { export class MessageUserJoined {
constructor(public userId: string, public name: string, public characterLayers: string[], public position: PointInterface) { constructor(public userId: number, public name: string, public characterLayers: string[], public position: PointInterface) {
} }
} }

View File

@ -1,6 +1,6 @@
import {PointInterface} from "./PointInterface"; import {PointInterface} from "./PointInterface";
export class MessageUserMoved { export class MessageUserMoved {
constructor(public userId: string, public position: PointInterface) { constructor(public userId: number, public position: PointInterface) {
} }
} }

View File

@ -6,6 +6,6 @@ export class Point implements PointInterface{
} }
export class MessageUserPosition { export class MessageUserPosition {
constructor(public userId: string, public name: string, public characterLayers: string[], public position: PointInterface) { constructor(public userId: number, public name: string, public characterLayers: string[], public position: PointInterface) {
} }
} }

View File

@ -1,5 +1,5 @@
export interface UserInGroupInterface { export interface UserInGroupInterface {
userId: string, userId: number,
name: string, name: string,
initiator: boolean initiator: boolean
} }

View File

@ -1,11 +0,0 @@
import * as tg from "generic-type-guard";
import {isPointInterface} from "./PointInterface";
import {isViewport} from "./ViewportMessage";
export const isUserMovesInterface =
new tg.IsInterface().withProperties({
position: isPointInterface,
viewport: isViewport,
}).get();
export type UserMovesInterface = tg.GuardedType<typeof isUserMovesInterface>;

View File

@ -7,12 +7,12 @@ export const isSignalData =
export const isWebRtcSignalMessageInterface = export const isWebRtcSignalMessageInterface =
new tg.IsInterface().withProperties({ new tg.IsInterface().withProperties({
receiverId: tg.isString, receiverId: tg.isNumber,
signal: isSignalData signal: isSignalData
}).get(); }).get();
export const isWebRtcScreenSharingStartMessageInterface = export const isWebRtcScreenSharingStartMessageInterface =
new tg.IsInterface().withProperties({ new tg.IsInterface().withProperties({
userId: tg.isString, userId: tg.isNumber,
roomId: tg.isString roomId: tg.isString
}).get(); }).get();
export type WebRtcSignalMessageInterface = tg.GuardedType<typeof isWebRtcSignalMessageInterface>; export type WebRtcSignalMessageInterface = tg.GuardedType<typeof isWebRtcSignalMessageInterface>;

View File

@ -11,15 +11,15 @@ import {PositionNotifier} from "./PositionNotifier";
import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
import {Movable} from "_Model/Movable"; import {Movable} from "_Model/Movable";
export type ConnectCallback = (user: string, group: Group) => void; export type ConnectCallback = (user: number, group: Group) => void;
export type DisconnectCallback = (user: string, group: Group) => void; export type DisconnectCallback = (user: number, group: Group) => void;
export class World { export class World {
private readonly minDistance: number; private readonly minDistance: number;
private readonly groupRadius: number; private readonly groupRadius: number;
// Users, sorted by ID // Users, sorted by ID
private readonly users: Map<string, User>; private readonly users: Map<number, User>;
private readonly groups: Set<Group>; private readonly groups: Set<Group>;
private readonly connectCallback: ConnectCallback; private readonly connectCallback: ConnectCallback;
@ -37,7 +37,7 @@ export class World {
onMoves: MovesCallback, onMoves: MovesCallback,
onLeaves: LeavesCallback) onLeaves: LeavesCallback)
{ {
this.users = new Map<string, User>(); this.users = new Map<number, User>();
this.groups = new Set<Group>(); this.groups = new Set<Group>();
this.connectCallback = connectCallback; this.connectCallback = connectCallback;
this.disconnectCallback = disconnectCallback; this.disconnectCallback = disconnectCallback;
@ -51,7 +51,7 @@ export class World {
return Array.from(this.groups.values()); return Array.from(this.groups.values());
} }
public getUsers(): Map<string, User> { public getUsers(): Map<number, User> {
return this.users; return this.users;
} }

View File

@ -32,14 +32,14 @@ describe("PositionNotifier", () => {
leaveTriggered = true; leaveTriggered = true;
}); });
const user1 = new User("1", { const user1 = new User(1, {
x: 500, x: 500,
y: 500, y: 500,
moving: false, moving: false,
direction: 'down' direction: 'down'
}, false); }, false);
const user2 = new User("2", { const user2 = new User(2, {
x: -9999, x: -9999,
y: -9999, y: -9999,
moving: false, moving: false,
@ -110,14 +110,14 @@ describe("PositionNotifier", () => {
leaveTriggered = true; leaveTriggered = true;
}); });
const user1 = new User("1", { const user1 = new User(1, {
x: 500, x: 500,
y: 500, y: 500,
moving: false, moving: false,
direction: 'down' direction: 'down'
}, false); }, false);
const user2 = new User("2", { const user2 = new User(2, {
x: -9999, x: -9999,
y: -9999, y: -9999,
moving: false, moving: false,

View File

@ -6,55 +6,55 @@ import { Group } from "../src/Model/Group";
describe("World", () => { describe("World", () => {
it("should connect user1 and user2", () => { it("should connect user1 and user2", () => {
let connectCalledNumber: number = 0; let connectCalledNumber: number = 0;
const connect: ConnectCallback = (user: string, group: Group): void => { const connect: ConnectCallback = (user: number, group: Group): void => {
connectCalledNumber++; connectCalledNumber++;
} }
const disconnect: DisconnectCallback = (user: string, group: Group): void => { const disconnect: DisconnectCallback = (user: number, group: Group): void => {
} }
const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {});
world.join({ userId: "foo" }, new Point(100, 100)); world.join({ userId: 1 }, new Point(100, 100));
world.join({ userId: "bar" }, new Point(500, 100)); world.join({ userId: 2 }, new Point(500, 100));
world.updatePosition({ userId: "bar" }, new Point(261, 100)); world.updatePosition({ userId: 2 }, new Point(261, 100));
expect(connectCalledNumber).toBe(0); expect(connectCalledNumber).toBe(0);
world.updatePosition({ userId: "bar" }, new Point(101, 100)); world.updatePosition({ userId: 2 }, new Point(101, 100));
expect(connectCalledNumber).toBe(2); expect(connectCalledNumber).toBe(2);
world.updatePosition({ userId: "bar" }, new Point(102, 100)); world.updatePosition({ userId: 2 }, new Point(102, 100));
expect(connectCalledNumber).toBe(2); expect(connectCalledNumber).toBe(2);
}); });
it("should connect 3 users", () => { it("should connect 3 users", () => {
let connectCalled: boolean = false; let connectCalled: boolean = false;
const connect: ConnectCallback = (user: string, group: Group): void => { const connect: ConnectCallback = (user: number, group: Group): void => {
connectCalled = true; connectCalled = true;
} }
const disconnect: DisconnectCallback = (user: string, group: Group): void => { const disconnect: DisconnectCallback = (user: number, group: Group): void => {
} }
const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {});
world.join({ userId: "foo" }, new Point(100, 100)); world.join({ userId: 1 }, new Point(100, 100));
world.join({ userId: "bar" }, new Point(200, 100)); world.join({ userId: 2 }, new Point(200, 100));
expect(connectCalled).toBe(true); expect(connectCalled).toBe(true);
connectCalled = false; connectCalled = false;
// baz joins at the outer limit of the group // baz joins at the outer limit of the group
world.join({ userId: "baz" }, new Point(311, 100)); world.join({ userId: 3 }, new Point(311, 100));
expect(connectCalled).toBe(false); expect(connectCalled).toBe(false);
world.updatePosition({ userId: "baz" }, new Point(309, 100)); world.updatePosition({ userId: 3 }, new Point(309, 100));
expect(connectCalled).toBe(true); expect(connectCalled).toBe(true);
}); });
@ -62,27 +62,27 @@ describe("World", () => {
it("should disconnect user1 and user2", () => { it("should disconnect user1 and user2", () => {
let connectCalled: boolean = false; let connectCalled: boolean = false;
let disconnectCallNumber: number = 0; let disconnectCallNumber: number = 0;
const connect: ConnectCallback = (user: string, group: Group): void => { const connect: ConnectCallback = (user: number, group: Group): void => {
connectCalled = true; connectCalled = true;
} }
const disconnect: DisconnectCallback = (user: string, group: Group): void => { const disconnect: DisconnectCallback = (user: number, group: Group): void => {
disconnectCallNumber++; disconnectCallNumber++;
} }
const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {});
world.join({ userId: "foo" }, new Point(100, 100)); world.join({ userId: 1 }, new Point(100, 100));
world.join({ userId: "bar" }, new Point(259, 100)); world.join({ userId: 2 }, new Point(259, 100));
expect(connectCalled).toBe(true); expect(connectCalled).toBe(true);
expect(disconnectCallNumber).toBe(0); expect(disconnectCallNumber).toBe(0);
world.updatePosition({ userId: "bar" }, new Point(100+160+160+1, 100)); world.updatePosition({ userId: 2 }, new Point(100+160+160+1, 100));
expect(disconnectCallNumber).toBe(2); expect(disconnectCallNumber).toBe(2);
world.updatePosition({ userId: "bar" }, new Point(262, 100)); world.updatePosition({ userId: 2 }, new Point(262, 100));
expect(disconnectCallNumber).toBe(2); expect(disconnectCallNumber).toBe(2);
}); });

View File

@ -67,7 +67,7 @@ services:
- "traefik.http.routers.maps-ssl.service=maps" - "traefik.http.routers.maps-ssl.service=maps"
back: back:
image: thecodingmachine/workadventure-back-base:latest image: thecodingmachine/nodejs:12
command: yarn dev command: yarn dev
#command: yarn run profile #command: yarn run profile
environment: environment:
@ -103,3 +103,11 @@ services:
- "traefik.http.routers.website-ssl.entryPoints=websecure" - "traefik.http.routers.website-ssl.entryPoints=websecure"
- "traefik.http.routers.website-ssl.tls=true" - "traefik.http.routers.website-ssl.tls=true"
- "traefik.http.routers.website-ssl.service=website" - "traefik.http.routers.website-ssl.service=website"
messages:
image: thecodingmachine/workadventure-back-base:latest
environment:
STARTUP_COMMAND_1: yarn install
STARTUP_COMMAND_2: yarn run proto:watch
volumes:
- ./messages:/usr/src/app

View File

@ -1,13 +1,19 @@
import Axios from "axios"; import Axios from "axios";
import {API_URL} from "./Enum/EnvironmentVariable"; import {API_URL} from "./Enum/EnvironmentVariable";
import {MessageUI} from "./Logger/MessageUI"; import {MessageUI} from "./Logger/MessageUI";
import {SetPlayerDetailsMessage} from "../../messages/generated/src/proto/messages_pb" import {
PositionMessage,
SetPlayerDetailsMessage,
UserMovesMessage,
ViewportMessage
} from "../../messages/generated/messages_pb"
const SocketIo = require('socket.io-client'); const SocketIo = require('socket.io-client');
import Socket = SocketIOClient.Socket; import Socket = SocketIOClient.Socket;
import {PlayerAnimationNames} from "./Phaser/Player/Animation"; import {PlayerAnimationNames} from "./Phaser/Player/Animation";
import {UserSimplePeerInterface} from "./WebRtc/SimplePeer"; import {UserSimplePeerInterface} from "./WebRtc/SimplePeer";
import {SignalData} from "simple-peer"; import {SignalData} from "simple-peer";
import Direction = PositionMessage.Direction;
enum EventMessage{ enum EventMessage{
WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_SIGNAL = "webrtc-signal",
@ -46,19 +52,19 @@ export class Point implements PointInterface{
} }
export interface MessageUserPositionInterface { export interface MessageUserPositionInterface {
userId: string; userId: number;
name: string; name: string;
characterLayers: string[]; characterLayers: string[];
position: PointInterface; position: PointInterface;
} }
export interface MessageUserMovedInterface { export interface MessageUserMovedInterface {
userId: string; userId: number;
position: PointInterface; position: PointInterface;
} }
export interface MessageUserJoined { export interface MessageUserJoined {
userId: string; userId: number;
name: string; name: string;
characterLayers: string[]; characterLayers: string[];
position: PointInterface position: PointInterface
@ -80,16 +86,16 @@ export interface WebRtcStartMessageInterface {
} }
export interface WebRtcDisconnectMessageInterface { export interface WebRtcDisconnectMessageInterface {
userId: string userId: number
} }
export interface WebRtcSignalSentMessageInterface { export interface WebRtcSignalSentMessageInterface {
receiverId: string, receiverId: number,
signal: SignalData signal: SignalData
} }
export interface WebRtcSignalReceivedMessageInterface { export interface WebRtcSignalReceivedMessageInterface {
userId: string, userId: number,
signal: SignalData signal: SignalData
} }
@ -105,11 +111,6 @@ export interface ViewportInterface {
bottom: number, bottom: number,
} }
export interface UserMovesInterface {
position: PositionInterface,
viewport: ViewportInterface,
}
export interface BatchedMessageInterface { export interface BatchedMessageInterface {
event: string, event: string,
payload: unknown payload: unknown
@ -130,7 +131,7 @@ export interface RoomJoinedMessageInterface {
export class Connection implements Connection { export class Connection implements Connection {
private readonly socket: Socket; private readonly socket: Socket;
private userId: string|null = null; private userId: number|null = null;
private constructor(token: string) { private constructor(token: string) {
@ -173,7 +174,7 @@ export class Connection implements Connection {
const message = new SetPlayerDetailsMessage(); const message = new SetPlayerDetailsMessage();
message.setName(name); message.setName(name);
message.setCharacterlayersList(characterLayersSelected); message.setCharacterlayersList(characterLayersSelected);
connection.socket.emit(EventMessage.SET_PLAYER_DETAILS, message.serializeBinary().buffer, (id: string) => { connection.socket.emit(EventMessage.SET_PLAYER_DETAILS, message.serializeBinary().buffer, (id: number) => {
connection.userId = id; connection.userId = id;
}); });
@ -214,7 +215,40 @@ export class Connection implements Connection {
return; return;
} }
const point = new Point(x, y, direction, moving); const point = new Point(x, y, direction, moving);
this.socket.emit(EventMessage.USER_POSITION, { position: point, viewport } as UserMovesInterface); const positionMessage = new PositionMessage();
positionMessage.setX(Math.floor(x));
positionMessage.setY(Math.floor(y));
let directionEnum: PositionMessage.DirectionMap[keyof PositionMessage.DirectionMap];
switch (direction) {
case 'up':
directionEnum = Direction.UP;
break;
case 'down':
directionEnum = Direction.DOWN;
break;
case 'left':
directionEnum = Direction.LEFT;
break;
case 'right':
directionEnum = Direction.RIGHT;
break;
default:
throw new Error("Unexpected direction");
}
positionMessage.setDirection(directionEnum);
positionMessage.setMoving(moving);
const viewportMessage = new ViewportMessage();
viewportMessage.setLeft(Math.floor(viewport.left));
viewportMessage.setRight(Math.floor(viewport.right));
viewportMessage.setTop(Math.floor(viewport.top));
viewportMessage.setBottom(Math.floor(viewport.bottom));
const userMovesMessage = new UserMovesMessage();
userMovesMessage.setPosition(positionMessage);
userMovesMessage.setViewport(viewportMessage);
this.socket.emit(EventMessage.USER_POSITION, userMovesMessage.serializeBinary().buffer);
} }
public setSilent(silent: boolean): void { public setSilent(silent: boolean): void {
@ -233,7 +267,7 @@ export class Connection implements Connection {
this.socket.on(EventMessage.USER_MOVED, callback); this.socket.on(EventMessage.USER_MOVED, callback);
} }
public onUserLeft(callback: (userId: string) => void): void { public onUserLeft(callback: (userId: number) => void): void {
this.socket.on(EventMessage.USER_LEFT, callback); this.socket.on(EventMessage.USER_LEFT, callback);
} }
@ -249,14 +283,14 @@ export class Connection implements Connection {
this.socket.on(EventMessage.CONNECT_ERROR, callback) this.socket.on(EventMessage.CONNECT_ERROR, callback)
} }
public sendWebrtcSignal(signal: unknown, receiverId : string) { public sendWebrtcSignal(signal: unknown, receiverId: number) {
return this.socket.emit(EventMessage.WEBRTC_SIGNAL, { return this.socket.emit(EventMessage.WEBRTC_SIGNAL, {
receiverId: receiverId, receiverId: receiverId,
signal: signal signal: signal
} as WebRtcSignalSentMessageInterface); } as WebRtcSignalSentMessageInterface);
} }
public sendWebrtcScreenSharingSignal(signal: unknown, receiverId : string) { public sendWebrtcScreenSharingSignal(signal: unknown, receiverId: number) {
return this.socket.emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, { return this.socket.emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, {
receiverId: receiverId, receiverId: receiverId,
signal: signal signal: signal
@ -286,7 +320,7 @@ export class Connection implements Connection {
} }
public getUserId(): string|null { public getUserId(): number|null {
return this.userId; return this.userId;
} }

View File

@ -6,10 +6,10 @@ import {Character} from "../Entity/Character";
* Class representing the sprite of a remote player (a player that plays on another computer) * Class representing the sprite of a remote player (a player that plays on another computer)
*/ */
export class RemotePlayer extends Character { export class RemotePlayer extends Character {
userId: string; userId: number;
constructor( constructor(
userId: string, userId: number,
Scene: GameScene, Scene: GameScene,
x: number, x: number,
y: number, y: number,

View File

@ -1,7 +1,7 @@
import {PointInterface} from "../../Connection"; import {PointInterface} from "../../Connection";
export interface AddPlayerInterface { export interface AddPlayerInterface {
userId: string; userId: number;
name: string; name: string;
characterLayers: string[]; characterLayers: string[];
position: PointInterface; position: PointInterface;

View File

@ -63,7 +63,7 @@ interface AddPlayerEventInterface {
interface RemovePlayerEventInterface { interface RemovePlayerEventInterface {
type: 'RemovePlayerEvent' type: 'RemovePlayerEvent'
userId: string userId: number
} }
interface UserMovedEventInterface { interface UserMovedEventInterface {
@ -86,7 +86,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
Terrains : Array<Phaser.Tilemaps.Tileset>; Terrains : Array<Phaser.Tilemaps.Tileset>;
CurrentPlayer!: CurrentGamerInterface; CurrentPlayer!: CurrentGamerInterface;
MapPlayers!: Phaser.Physics.Arcade.Group; MapPlayers!: Phaser.Physics.Arcade.Group;
MapPlayersByKey : Map<string, RemotePlayer> = new Map<string, RemotePlayer>(); MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
Map!: Phaser.Tilemaps.Tilemap; Map!: Phaser.Tilemaps.Tilemap;
Layers!: Array<Phaser.Tilemaps.StaticTilemapLayer>; Layers!: Array<Phaser.Tilemaps.StaticTilemapLayer>;
Objects!: Array<Phaser.Physics.Arcade.Sprite>; Objects!: Array<Phaser.Physics.Arcade.Sprite>;
@ -217,7 +217,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
this.updatePlayerPosition(message); this.updatePlayerPosition(message);
}); });
connection.onUserLeft((userId: string) => { connection.onUserLeft((userId: number) => {
this.removePlayer(userId); this.removePlayer(userId);
}); });
@ -271,7 +271,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
self.presentationModeSprite.setVisible(true); self.presentationModeSprite.setVisible(true);
self.chatModeSprite.setVisible(true); self.chatModeSprite.setVisible(true);
}, },
onDisconnect(userId: string) { onDisconnect(userId: number) {
if (self.simplePeer.getNbConnections() === 0) { if (self.simplePeer.getNbConnections() === 0) {
self.presentationModeSprite.setVisible(false); self.presentationModeSprite.setVisible(false);
self.chatModeSprite.setVisible(false); self.chatModeSprite.setVisible(false);
@ -918,7 +918,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
// Let's move all users // Let's move all users
const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time); const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: string) => { updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: number) => {
const player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId); const player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
if (player === undefined) { if (player === undefined) {
throw new Error('Cannot find player with ID "' + userId +'"'); throw new Error('Cannot find player with ID "' + userId +'"');
@ -973,7 +973,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
player.destroy(); player.destroy();
this.MapPlayers.remove(player); this.MapPlayers.remove(player);
}); });
this.MapPlayersByKey = new Map<string, RemotePlayer>(); this.MapPlayersByKey = new Map<number, RemotePlayer>();
// load map // load map
usersPosition.forEach((userPosition : MessageUserPositionInterface) => { usersPosition.forEach((userPosition : MessageUserPositionInterface) => {
@ -1030,14 +1030,14 @@ export class GameScene extends Phaser.Scene implements CenterListener {
/** /**
* Called by the connexion when a player is removed from the map * Called by the connexion when a player is removed from the map
*/ */
public removePlayer(userId: string) { public removePlayer(userId: number) {
this.pendingEvents.enqueue({ this.pendingEvents.enqueue({
type: "RemovePlayerEvent", type: "RemovePlayerEvent",
userId userId
}); });
} }
private doRemovePlayer(userId: string) { private doRemovePlayer(userId: number) {
const player = this.MapPlayersByKey.get(userId); const player = this.MapPlayersByKey.get(userId);
if (player === undefined) { if (player === undefined) {
console.error('Cannot find user with id ', userId); console.error('Cannot find user with id ', userId);

View File

@ -6,19 +6,19 @@ import {PlayerMovement} from "./PlayerMovement";
import {HasMovedEvent} from "./GameManager"; import {HasMovedEvent} from "./GameManager";
export class PlayersPositionInterpolator { export class PlayersPositionInterpolator {
playerMovements: Map<string, PlayerMovement> = new Map<string, PlayerMovement>(); playerMovements: Map<number, PlayerMovement> = new Map<number, PlayerMovement>();
updatePlayerPosition(userId: string, playerMovement: PlayerMovement) : void { updatePlayerPosition(userId: number, playerMovement: PlayerMovement) : void {
this.playerMovements.set(userId, playerMovement); this.playerMovements.set(userId, playerMovement);
} }
removePlayer(userId: string): void { removePlayer(userId: number): void {
this.playerMovements.delete(userId); this.playerMovements.delete(userId);
} }
getUpdatedPositions(tick: number) : Map<string, HasMovedEvent> { getUpdatedPositions(tick: number) : Map<number, HasMovedEvent> {
const positions = new Map<string, HasMovedEvent>(); const positions = new Map<number, HasMovedEvent>();
this.playerMovements.forEach((playerMovement: PlayerMovement, userId: string) => { this.playerMovements.forEach((playerMovement: PlayerMovement, userId: number) => {
if (playerMovement.isOutdated(tick)) { if (playerMovement.isOutdated(tick)) {
//console.log("outdated") //console.log("outdated")
this.playerMovements.delete(userId); this.playerMovements.delete(userId);

View File

@ -387,7 +387,7 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
disabledMicrophoneByUserId(userId: string){ disabledMicrophoneByUserId(userId: number){
const element = document.getElementById(`microphone-${userId}`); const element = document.getElementById(`microphone-${userId}`);
if(!element){ if(!element){
return; return;
@ -399,7 +399,7 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
enabledMicrophoneByUserId(userId: string){ enabledMicrophoneByUserId(userId: number){
const element = document.getElementById(`microphone-${userId}`); const element = document.getElementById(`microphone-${userId}`);
if(!element){ if(!element){
return; return;
@ -411,7 +411,7 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
disabledVideoByUserId(userId: string) { disabledVideoByUserId(userId: number) {
let element = document.getElementById(`${userId}`); let element = document.getElementById(`${userId}`);
if (element) { if (element) {
element.style.opacity = "0"; element.style.opacity = "0";
@ -426,7 +426,7 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
enabledVideoByUserId(userId: string){ enabledVideoByUserId(userId: number){
let element = document.getElementById(`${userId}`); let element = document.getElementById(`${userId}`);
if(element){ if(element){
element.style.opacity = "1"; element.style.opacity = "1";

View File

@ -14,7 +14,7 @@ export class ScreenSharingPeer extends Peer {
*/ */
private isReceivingStream:boolean = false; private isReceivingStream:boolean = false;
constructor(private userId: string, initiator: boolean, private connection: Connection) { constructor(private userId: number, initiator: boolean, private connection: Connection) {
super({ super({
initiator: initiator ? initiator : false, initiator: initiator ? initiator : false,
reconnectTimer: 10000, reconnectTimer: 10000,
@ -52,7 +52,7 @@ export class ScreenSharingPeer extends Peer {
if (message.streamEnded !== true) { if (message.streamEnded !== true) {
console.error('Unexpected message on screen sharing peer connection'); console.error('Unexpected message on screen sharing peer connection');
} }
mediaManager.removeActiveScreenSharingVideo(this.userId); mediaManager.removeActiveScreenSharingVideo("" + this.userId);
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -63,7 +63,7 @@ export class ScreenSharingPeer extends Peer {
this.on('connect', () => { this.on('connect', () => {
// FIXME: we need to put the loader on the screen sharing connection // FIXME: we need to put the loader on the screen sharing connection
mediaManager.isConnected(this.userId); mediaManager.isConnected("" + this.userId);
console.info(`connect => ${this.userId}`); console.info(`connect => ${this.userId}`);
}); });
@ -86,10 +86,10 @@ export class ScreenSharingPeer extends Peer {
//console.log(`ScreenSharingPeer::stream => ${this.userId}`, stream); //console.log(`ScreenSharingPeer::stream => ${this.userId}`, stream);
//console.log(`stream => ${this.userId} => `, stream); //console.log(`stream => ${this.userId} => `, stream);
if(!stream){ if(!stream){
mediaManager.removeActiveScreenSharingVideo(this.userId); mediaManager.removeActiveScreenSharingVideo("" + this.userId);
this.isReceivingStream = false; this.isReceivingStream = false;
} else { } else {
mediaManager.addStreamRemoteScreenSharing(this.userId, stream); mediaManager.addStreamRemoteScreenSharing("" + this.userId, stream);
this.isReceivingStream = true; this.isReceivingStream = true;
} }
} }
@ -100,7 +100,7 @@ export class ScreenSharingPeer extends Peer {
public destroy(error?: Error): void { public destroy(error?: Error): void {
try { try {
mediaManager.removeActiveScreenSharingVideo(this.userId); mediaManager.removeActiveScreenSharingVideo("" + this.userId);
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
//console.log('Closing connection with '+userId); //console.log('Closing connection with '+userId);

View File

@ -16,7 +16,7 @@ import {VideoPeer} from "./VideoPeer";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
export interface UserSimplePeerInterface{ export interface UserSimplePeerInterface{
userId: string; userId: number;
name?: string; name?: string;
initiator?: boolean; initiator?: boolean;
} }
@ -24,7 +24,7 @@ export interface UserSimplePeerInterface{
export interface PeerConnectionListener { export interface PeerConnectionListener {
onConnect(user: UserSimplePeerInterface): void; onConnect(user: UserSimplePeerInterface): void;
onDisconnect(userId: string): void; onDisconnect(userId: number): void;
} }
/** /**
@ -35,8 +35,8 @@ export class SimplePeer {
private WebRtcRoomId: string; private WebRtcRoomId: string;
private Users: Array<UserSimplePeerInterface> = new Array<UserSimplePeerInterface>(); private Users: Array<UserSimplePeerInterface> = new Array<UserSimplePeerInterface>();
private PeerScreenSharingConnectionArray: Map<string, ScreenSharingPeer> = new Map<string, ScreenSharingPeer>(); private PeerScreenSharingConnectionArray: Map<number, ScreenSharingPeer> = new Map<number, ScreenSharingPeer>();
private PeerConnectionArray: Map<string, VideoPeer> = new Map<string, VideoPeer>(); private PeerConnectionArray: Map<number, VideoPeer> = new Map<number, VideoPeer>();
private readonly sendLocalVideoStreamCallback: UpdatedLocalStreamCallback; private readonly sendLocalVideoStreamCallback: UpdatedLocalStreamCallback;
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback; private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
@ -140,8 +140,8 @@ export class SimplePeer {
} }
} }
mediaManager.removeActiveVideo(user.userId); mediaManager.removeActiveVideo("" + user.userId);
mediaManager.addActiveVideo(user.userId, name); mediaManager.addActiveVideo("" + user.userId, name);
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
// When a connection is established to a video stream, and if a screen sharing is taking place, // When a connection is established to a video stream, and if a screen sharing is taking place,
@ -171,8 +171,8 @@ export class SimplePeer {
// We should display the screen sharing ONLY if we are not initiator // We should display the screen sharing ONLY if we are not initiator
if (!user.initiator) { if (!user.initiator) {
mediaManager.removeActiveScreenSharingVideo(user.userId); mediaManager.removeActiveScreenSharingVideo("" + user.userId);
mediaManager.addScreenSharingActiveVideo(user.userId); mediaManager.addScreenSharingActiveVideo("" + user.userId);
} }
const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
@ -189,7 +189,7 @@ export class SimplePeer {
* *
* @param userId * @param userId
*/ */
private closeConnection(userId : string) { private closeConnection(userId : number) {
try { try {
//mediaManager.removeActiveVideo(userId); //mediaManager.removeActiveVideo(userId);
const peer = this.PeerConnectionArray.get(userId); const peer = this.PeerConnectionArray.get(userId);
@ -217,9 +217,9 @@ export class SimplePeer {
* *
* @param userId * @param userId
*/ */
private closeScreenSharingConnection(userId : string) { private closeScreenSharingConnection(userId : number) {
try { try {
mediaManager.removeActiveScreenSharingVideo(userId); mediaManager.removeActiveScreenSharingVideo("" + userId);
const peer = this.PeerScreenSharingConnectionArray.get(userId); const peer = this.PeerScreenSharingConnectionArray.get(userId);
if (peer === undefined) { if (peer === undefined) {
console.warn("Tried to close connection for user "+userId+" but could not find user") console.warn("Tried to close connection for user "+userId+" but could not find user")
@ -293,7 +293,7 @@ export class SimplePeer {
* *
* @param userId * @param userId
*/ */
private pushVideoToRemoteUser(userId : string) { private pushVideoToRemoteUser(userId : number) {
try { try {
const PeerConnection = this.PeerConnectionArray.get(userId); const PeerConnection = this.PeerConnectionArray.get(userId);
if (!PeerConnection) { if (!PeerConnection) {
@ -314,7 +314,7 @@ export class SimplePeer {
} }
} }
private pushScreenSharingToRemoteUser(userId : string) { private pushScreenSharingToRemoteUser(userId : number) {
const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId); const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId);
if (!PeerConnection) { if (!PeerConnection) {
throw new Error('While pushing screen sharing, cannot find user with ID ' + userId); throw new Error('While pushing screen sharing, cannot find user with ID ' + userId);
@ -359,7 +359,7 @@ export class SimplePeer {
} }
} }
private sendLocalScreenSharingStreamToUser(userId: string): void { private sendLocalScreenSharingStreamToUser(userId: number): void {
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection) // If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
if (this.PeerScreenSharingConnectionArray.has(userId)) { if (this.PeerScreenSharingConnectionArray.has(userId)) {
this.pushScreenSharingToRemoteUser(userId); this.pushScreenSharingToRemoteUser(userId);
@ -376,7 +376,7 @@ export class SimplePeer {
} }
} }
private stopLocalScreenSharingStreamToUser(userId: string, stream: MediaStream): void { private stopLocalScreenSharingStreamToUser(userId: number, stream: MediaStream): void {
const PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(userId); const PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(userId);
if (!PeerConnectionScreenSharing) { if (!PeerConnectionScreenSharing) {
throw new Error('Weird, screen sharing connection to user ' + userId + 'not found') throw new Error('Weird, screen sharing connection to user ' + userId + 'not found')

View File

@ -9,7 +9,7 @@ const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
* A peer connection used to transmit video / audio signals between 2 peers. * A peer connection used to transmit video / audio signals between 2 peers.
*/ */
export class VideoPeer extends Peer { export class VideoPeer extends Peer {
constructor(private userId: string, initiator: boolean, private connection: Connection) { constructor(private userId: number, initiator: boolean, private connection: Connection) {
super({ super({
initiator: initiator ? initiator : false, initiator: initiator ? initiator : false,
reconnectTimer: 10000, reconnectTimer: 10000,
@ -63,11 +63,11 @@ export class VideoPeer extends Peer {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
this.on('error', (err: any) => { this.on('error', (err: any) => {
console.error(`error => ${this.userId} => ${err.code}`, err); console.error(`error => ${this.userId} => ${err.code}`, err);
mediaManager.isError(userId); mediaManager.isError("" + userId);
}); });
this.on('connect', () => { this.on('connect', () => {
mediaManager.isConnected(this.userId); mediaManager.isConnected("" + this.userId);
console.info(`connect => ${this.userId}`); console.info(`connect => ${this.userId}`);
}); });
@ -108,7 +108,7 @@ export class VideoPeer extends Peer {
mediaManager.disabledVideoByUserId(this.userId); mediaManager.disabledVideoByUserId(this.userId);
mediaManager.disabledMicrophoneByUserId(this.userId); mediaManager.disabledMicrophoneByUserId(this.userId);
} else { } else {
mediaManager.addStreamRemoteVideo(this.userId, stream); mediaManager.addStreamRemoteVideo("" + this.userId, stream);
} }
} }
@ -117,7 +117,7 @@ export class VideoPeer extends Peer {
*/ */
public destroy(error?: Error): void { public destroy(error?: Error): void {
try { try {
mediaManager.removeActiveVideo(this.userId); mediaManager.removeActiveVideo("" + this.userId);
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
//console.log('Closing connection with '+userId); //console.log('Closing connection with '+userId);

View File

@ -1 +1,2 @@
src/ *
!.gitignore

View File

@ -1,31 +0,0 @@
// package:
// file: src/proto/messages.proto
import * as jspb from "google-protobuf";
export class SetPlayerDetailsMessage extends jspb.Message {
getName(): string;
setName(value: string): void;
clearCharacterlayersList(): void;
getCharacterlayersList(): Array<string>;
setCharacterlayersList(value: Array<string>): void;
addCharacterlayers(value: string, index?: number): string;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): SetPlayerDetailsMessage.AsObject;
static toObject(includeInstance: boolean, msg: SetPlayerDetailsMessage): SetPlayerDetailsMessage.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: SetPlayerDetailsMessage, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): SetPlayerDetailsMessage;
static deserializeBinaryFromReader(message: SetPlayerDetailsMessage, reader: jspb.BinaryReader): SetPlayerDetailsMessage;
}
export namespace SetPlayerDetailsMessage {
export type AsObject = {
name: string,
characterlayersList: Array<string>,
}
}

View File

@ -1,223 +0,0 @@
// source: src/proto/messages.proto
/**
* @fileoverview
* @enhanceable
* @suppress {messageConventions} JS Compiler reports an error if a variable or
* field starts with 'MSG_' and isn't a translatable message.
* @public
*/
// GENERATED CODE -- DO NOT EDIT!
var jspb = require('google-protobuf');
var goog = jspb;
var global = Function('return this')();
goog.exportSymbol('proto.SetPlayerDetailsMessage', null, global);
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.SetPlayerDetailsMessage = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, proto.SetPlayerDetailsMessage.repeatedFields_, null);
};
goog.inherits(proto.SetPlayerDetailsMessage, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.SetPlayerDetailsMessage.displayName = 'proto.SetPlayerDetailsMessage';
}
/**
* List of repeated fields within this message type.
* @private {!Array<number>}
* @const
*/
proto.SetPlayerDetailsMessage.repeatedFields_ = [2];
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.SetPlayerDetailsMessage.prototype.toObject = function(opt_includeInstance) {
return proto.SetPlayerDetailsMessage.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.SetPlayerDetailsMessage} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.SetPlayerDetailsMessage.toObject = function(includeInstance, msg) {
var f, obj = {
name: jspb.Message.getFieldWithDefault(msg, 1, ""),
characterlayersList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.SetPlayerDetailsMessage}
*/
proto.SetPlayerDetailsMessage.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.SetPlayerDetailsMessage;
return proto.SetPlayerDetailsMessage.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.SetPlayerDetailsMessage} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.SetPlayerDetailsMessage}
*/
proto.SetPlayerDetailsMessage.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setName(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.addCharacterlayers(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.SetPlayerDetailsMessage.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.SetPlayerDetailsMessage.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.SetPlayerDetailsMessage} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.SetPlayerDetailsMessage.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getName();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
f = message.getCharacterlayersList();
if (f.length > 0) {
writer.writeRepeatedString(
2,
f
);
}
};
/**
* optional string name = 1;
* @return {string}
*/
proto.SetPlayerDetailsMessage.prototype.getName = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.SetPlayerDetailsMessage} returns this
*/
proto.SetPlayerDetailsMessage.prototype.setName = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
/**
* repeated string characterLayers = 2;
* @return {!Array<string>}
*/
proto.SetPlayerDetailsMessage.prototype.getCharacterlayersList = function() {
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 2));
};
/**
* @param {!Array<string>} value
* @return {!proto.SetPlayerDetailsMessage} returns this
*/
proto.SetPlayerDetailsMessage.prototype.setCharacterlayersList = function(value) {
return jspb.Message.setField(this, 2, value || []);
};
/**
* @param {string} value
* @param {number=} opt_index
* @return {!proto.SetPlayerDetailsMessage} returns this
*/
proto.SetPlayerDetailsMessage.prototype.addCharacterlayers = function(value, opt_index) {
return jspb.Message.addToRepeatedField(this, 2, value, opt_index);
};
/**
* Clears the list making it empty but non-null.
* @return {!proto.SetPlayerDetailsMessage} returns this
*/
proto.SetPlayerDetailsMessage.prototype.clearCharacterlayersList = function() {
return this.setCharacterlayersList([]);
};
goog.object.extend(exports, proto);

View File

@ -1,6 +1,41 @@
syntax = "proto3"; syntax = "proto3";
/*********** CLIENT TO SERVER MESSAGES *************/
message SetPlayerDetailsMessage { message SetPlayerDetailsMessage {
string name = 1; string name = 1;
repeated string characterLayers = 2; repeated string characterLayers = 2;
} }
message PositionMessage {
int32 x = 1;
int32 y = 2;
enum Direction {
UP = 0;
RIGHT = 1;
DOWN = 2;
LEFT = 3;
}
Direction direction = 3;
bool moving = 4;
}
message ViewportMessage {
int32 left = 1;
int32 top = 2;
int32 right = 3;
int32 bottom = 4;
}
message UserMovesMessage {
PositionMessage position = 1;
ViewportMessage viewport = 2;
}
/*********** SERVER TO CLIENT MESSAGES *************/
message UserMovedMessage {
int32 userId = 1;
PositionMessage position = 2;
}

View File

@ -4,8 +4,8 @@
"description": "", "description": "",
"main": "generated/src/proto/messages_pb.js", "main": "generated/src/proto/messages_pb.js",
"scripts": { "scripts": {
"proto": "protoc --plugin=\"protoc-gen-ts=./node_modules/.bin/protoc-gen-ts\" --js_out=\"import_style=commonjs,binary:src/messages/generated\" --ts_out=\"src/messages/generated\" src/messages/messages.proto", "proto": "protoc --plugin=\"protoc-gen-ts=./node_modules/.bin/protoc-gen-ts\" --js_out=\"import_style=commonjs,binary:generated\" --ts_out=\"generated\" messages.proto",
"proto:watch": "inotifywait -q -m -e close_write src/messages/messages.proto | while read -r filename event; do yarn run proto; done" "proto:watch": "inotifywait -q -m -e close_write messages.proto | while read -r filename event; do yarn run proto; done"
}, },
"repository": { "repository": {
"type": "git", "type": "git",