Merge pull request #320 from thecodingmachine/nodejsTags

now fetch the tags from the admin into the nodejs back
This commit is contained in:
Kharhamel 2020-10-14 16:58:57 +02:00 committed by GitHub
commit 49a0125f88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 226 additions and 149 deletions

View File

@ -1,6 +1,6 @@
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
import {MINIMUM_DISTANCE, GROUP_RADIUS} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." import {MINIMUM_DISTANCE, GROUP_RADIUS} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
import {GameRoom} from "../Model/GameRoom"; import {GameRoom, GameRoomPolicyTypes} from "../Model/GameRoom";
import {Group} from "../Model/Group"; import {Group} from "../Model/Group";
import {User} from "../Model/User"; import {User} from "../Model/User";
import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage"; import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage";
@ -41,7 +41,7 @@ import {cpuTracker} from "../Services/CpuTracker";
import {ViewportInterface} from "../Model/Websocket/ViewportMessage"; import {ViewportInterface} from "../Model/Websocket/ViewportMessage";
import {jwtTokenManager} from "../Services/JWTTokenManager"; import {jwtTokenManager} from "../Services/JWTTokenManager";
import {adminApi} from "../Services/AdminApi"; import {adminApi} from "../Services/AdminApi";
import {RoomIdentifier} from "../Model/RoomIdentifier"; import {PositionInterface} from "../Model/PositionInterface";
function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void { function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
socket.batchedMessages.addPayload(payload); socket.batchedMessages.addPayload(payload);
@ -113,11 +113,9 @@ export class IoSocketController {
const websocketExtensions = req.getHeader('sec-websocket-extensions'); const websocketExtensions = req.getHeader('sec-websocket-extensions');
const roomId = query.roomId; const roomId = query.roomId;
//todo: better validation: /\/_\/.*\/.*/ or /\/@\/.*\/.*\/.*/
if (typeof roomId !== 'string') { if (typeof roomId !== 'string') {
throw new Error('Undefined room ID: '); throw new Error('Undefined room ID: ');
} }
const roomIdentifier = new RoomIdentifier(roomId);
const token = query.token; const token = query.token;
const x = Number(query.x); const x = Number(query.x);
@ -143,17 +141,20 @@ export class IoSocketController {
const userUuid = await jwtTokenManager.getUserUuidFromToken(token); const userUuid = await jwtTokenManager.getUserUuidFromToken(token);
console.log('uuid', userUuid);
let memberTags: string[] = []; let memberTags: string[] = [];
if (roomIdentifier.anonymous === false) { const room = await this.getOrCreateRoom(roomId);
const grants = await adminApi.memberIsGrantedAccessToRoom(userUuid, roomIdentifier); if (!room.anonymous && room.policyType !== GameRoomPolicyTypes.ANONYMUS_POLICY) {
if (!grants.granted) { try {
const userData = await adminApi.fetchMemberDataByUuid(userUuid);
memberTags = userData.tags;
if (room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && !room.canAccess(memberTags)) {
throw new Error('No correct tags')
}
console.log('access granted for user '+userUuid+' and room '+roomId);
} catch (e) {
console.log('access not granted for user '+userUuid+' and room '+roomId); console.log('access not granted for user '+userUuid+' and room '+roomId);
throw new Error('Client cannot acces this ressource.') throw new Error('Client cannot acces this ressource.')
} else {
memberTags = grants.memberTags;
console.log('access granted for user '+userUuid+' and room '+roomId);
} }
} }
@ -172,6 +173,7 @@ export class IoSocketController {
roomId, roomId,
name, name,
characterLayers, characterLayers,
tags: memberTags,
position: { position: {
x: x, x: x,
y: y, y: y,
@ -183,8 +185,7 @@ export class IoSocketController {
right, right,
bottom, bottom,
left left
}, }
tags: memberTags
}, },
/* Spell these correctly */ /* Spell these correctly */
websocketKey, websocketKey,
@ -219,9 +220,9 @@ export class IoSocketController {
client.disconnecting = false; client.disconnecting = false;
client.name = ws.name; client.name = ws.name;
client.tags = ws.tags;
client.characterLayers = ws.characterLayers; client.characterLayers = ws.characterLayers;
client.roomId = ws.roomId; client.roomId = ws.roomId;
client.tags = ws.tags;
this.sockets.set(client.userId, client); this.sockets.set(client.userId, client);
@ -230,7 +231,7 @@ export class IoSocketController {
console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)'); console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
// Let's join the room // Let's join the room
this.handleJoinRoom(client, client.roomId, client.position, client.viewport, client.name, client.characterLayers); this.handleJoinRoom(client, client.position, client.viewport);
}, },
message: (ws, arrayBuffer, isBinary): void => { message: (ws, arrayBuffer, isBinary): void => {
const client = ws as ExSocketInterface; const client = ws as ExSocketInterface;
@ -266,11 +267,6 @@ export class IoSocketController {
Client.disconnecting = true; Client.disconnecting = true;
//leave room //leave room
this.leaveRoom(Client); this.leaveRoom(Client);
//delete all socket information
/*delete Client.roomId;
delete Client.token;
delete Client.position;*/
} catch (e) { } catch (e) {
console.error('An error occurred on "disconnect"'); console.error('An error occurred on "disconnect"');
console.error(e); console.error(e);
@ -283,21 +279,6 @@ export class IoSocketController {
console.log('A user left (', this.sockets.size, ' connected users)'); console.log('A user left (', this.sockets.size, ' connected users)');
} }
}) })
// TODO: finish this!
/*this.Io.on(SocketIoEvent.CONNECTION, (socket: Socket) => {
socket.on(SocketIoEvent.WEBRTC_SIGNAL, (data: unknown) => {
this.emitVideo((socket as ExSocketInterface), data);
});
socket.on(SocketIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => {
this.emitScreenSharing((socket as ExSocketInterface), data);
});
});*/
} }
private emitError(Client: ExSocketInterface, message: string): void { private emitError(Client: ExSocketInterface, message: string): void {
@ -313,10 +294,10 @@ export class IoSocketController {
console.warn(message); console.warn(message);
} }
private handleJoinRoom(client: ExSocketInterface, roomId: string, position: PointInterface, viewport: ViewportInterface, name: string, characterLayers: string[]): void { private handleJoinRoom(client: ExSocketInterface, position: PointInterface, viewport: ViewportInterface): void {
try { try {
//join new previous room //join new previous room
const gameRoom = this.joinRoom(client, roomId, position); const gameRoom = this.joinRoom(client, position);
const things = gameRoom.setViewport(client, viewport); const things = gameRoom.setViewport(client, viewport);
@ -357,7 +338,6 @@ export class IoSocketController {
} }
roomJoinedMessage.setCurrentuserid(client.userId); roomJoinedMessage.setCurrentuserid(client.userId);
roomJoinedMessage.setTagList(client.tags);
const serverToClientMessage = new ServerToClientMessage(); const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage); serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
@ -575,77 +555,42 @@ export class IoSocketController {
} }
} }
} }
private joinRoom(client : ExSocketInterface, roomId: string, position: PointInterface): GameRoom { private async getOrCreateRoom(roomId: string): Promise<GameRoom> {
//join user in room
this.nbClientsPerRoomGauge.inc({ room: roomId });
client.roomId = roomId;
client.position = position;
//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 GameRoom((user1: User, group: Group) => { world = new GameRoom(
this.joinWebRtcRoom(user1, group); roomId,
}, (user1: User, group: Group) => { (user: User, group: Group) => this.joinWebRtcRoom(user, group),
this.disConnectedUser(user1, group); (user: User, group: Group) => this.disConnectedUser(user, group),
}, MINIMUM_DISTANCE, GROUP_RADIUS, (thing: Movable, listener: User) => { MINIMUM_DISTANCE,
const clientListener = this.searchClientByIdOrFail(listener.id); GROUP_RADIUS,
if (thing instanceof User) { (thing: Movable, listener: User) => this.onRoomEnter(thing, listener),
const clientUser = this.searchClientByIdOrFail(thing.id); (thing: Movable, position:PositionInterface, listener:User) => this.onClientMove(thing, position, listener),
(thing: Movable, listener:User) => this.onClientLeave(thing, listener)
const userJoinedMessage = new UserJoinedMessage(); );
if (!Number.isInteger(clientUser.userId)) { if (!world.anonymous) {
throw new Error('clientUser.userId is not an integer '+clientUser.userId); const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
} world.tags = data.tags
userJoinedMessage.setUserid(clientUser.userId); world.policyType = Number(data.policy_type)
userJoinedMessage.setName(clientUser.name); }
userJoinedMessage.setCharacterlayersList(clientUser.characterLayers);
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
const subMessage = new SubMessage();
subMessage.setUserjoinedmessage(userJoinedMessage);
emitInBatch(clientListener, subMessage);
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
} else {
console.error('Unexpected type for Movable.');
}
}, (thing: Movable, position, listener) => {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
const userMovedMessage = new UserMovedMessage();
userMovedMessage.setUserid(clientUser.userId);
userMovedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
const subMessage = new SubMessage();
subMessage.setUsermovedmessage(userMovedMessage);
clientListener.emitInBatch(subMessage);
//console.log("Sending USER_MOVED event");
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
} else {
console.error('Unexpected type for Movable.');
}
}, (thing: Movable, listener) => {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
this.emitUserLeftEvent(clientListener, clientUser.userId);
} else if (thing instanceof Group) {
this.emitDeleteGroupEvent(clientListener, thing.getId());
} else {
console.error('Unexpected type for Movable.');
}
});
this.Worlds.set(roomId, world); this.Worlds.set(roomId, world);
} }
return Promise.resolve(world)
}
private joinRoom(client : ExSocketInterface, position: PointInterface): GameRoom {
const roomId = client.roomId;
//join user in room
this.nbClientsPerRoomGauge.inc({ room: roomId });
client.position = position;
const world = this.Worlds.get(roomId)
if(world === undefined){
throw new Error('Could not find room for ID: '+client.roomId)
}
// Dispatch groups position to newly connected user // Dispatch groups position to newly connected user
world.getGroups().forEach((group: Group) => { world.getGroups().forEach((group: Group) => {
@ -655,6 +600,64 @@ export class IoSocketController {
world.join(client, client.position); world.join(client, client.position);
return world; return world;
} }
private onRoomEnter(thing: Movable, listener: User) {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
const userJoinedMessage = new UserJoinedMessage();
if (!Number.isInteger(clientUser.userId)) {
throw new Error('clientUser.userId is not an integer '+clientUser.userId);
}
userJoinedMessage.setUserid(clientUser.userId);
userJoinedMessage.setName(clientUser.name);
userJoinedMessage.setCharacterlayersList(clientUser.characterLayers);
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
const subMessage = new SubMessage();
subMessage.setUserjoinedmessage(userJoinedMessage);
emitInBatch(clientListener, subMessage);
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
} else {
console.error('Unexpected type for Movable.');
}
}
private onClientMove(thing: Movable, position:PositionInterface, listener:User): void {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
const userMovedMessage = new UserMovedMessage();
userMovedMessage.setUserid(clientUser.userId);
userMovedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
const subMessage = new SubMessage();
subMessage.setUsermovedmessage(userMovedMessage);
clientListener.emitInBatch(subMessage);
//console.log("Sending USER_MOVED event");
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
} else {
console.error('Unexpected type for Movable.');
}
}
private onClientLeave(thing: Movable, listener:User) {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
this.emitUserLeftEvent(clientListener, clientUser.userId);
} else if (thing instanceof Group) {
this.emitDeleteGroupEvent(clientListener, thing.getId());
} else {
console.error('Unexpected type for Movable.');
}
}
private emitCreateUpdateGroupEvent(client: ExSocketInterface, group: Group): void { private emitCreateUpdateGroupEvent(client: ExSocketInterface, group: Group): void {
const position = group.getPosition(); const position = group.getPosition();

View File

@ -8,10 +8,18 @@ import {EntersCallback, LeavesCallback, MovesCallback} from "_Model/Zone";
import {PositionNotifier} from "./PositionNotifier"; 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";
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
import {arrayIntersect} from "../Services/ArrayHelper";
export type ConnectCallback = (user: User, group: Group) => void; export type ConnectCallback = (user: User, group: Group) => void;
export type DisconnectCallback = (user: User, group: Group) => void; export type DisconnectCallback = (user: User, group: Group) => void;
export enum GameRoomPolicyTypes {
ANONYMUS_POLICY = 1,
MEMBERS_ONLY_POLICY,
USE_TAGS_POLICY,
}
export class GameRoom { export class GameRoom {
private readonly minDistance: number; private readonly minDistance: number;
private readonly groupRadius: number; private readonly groupRadius: number;
@ -26,8 +34,16 @@ export class GameRoom {
private itemsState: Map<number, unknown> = new Map<number, unknown>(); private itemsState: Map<number, unknown> = new Map<number, unknown>();
private readonly positionNotifier: PositionNotifier; private readonly positionNotifier: PositionNotifier;
public readonly roomId: string;
public readonly anonymous: boolean;
public tags: string[];
public policyType: GameRoomPolicyTypes;
public readonly roomSlug: string;
public readonly worldSlug: string = '';
public readonly organizationSlug: string = '';
constructor(connectCallback: ConnectCallback, constructor(roomId: string,
connectCallback: ConnectCallback,
disconnectCallback: DisconnectCallback, disconnectCallback: DisconnectCallback,
minDistance: number, minDistance: number,
groupRadius: number, groupRadius: number,
@ -35,6 +51,21 @@ export class GameRoom {
onMoves: MovesCallback, onMoves: MovesCallback,
onLeaves: LeavesCallback) onLeaves: LeavesCallback)
{ {
this.roomId = roomId;
this.anonymous = isRoomAnonymous(roomId);
this.tags = [];
this.policyType = GameRoomPolicyTypes.ANONYMUS_POLICY;
if (this.anonymous) {
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
} else {
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
this.roomSlug = roomSlug;
this.organizationSlug = organizationSlug;
this.worldSlug = worldSlug;
}
this.users = new Map<number, User>(); this.users = new Map<number, User>();
this.groups = new Set<Group>(); this.groups = new Set<Group>();
this.connectCallback = connectCallback; this.connectCallback = connectCallback;
@ -248,4 +279,8 @@ export class GameRoom {
} }
return this.positionNotifier.setViewport(user, viewport); return this.positionNotifier.setViewport(user, viewport);
} }
canAccess(userTags: string[]): boolean {
return arrayIntersect(userTags, this.tags);
}
} }

View File

@ -1,25 +1,30 @@
export class RoomIdentifier { //helper functions to parse room IDs
public readonly anonymous: boolean;
public readonly id:string
public readonly organizationSlug: string|undefined;
public readonly worldSlug: string|undefined;
public readonly roomSlug: string|undefined;
constructor(roomID: string) {
if (roomID.startsWith('_/')) {
this.anonymous = true;
} else if(roomID.startsWith('@/')) {
this.anonymous = false;
const match = /@\/([^/]+)\/([^/]+)\/(.+)/.exec(roomID); export const isRoomAnonymous = (roomID: string): boolean => {
if (!match) { if (roomID.startsWith('_/')) {
throw new Error('Could not extract info from "'+roomID+'"'); return true;
} } else if(roomID.startsWith('@/')) {
this.organizationSlug = match[1]; return false;
this.worldSlug = match[2]; } else {
this.roomSlug = match[3]; throw new Error('Incorrect room ID: '+roomID);
} else {
throw new Error('Incorrect room ID: '+roomID);
}
this.id = roomID;
} }
} }
export const extractRoomSlugPublicRoomId = (roomId: string): string => {
const idParts = roomId.split('/');
if (idParts.length < 3) throw new Error('Incorrect roomId: '+roomId);
return idParts.slice(2).join('/');
}
export interface extractDataFromPrivateRoomIdResponse {
organizationSlug: string;
worldSlug: string;
roomSlug: string;
}
export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => {
const idParts = roomId.split('/');
if (idParts.length < 4) throw new Error('Incorrect roomId: '+roomId);
const organizationSlug = idParts[1];
const worldSlug = idParts[2];
const roomSlug = idParts[3];
return {organizationSlug, worldSlug, roomSlug}
}

View File

@ -1,12 +1,13 @@
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
import Axios from "axios"; import Axios from "axios";
import {RoomIdentifier} from "../Model/RoomIdentifier";
export interface AdminApiData { export interface AdminApiData {
organizationSlug: string organizationSlug: string
worldSlug: string worldSlug: string
roomSlug: string roomSlug: string
mapUrlStart: string mapUrlStart: string
tags: string[]
policy_type: number
userUuid: string userUuid: string
} }
@ -15,6 +16,11 @@ export interface GrantedApiData {
memberTags: string[] memberTags: string[]
} }
export interface fetchMemberDataByUuidResponse {
uuid: string;
tags: string[];
}
class AdminApi { class AdminApi {
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> { async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
@ -40,6 +46,16 @@ class AdminApi {
return res.data; return res.data;
} }
async fetchMemberDataByUuid(uuid: string): Promise<fetchMemberDataByUuidResponse> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
const res = await Axios.get(ADMIN_API_URL+'/membership/'+uuid,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
}
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> { async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
if (!ADMIN_API_URL) { if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!'); return Promise.reject('No admin backoffice set!');
@ -50,24 +66,6 @@ class AdminApi {
) )
return res.data; return res.data;
} }
async memberIsGrantedAccessToRoom(memberId: string, roomIdentifier: RoomIdentifier): Promise<GrantedApiData> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
try {
const res = await Axios.get(ADMIN_API_URL+'/api/member/is-granted-access',
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`}, params: {memberId, organizationSlug: roomIdentifier.organizationSlug, worldSlug: roomIdentifier.worldSlug, roomSlug: roomIdentifier.roomSlug} }
)
return res.data;
} catch (e) {
console.log(e.message)
return {
granted: false,
memberTags: []
};
}
}
} }
export const adminApi = new AdminApi(); export const adminApi = new AdminApi();

View File

@ -0,0 +1,3 @@
export const arrayIntersect = (array1: string[], array2: string[]) : boolean => {
return array1.filter(value => array2.includes(value)).length > 0;
}

View File

@ -0,0 +1,14 @@
import {arrayIntersect} from "../src/Services/ArrayHelper";
describe("RoomIdentifier", () => {
it("should return true on intersect", () => {
expect(arrayIntersect(['admin', 'user'], ['admin', 'superAdmin'])).toBe(true);
});
it("should be reflexive", () => {
expect(arrayIntersect(['admin', 'superAdmin'], ['admin', 'user'])).toBe(true);
});
it("should return false on non intersect", () => {
expect(arrayIntersect(['admin', 'user'], ['superAdmin'])).toBe(false);
});
})

View File

@ -0,0 +1,19 @@
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "../src/Model/RoomIdentifier";
describe("RoomIdentifier", () => {
it("should flag public id as anonymous", () => {
expect(isRoomAnonymous('_/global/test')).toBe(true);
});
it("should flag public id as not anonymous", () => {
expect(isRoomAnonymous('@/afup/afup2020/1floor')).toBe(false);
});
it("should extract roomSlug from public ID", () => {
expect(extractRoomSlugPublicRoomId('_/global/npeguin/test.json')).toBe('npeguin/test.json');
});
it("should extract correct from private ID", () => {
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId('@/afup/afup2020/1floor');
expect(organizationSlug).toBe('afup');
expect(worldSlug).toBe('afup2020');
expect(roomSlug).toBe('1floor');
});
})

View File

@ -21,7 +21,7 @@ describe("World", () => {
} }
const world = new GameRoom(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {});
world.join(createMockUser(1), new Point(100, 100)); world.join(createMockUser(1), new Point(100, 100));
@ -48,7 +48,7 @@ describe("World", () => {
} }
const world = new GameRoom(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {});
world.join(createMockUser(1), new Point(100, 100)); world.join(createMockUser(1), new Point(100, 100));
@ -77,7 +77,7 @@ describe("World", () => {
disconnectCallNumber++; disconnectCallNumber++;
} }
const world = new GameRoom(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {});
world.join(createMockUser(1), new Point(100, 100)); world.join(createMockUser(1), new Point(100, 100));