diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..cbfe8a3
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,4 @@
+build
+node_modules
+package-lock.json
+*.log*
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..aad801b
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,17 @@
+{
+ "parserOptions": {
+ "sourceType": "module"
+ },
+ "env": {
+ "node": true,
+ "es6": true
+ },
+ "rules": {
+ "no-console": [true, "log", "debug", "error"],
+ "quotes": [2, "single", {"avoidEscape": true}],
+ "strict": [2, "never"]
+ },
+ "extends": [
+ "eslint:recommended"
+ ]
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..00bd086
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.vscode
+build
+node_modules
+*.log
+package-lock.json
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..a4c185d
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,12 @@
+image: node:10.16.3
+
+cache:
+ paths:
+ - node_modules/
+
+before_script:
+ - npm install --no-optional
+
+build:
+ script:
+ - npm test
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..28cffdc
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,6 @@
+## Contributing
+
+* If you find solution to an [issue/improvements](https://github.com/hgouveia/html5multiplayer/issues) would be helpful to everyone, feel free to send us a pull request.
+* The ideal approach to create a fix would be to fork the repository, create a branch in your repository, and make a pull request out of it.
+* It is desirable if there is enough comments/documentation and Tests included in the pull request.
+* For general idea of contribution, please follow the guidelines mentioned [here](https://guides.github.com/activities/contributing-to-open-source/).
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..0f22531
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,20 @@
+# Compiles webpack and typescript
+FROM node:10 as BUILD
+WORKDIR /tmp/app
+COPY . .
+RUN npm i --no-optional
+ENV NODE_ENV=production
+RUN npm run build
+
+# Deploy final app
+FROM node:10
+ENV NODE_ENV=production
+ENV HOST=0.0.0.0
+ENV PORT=3478
+WORKDIR /usr/src/app
+COPY --from=BUILD /tmp/app/package*.json /usr/src/app/
+COPY --from=BUILD /tmp/app/build/ /usr/src/app/build
+COPY --from=BUILD /tmp/app/index.html /usr/src/app/
+RUN npm ci
+EXPOSE $PORT
+CMD [ "npm", "start" ]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8475206
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Jose De Gouveia
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c3553da
--- /dev/null
+++ b/README.md
@@ -0,0 +1,69 @@
+# Multiplayer Game
+
+A very basic multiplayer implementations using HTML5 Canvas and socket.io for learning purpose
+
+[Demo](https://game.joyalstudios.com/html5multiplayer)
+
+## Usage
+
+```bash
+npm install
+npm run build
+```
+
+Access via http://localhost:3478/play to play the game
+
+Access stats info via http://localhost:3478/list
+
+Running using **docker-compose**
+```bash
+docker-compose up -d
+```
+
+or plain docker
+
+Build:
+```bash
+docker build -t hgouveia/html-mp:latest .
+```
+Run:
+```
+docker run -d --name mp-game -e PORT=3478 -e DEBUG=ts-mp:* -p 3478:3478 hgouveia/html-mp:latest
+```
+
+if you want to hide the port, you could use Nginx Proxy
+
+```conf
+server {
+ listen 80;
+ root /www/game;
+ server_name mydomain.com;
+
+ location /mygame/ {
+ proxy_pass http://127.0.0.1:3478/;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_cache_bypass $http_upgrade;
+ }
+
+ location / {
+ try_files $uri $uri/ =404;
+ }
+}
+```
+
+## TODO
+- Move HIT/DIE/BULLETS events to be processed on the server
+- Add basic predictions
+- Add security aspects (for ex: any player data could be altered via client)
+
+## License
+
+Read [License](LICENSE) for more licensing information.
+
+## Contributing
+
+Read [here](CONTRIBUTING.md) for more information.
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..3afec16
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,13 @@
+version: '3'
+services:
+ node:
+ build: .
+ container_name: mp-server
+ restart: always
+ environment:
+ - NODE_ENV=production
+ - DEBUG=ts-mp:*
+ - HOST=0.0.0.0
+ - PORT=3478
+ ports:
+ - "3478:3478"
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..e47edb9
--- /dev/null
+++ b/index.html
@@ -0,0 +1,106 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/server/dashboard/views/home.ejs b/src/server/dashboard/views/home.ejs
new file mode 100644
index 0000000..59593a0
--- /dev/null
+++ b/src/server/dashboard/views/home.ejs
@@ -0,0 +1 @@
+<%- body %>
\ No newline at end of file
diff --git a/src/server/dashboard/views/layout.ejs b/src/server/dashboard/views/layout.ejs
new file mode 100644
index 0000000..3090ce6
--- /dev/null
+++ b/src/server/dashboard/views/layout.ejs
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Server
+
+
+
+
+
+
+
+
+
+
+
+
+ <%- include(section) %>
+
+
+
+
\ No newline at end of file
diff --git a/src/server/dashboard/views/playerList.ejs b/src/server/dashboard/views/playerList.ejs
new file mode 100644
index 0000000..a323874
--- /dev/null
+++ b/src/server/dashboard/views/playerList.ejs
@@ -0,0 +1,56 @@
+
+
Active Players (<%=players.length%>)
+
+
+
+
+
ID
+
Life
+
Dead
+
Kill
+
X
+
Y
+
+
+
+ <% for (let player of players) {%>
+
+
<%= player.id %>
+
<%= player.name %>
+
<%= player.getLife() %>
+
<%= player.getDead() %>
+
<%= player.getKill() %>
+
<%= player.x %>
+
<%= player.y %>
+
+ <% } %>
+
+
+
+
History Players (<%=removedPlayers.length%>)
+
+
+
+
+
ID
+
Life
+
Dead
+
Kill
+
X
+
Y
+
+
+
+ <% for (let player of removedPlayers) {%>
+
+
<%= player.id %>
+
<%= player.name %>
+
<%= player.getLife() %>
+
<%= player.getDead() %>
+
<%= player.getKill() %>
+
<%= player.x %>
+
<%= player.y %>
+
+ <% } %>
+
+
\ No newline at end of file
diff --git a/src/server/events/IEventHandler.ts b/src/server/events/IEventHandler.ts
new file mode 100644
index 0000000..853fc48
--- /dev/null
+++ b/src/server/events/IEventHandler.ts
@@ -0,0 +1,3 @@
+export interface IEventHandler {
+ attachEvents(client: SocketIO.Socket): void;
+}
diff --git a/src/server/events/PlayerEventsHandler.ts b/src/server/events/PlayerEventsHandler.ts
new file mode 100644
index 0000000..64949af
--- /dev/null
+++ b/src/server/events/PlayerEventsHandler.ts
@@ -0,0 +1,202 @@
+import debug, { Debugger } from 'debug';
+import { PLAYER_EVENTS, SERVER_EVENTS, WS_EVENTS } from '../../common/constants';
+import { IPlayer } from '../../common/IPlayer';
+import { PlayersHandler } from '../../common/PlayersHandler';
+import { Player } from '../lib/Player';
+import { IEventHandler } from './IEventHandler';
+
+const dinfo: Debugger = debug('ts-mp:server:ws:playerevents');
+
+export class PlayerEventsHandler implements IEventHandler {
+
+ constructor(
+ protected socket: SocketIO.Server,
+ protected clients: { [id: string]: SocketIO.Socket },
+ protected playersHandler: PlayersHandler,
+ ) { }
+
+ /**
+ *
+ *
+ * @param {SocketIO.Socket} client
+ * @memberof PlayerEventsHandler
+ */
+ public attachEvents(client: SocketIO.Socket): void {
+
+ // Default events from websocket
+ client.on(WS_EVENTS.CLIENT_DISCONNECT, () => this.onClientDisconnect(client));
+
+ // Listen for new player
+ client.on(`${PLAYER_EVENTS.JOIN}`, (data: any, successFn: any) =>
+ this.onPlayerJoined(client, data, successFn));
+
+ // Listen for move player
+ client.on(`${PLAYER_EVENTS.MOVE}`, (data: any) => this.onMovePlayer(client, data));
+
+ // Listen for player fire
+ client.on(`${PLAYER_EVENTS.SHOT}`, (data: any) => this.onFireBullet(client, data));
+
+ // Listen for player hit
+ client.on(`${PLAYER_EVENTS.HIT}`, (data: any) => this.onPlayerHit(client, data));
+
+ }
+
+ /**
+ *
+ *
+ * @protected
+ * @param {SocketIO.Socket} client
+ * @param {*} data
+ * @memberof PlayerEventsHandler
+ */
+ protected onPlayerJoined(client: SocketIO.Socket, data: any, successFn: any): void {
+ dinfo(`player joined: ${data.name} - [${client.id}]`);
+
+ const player: Player = new Player(
+ client.id,
+ data.name,
+ data.x || 0,
+ data.y || 0,
+ data.angle || 0,
+ client,
+ );
+
+ const playerList: IPlayer[] = this.playersHandler.getPlayers()
+ .map((p: Player) => p.toNetPackage());
+ this.playersHandler.addPlayer(player);
+
+ // Notifies to the user that succesfuly joined to the game
+ successFn();
+
+ // Broadcast new player to connected socket clients
+ client.broadcast.emit(`${PLAYER_EVENTS.JOIN}`, player.toNetPackage());
+ // Sent all connected player to the player
+ client.emit(`${SERVER_EVENTS.PLAYER_LIST}`, playerList);
+ }
+
+ /**
+ *
+ *
+ * @protected
+ * @param {SocketIO.Socket} client
+ * @memberof PlayerEventsHandler
+ */
+ protected onClientDisconnect(client: SocketIO.Socket): void {
+ dinfo(`Player has disconnected: ${client.id}`);
+
+ // Remove from the list
+ this.playersHandler.removePlayerById(client.id);
+
+ // Broadcast removed player to remaning connected socket clients
+ client.broadcast.emit(`${PLAYER_EVENTS.REMOVE}`, { id: client.id });
+ }
+
+ /**
+ *
+ *
+ * @protected
+ * @param {SocketIO.Socket} client
+ * @param {*} data
+ * @returns
+ * @memberof PlayerEventsHandler
+ */
+ protected onMovePlayer(client: SocketIO.Socket, data: any): void {
+ const player: Player = this.playersHandler.getPlayerById(client.id) as Player;
+
+ if (!player) {
+ dinfo(`Player not found: ${client.id}`);
+ return;
+ }
+
+ // Update player position
+ player.angle = data.angle;
+ player.x = data.x;
+ player.y = data.y;
+
+ // Broadcast updated position to others players
+ client.broadcast.emit(`${PLAYER_EVENTS.MOVE}`, player.toNetPackage());
+ }
+
+ /**
+ *
+ *
+ * @protected
+ * @param {SocketIO.Socket} client
+ * @param {*} data
+ * @returns
+ * @memberof PlayerEventsHandler
+ */
+ protected onFireBullet(client: SocketIO.Socket, data: any): void {
+ const player: Player = this.playersHandler.getPlayerById(client.id) as Player;
+
+ if (!player) {
+ dinfo(`Player not found: ${client.id}`);
+ return;
+ }
+
+ // Relay bullet spawn point from the player to all players
+ client.broadcast.emit(`${PLAYER_EVENTS.SHOT}`, {
+ id: player.id,
+ x: data.x,
+ y: data.y,
+ angle: data.angle,
+ });
+ }
+
+ /**
+ *
+ *
+ * @protected
+ * @param {SocketIO.Socket} client
+ * @param {*} data
+ * @memberof PlayerEventsHandler
+ */
+ protected onPlayerHit(client: SocketIO.Socket, data: any): void {
+ const player: Player = this.playersHandler.getPlayerById(client.id) as Player;
+
+ // Calculate the hit damage
+ player.hit();
+
+ // Relay to other players
+ client.broadcast.emit(`${PLAYER_EVENTS.HIT}`, {
+ id: player.id,
+ hitBy: data.hitBy,
+ life: player.getLife(),
+ });
+
+ // check if player is dead
+ if (player.getLife() <= 0) {
+ this.onPlayerDead(client, player, data);
+ }
+ }
+
+ /**
+ *
+ *
+ * @private
+ * @param {SocketIO.Socket} client
+ * @param {Player} player
+ * @param {*} data
+ * @memberof PlayerEventsHandler
+ */
+ private onPlayerDead(client: SocketIO.Socket, player: Player, data: any): void {
+
+ // Reset player attributes
+ player.dead();
+
+ // Send to all players
+ this.socket.emit(`${PLAYER_EVENTS.DIE}`, { id: player.id, killedBy: data.hitBy });
+
+ let killedByName = 'unknown';
+ const killerPlayer: Player = this.playersHandler.getPlayerById(data.hitBy) as Player;
+
+ if (killerPlayer) {
+ killerPlayer.addKill();
+ killedByName = killerPlayer.name;
+ this.clients[data.hitBy].emit(`${PLAYER_EVENTS.KILLED_PLAYER}`, { id: player.id, name: player.name });
+ }
+
+ dinfo(`Player ${player.name} was killed by ${killedByName}`);
+ }
+
+}
diff --git a/src/server/events/ServerEventsHandler.ts b/src/server/events/ServerEventsHandler.ts
new file mode 100644
index 0000000..261b462
--- /dev/null
+++ b/src/server/events/ServerEventsHandler.ts
@@ -0,0 +1,48 @@
+import debug, { Debugger } from 'debug';
+import { SERVER_EVENTS } from '../../common/constants';
+import { PlayersHandler } from '../../common/PlayersHandler';
+import { Player } from '../lib/Player';
+import { IEventHandler } from './IEventHandler';
+
+const dinfo: Debugger = debug('ts-mp:server:ws:playerevents');
+
+export class ServerEventsHandler implements IEventHandler {
+
+ constructor(
+ protected socket: SocketIO.Server,
+ protected clients: { [id: string]: SocketIO.Socket },
+ protected playersHandler: PlayersHandler,
+ ) { }
+
+ /**
+ *
+ *
+ * @param {SocketIO.Socket} client
+ * @memberof PlayerEventsHandler
+ */
+ public attachEvents(client: SocketIO.Socket): void {
+ // Listen for player stats request
+ client.on(`${SERVER_EVENTS.STATS}`, (responseFn: any) => this.onPlayersStats(client, responseFn));
+ }
+
+ /**
+ *
+ *
+ * @protected
+ * @param {SocketIO.Socket} client
+ * @memberof PlayerEventsHandler
+ */
+ protected onPlayersStats(client: SocketIO.Socket, responseFn: any): void {
+ const playersStats = this.playersHandler.getPlayers()
+ .reduce((acc: any[], player: Player) => {
+ acc.push({
+ name: player.name,
+ kill: player.getKill(),
+ dead: player.getDead(),
+ });
+ return acc;
+ }, []);
+ responseFn(playersStats);
+ dinfo(`Players Stats[${playersStats.length}]`, playersStats);
+ }
+}
diff --git a/src/server/index.ts b/src/server/index.ts
new file mode 100644
index 0000000..6a8521f
--- /dev/null
+++ b/src/server/index.ts
@@ -0,0 +1,22 @@
+import debug, { Debugger } from 'debug';
+import express, { Application } from 'express';
+import { Server as httpServer } from 'http';
+import { PlayersHandler } from '../common/PlayersHandler';
+import { Dashboard } from './dashboard';
+import { WebSocketHandler } from './lib/WebSocketHandler';
+
+const PORT: number = parseInt(process.env.PORT, 10) || 3478;
+const HOST: string = process.env.HOST || '0.0.0.0';
+const dinfo: Debugger = debug('ts-mp:server');
+
+const app: Application = express();
+const server: httpServer = app.listen(PORT, HOST);
+const playersHandler: PlayersHandler = new PlayersHandler();
+const wsHandler: WebSocketHandler = new WebSocketHandler(server, playersHandler);
+const dashboard: Dashboard = new Dashboard(app, playersHandler);
+
+wsHandler.init();
+dashboard.init();
+
+dinfo(`Server Initialized ${HOST}:${PORT}`);
+dinfo(`Dashboard Initialized http://${HOST}:${PORT}/list`);
diff --git a/src/server/lib/Player.ts b/src/server/lib/Player.ts
new file mode 100644
index 0000000..a639c94
--- /dev/null
+++ b/src/server/lib/Player.ts
@@ -0,0 +1,141 @@
+import { IPlayer } from '../../common/IPlayer';
+
+/**
+ *
+ *
+ * @export
+ * @class Player
+ */
+export class Player implements IPlayer {
+ /**
+ *
+ *
+ * @protected
+ * @type {number}
+ * @memberof Player
+ */
+ protected life: number = 100;
+
+ /**
+ *
+ *
+ * @protected
+ * @type {number}
+ * @memberof Player
+ */
+ protected lifeDamage: number = 5;
+
+ /**
+ *
+ *
+ * @protected
+ * @type {number}
+ * @memberof Player
+ */
+ protected deadCount: number = 0;
+
+ /**
+ *
+ *
+ * @protected
+ * @type {number}
+ * @memberof Player
+ */
+ protected killCount: number = 0;
+
+ /**
+ * Creates an instance of Player.
+ * @param {string} id
+ * @param {string} name
+ * @param {number} x
+ * @param {number} y
+ * @param {number} angle
+ * @param {SocketIO.Socket} client
+ * @memberof Player
+ */
+ constructor(
+ public id: string,
+ public name: string,
+ public x: number,
+ public y: number,
+ public angle: number,
+ public client: SocketIO.Socket,
+ ) { }
+
+ /**
+ *
+ *
+ * @returns {number}
+ * @memberof Player
+ */
+ public getLife(): number {
+ return this.life;
+ }
+
+ /**
+ *
+ *
+ * @returns {number}
+ * @memberof Player
+ */
+ public getDead(): number {
+ return this.deadCount;
+ }
+
+ /**
+ *
+ *
+ * @returns {number}
+ * @memberof Player
+ */
+ public getKill(): number {
+ return this.killCount;
+ }
+
+ /**
+ *
+ *
+ * @memberof Player
+ */
+ public addKill(): void {
+ this.killCount++;
+ }
+
+ /**
+ *
+ *
+ * @memberof Player
+ */
+ public hit(): void {
+ if (this.life > 0) {
+ this.life -= this.lifeDamage;
+ }
+ }
+
+ /**
+ *
+ *
+ * @memberof Player
+ */
+ public dead(): void {
+ this.life = 100;
+ this.deadCount++;
+ }
+
+ /**
+ *
+ *
+ * @returns {*}
+ * @memberof Player
+ */
+ public toNetPackage(): any {
+ return {
+ id: this.id,
+ name: this.name,
+ x: this.x,
+ y: this.y,
+ angle: this.angle,
+ life: this.life,
+ };
+ }
+}
diff --git a/src/server/lib/WebSocketHandler.ts b/src/server/lib/WebSocketHandler.ts
new file mode 100644
index 0000000..e267df2
--- /dev/null
+++ b/src/server/lib/WebSocketHandler.ts
@@ -0,0 +1,97 @@
+import debug, { Debugger } from 'debug';
+import { Server as httpServer } from 'http';
+import { Server as httpsServer } from 'https';
+import { listen } from 'socket.io';
+import { WS_EVENTS } from '../../common/constants';
+import { PlayersHandler } from '../../common/PlayersHandler';
+import { PlayerEventsHandler } from '../events/PlayerEventsHandler';
+import { ServerEventsHandler } from '../events/ServerEventsHandler';
+const dinfo: Debugger = debug('ts-mp:server:ws');
+
+/**
+ *
+ *
+ * @export
+ * @class WebSockerHandler
+ */
+export class WebSocketHandler {
+ /**
+ *
+ *
+ * @protected
+ * @type {SocketIO.Server}
+ * @memberof WebSockerHandler
+ */
+ protected socket: SocketIO.Server;
+
+ /**
+ *
+ *
+ * @protected
+ * @type {ServerEventsHandler}
+ * @memberof WebSocketHandler
+ */
+ protected serverEventsHandler: ServerEventsHandler;
+
+ /**
+ *
+ *
+ * @protected
+ * @type {PlayerEventsHandler}
+ * @memberof WebSocketHandler
+ */
+ protected playerEventsHandler: PlayerEventsHandler;
+
+ /**
+ *
+ *
+ * @protected
+ * @type {{ [id: string]: SocketIO.Socket }}
+ * @memberof WebSockerHandler
+ */
+ protected clients: { [id: string]: SocketIO.Socket } = {};
+
+ /**
+ * Creates an instance of WebSockerHandler.
+ * @param {(httpServer | httpsServer)} server
+ * @param {PlayersHandler} playersHandler
+ * @memberof WebSockerHandler
+ */
+ constructor(
+ protected server: httpServer | httpsServer,
+ protected playersHandler: PlayersHandler,
+ ) { }
+
+ /**
+ *
+ *
+ * @memberof WebSockerHandler
+ */
+ public init(): void {
+ this.socket = listen(this.server);
+ this.socket.sockets.on(WS_EVENTS.SOCKET_CONNECTION, this.attachClientEventsOnConnection.bind(this));
+ this.serverEventsHandler = new ServerEventsHandler(this.socket, this.clients, this.playersHandler);
+ this.playerEventsHandler = new PlayerEventsHandler(this.socket, this.clients, this.playersHandler);
+
+ dinfo('WS Initialized, is secure:', this.server instanceof httpsServer);
+ }
+
+ /**
+ *
+ *
+ * @protected
+ * @param {SocketIO.Socket} client
+ * @memberof WebSockerHandler
+ */
+ protected attachClientEventsOnConnection(client: SocketIO.Socket): void {
+ dinfo('New player has connected: ', client.id);
+
+ // store client
+ this.clients[client.id] = client;
+
+ // Attach Events
+ this.serverEventsHandler.attachEvents(client);
+ this.playerEventsHandler.attachEvents(client);
+ }
+
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..b11da3d
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "esModuleInterop": true,
+ "noImplicitAny": true,
+ "target": "es5",
+ "outDir": "./build",
+ "allowJs": true,
+ "sourceMap": true
+ },
+ "include": [
+ "./src/**/*"
+ ],
+ "exclude": [
+ "node_modules",
+ "**/*.spec.ts",
+ "./src/client/*"
+ ]
+}
\ No newline at end of file
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 0000000..dc32a8b
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,14 @@
+{
+ "defaultSeverity": "error",
+ "extends": [
+ "tslint:recommended"
+ ],
+ "jsRules": {},
+ "rules": {
+ "no-console": [true, "log", "debug", "error"],
+ "object-literal-sort-keys": false,
+ "only-arrow-functions": false,
+ "quotemark": [true, "single", "avoid-escape"]
+ },
+ "rulesDirectory": []
+}
\ No newline at end of file