alexlcdee 8 роки тому
батько
коміт
d272f65b5a
11 змінених файлів з 727 додано та 433 видалено
  1. 3 0
      build/.env
  2. 3 0
      build/.gitignore
  3. 4 0
      package.json
  4. 46 0
      src/Api.ts
  5. 22 132
      src/App.ts
  6. 215 129
      src/Client.ts
  7. 257 167
      src/Conversation.ts
  8. 134 0
      src/Interfaces.ts
  9. 23 0
      src/Log.ts
  10. 19 4
      src/main.ts
  11. 1 1
      src/tsconfig.json

+ 3 - 0
build/.env

@@ -0,0 +1,3 @@
+MODE=prod
+HOST=127.0.0.1
+PORT=5000

+ 3 - 0
build/.gitignore

@@ -0,0 +1,3 @@
+*
+!.gitignore
+!.env

+ 4 - 0
package.json

@@ -1,10 +1,14 @@
 {
   "name": "socket-io-app",
   "version": "0.0.1",
+  "license": "UNLICENSED",
+  "repository": "local",
+  "description": "No description",
   "dependencies": {
     "@types/node": "^7.0.13",
     "@types/socket.io": "^1.4.29",
     "@types/socket.io-client": "^1.4.29",
+    "dotenv": "^4.0.0",
     "socket.io": "^1.7.3"
   }
 }

+ 46 - 0
src/Api.ts

@@ -0,0 +1,46 @@
+import {Interfaces} from "./Interfaces";
+
+export namespace Api {
+
+    export class NullConnector implements Interfaces.ApiConnectorInterface {
+        execute(endpointPath: string, data: any): Promise<string> {
+            return new Promise((resolve, reject) => {
+                reject(`NullConnector can't resolve API call`);
+            });
+        }
+    }
+
+    export class DefaultConnector implements Interfaces.ApiConnectorInterface {
+        private host: string;
+        private port: number;
+        private pathPrefix: string;
+
+        constructor(host: string, port: number, pathPrefix: string = '') {
+            this.host = host;
+            this.port = port;
+            this.pathPrefix = pathPrefix;
+        }
+
+        execute(path: string, data: any): Promise<string> {
+            return new Promise((resolve, reject) => {
+                let client = require('http').request({
+                    host: this.host,
+                    port: this.port,
+                    path: this.pathPrefix + path,
+                    method: 'POST'
+                }, response => {
+                    let responseMessage = '';
+                    response.on('data', (chunk) => {
+                        responseMessage += chunk;
+                    }).on('end', () => {
+                        resolve(responseMessage);
+                    }).on('error', (err) => {
+                        reject(err);
+                    });
+                });
+                client.write(JSON.stringify(data));
+                client.end();
+            });
+        }
+    }
+}

+ 22 - 132
src/App.ts

@@ -1,149 +1,39 @@
-
-import {Client} from "./Client";
-import * as SocketIO from "socket.io";
-import {Conversation} from "./Conversation";
+import {Interfaces} from "./Interfaces";
 
 export class App {
-    private clients: ClientsContainer = new ClientsContainer(this);
-    public conversations: { [index: string]: Conversation } = {};
-    private staticMappings = {};
+    private clientsManager: Interfaces.ClientsManagerInterface;
+    private apiConnector: Interfaces.ApiConnectorInterface;
+    private conversationsManager: Interfaces.ConversationsManagerInterface;
+    private logger: Interfaces.LoggerInterface;
+
+    constructor(apiConnector: Interfaces.ApiConnectorInterface,
+                clientsManager: Interfaces.ClientsManagerInterface,
+                conversationsManager: Interfaces.ConversationsManagerInterface,
+                logger: Interfaces.LoggerInterface,
+                config: AppConfigInterface) {
+        this.apiConnector = apiConnector;
+        this.clientsManager = clientsManager;
+        this.conversationsManager = conversationsManager;
+        this.logger = logger;
 
-    constructor(config: AppConfigInterface) {
-        let fs = require('fs');
-        if (config.host === undefined) {
-            config.host = '127.0.0.1';
-        }
         let httpServer = require('http').createServer((request, response) => {
-            //res.setHeader('Access-Control-Allow-Origin', 'galapsy.ru galapsy.com');
         }).listen(config.port, config.host);
+
         let socketIO = require('socket.io')(httpServer);
         socketIO.on('connection', (socket) => {
             socket.on('userConnect', (data) => {
-                this.addClient(socket, data);
+                this.logger.debug(`Client connected`);
+                this.clientsManager.add(socket, data);
             });
         });
-        console.log(`Server started at ${config.host}:${config.port}`);
-    }
-
-    getClient(id: string): Promise<Client> {
-        return new Promise((resolve, reject) => {
-            if (this.clients.get(id) !== undefined) {
-                resolve(this.clients.get(id));
-            } else {
-                this.apiCall('/user', {id: id, action: 'getinfo'}).then((response: string) => {
-                    let data = JSON.parse(response);
-                    this.clients[data.id] = new Client(this, data);
-                    resolve(this.clients.get(id));
-                }).catch(reject)
-            }
-        });
-    }
-
-    addClient(socket: SocketIO.Socket, data: {} = {}) {
-        this.clients.add(socket, data);
-    }
-
-    addConversation(conversation: Conversation) {
-        this.conversations[conversation.id] = conversation;
-    }
-
-    removeConversation(id: string) {
-        delete this.conversations[id];
-    }
-
-    getConversation(id: number): Promise<Conversation> {
-        return new Promise((resolve, reject) => {
-            if (this.conversations.hasOwnProperty(id)) {
-                resolve(this.conversations[id]);
-            } else {
-                this.apiCall('/conversations', {action: 'get', id: id}).then((response: string) => {
-                    let data = JSON.parse(response);
-                    let conversation = new Conversation(this);
-                    conversation.duration = parseInt(data.duration);
-                    conversation.id = data.id;
-                    this.getClient(data.initiatorId).then(client => {
-                        conversation.initiator = client;
-                        this.getClient(data.recipientId).then(client => {
-                            conversation.recipient = client;
-                            if (data.isStarted == 1 && data.isFinished == 0) {
-                                conversation.state = Conversation.STATE_RUNNING;
-                            }
-                            this.conversations[id] = conversation;
-                            resolve(this.conversations[id]);
-                        });
-                    });
-                }).catch(err => {
-                    reject(err);
-                });
-            }
-        });
-    }
 
-    apiCall(path: string, data: any): Promise<string> {
-        return new Promise((resolve, reject) => {
-            let client = require('http').request({
-                host: 'galapsy.ru',
-                port: 80,
-                path: '/api' + path,
-                method: 'POST'
-            }, response => {
-                let responseMessage = '';
-                response.on('data', (chunk) => {
-                    responseMessage += chunk;
-                }).on('end', () => {
-                    resolve(responseMessage);
-                }).on('error', (err) => {
-                    reject(err);
-                });
-            });
-            client.write(JSON.stringify(data));
-            client.end();
-        });
-    }
-}
-
-export interface MessagingClientData {
-    id: string;
-    name: string;
-    photo: string;
-    role: string;
-    payedTime: string;
-    timeToPay: number;
-    coefficient: string;
-}
-
-class ClientsContainer {
-    private app: App;
-    private clients: { [index: string]: Client } = {};
-
-    constructor(app: App) {
-        this.app = app;
-    }
-
-    add(socket: SocketIO.Socket, data) {
-        if (!this.clients.hasOwnProperty(data.id)) {
-            this.app.apiCall('/user', {id: data.id, action: 'getinfo'})
-                .then((response: string) => {
-                    let data = JSON.parse(response);
-                    this.clients[data.id] = new Client(this.app, data);
-                    this.clients[data.id].addSocket(socket);
-                });
-        } else {
-            this.clients[data.id].addSocket(socket);
-            this.app.apiCall('/user', {id: data.id, action: 'setstatus', value: true});
-        }
-    }
-
-    get(id: string) {
-        if (this.clients !== undefined && this.clients[id] !== undefined) {
-            return this.clients[id];
-        }
+        console.log(`Server started at ${config.host}:${config.port}`);
+        this.logger.debug(`App started in debug mode`);
     }
-
 }
 
-
 export interface AppConfigInterface {
-    host?: string;
+    host: string;
     port: number;
+    mode: Interfaces.AppMode;
 }

+ 215 - 129
src/Client.ts

@@ -1,162 +1,248 @@
-import {App} from "./App";
-import {MessagingClientData} from "./App";
-import {Conversation} from "./Conversation";
 import * as SocketIO from "socket.io";
+import {Interfaces} from "./Interfaces";
 
-export class Client {
-    private sockets: SocketIO.Socket[] = [];
-    private clientId;
-    private clientPhoto;
-    private clientName;
-    private _coefficient = 1;
-    private isOnline: boolean = false;
-    protected app: App;
 
-    conversations: { [index: string]: Conversation } = {};
+export namespace Clients {
+    interface MessagingClientData {
+        id: string;
+        name: string;
+        photo: string;
+        role: string;
+        payedTime: string;
+        timeToPay: number;
+        coefficient: string;
+    }
 
-    payedTime = 0;
-    timeToPay = 0;
+    export class NullManager implements Interfaces.ClientsManagerInterface {
+        get(id: string): Promise<Interfaces.ClientInterface> {
+            return new Promise((resolve, reject) => {
+                reject("NullManger can't return client")
+            });
+        }
 
-    get coeficient() {
-        return this._coefficient;
-    }
+        add(socket: SocketIO.Socket, data) {
 
-    get id() {
-        return this.clientId;
+        }
     }
 
-    set id(value) {
-        this.clientId = value.toString();
-    }
+    export class DefaultManager implements Interfaces.ClientsManagerInterface {
+        private apiConnector: Interfaces.ApiConnectorInterface;
+        private clients: { [index: string]: Client } = {};
+        private logger: Interfaces.LoggerInterface;
+        private conversationsManager: Interfaces.ConversationsManagerInterface;
 
-    set status(value: boolean) {
-        this.app.apiCall('/user', {id: this.id, action: 'setstatus', value: value});
-        this.isOnline = value;
-    }
+        constructor(apiConnector: Interfaces.ApiConnectorInterface, conversationsManager: Interfaces.ConversationsManagerInterface, logger: Interfaces.LoggerInterface) {
+            this.apiConnector = apiConnector;
+            this.conversationsManager = conversationsManager;
+            this.logger = logger;
+        }
 
-    get name() {
-        return this.clientName;
-    }
+        private create(id: string): Promise<Interfaces.ClientInterface> {
+            return new Promise((resolve, reject) => {
+                this.apiConnector.execute('/user', {id: id, action: 'getinfo'}).then((response: string) => {
+                    let data = JSON.parse(response);
+                    this.clients[id] = new Client(this.apiConnector, this, this.conversationsManager, this.logger, data);
+                    resolve(this.clients[id]);
+                }).catch(reject);
+            });
+        }
 
-    get photo() {
-        return this.clientPhoto;
-    }
+        add(socket: SocketIO.Socket, data) {
+            function setStatus(client: Interfaces.ClientInterface, logger: Interfaces.LoggerInterface) {
+                logger.debug(`Setting status of client with ID=${data.id} to "Online"`);
+                client.status = true;
+            }
 
-    get status(): boolean {
-        return this.isOnline;
-    }
+            if (!this.clients.hasOwnProperty(data.id)) {
+                this.create(data.id).then((client: Client) => {
+                    this.clients[data.id] = client;
+                    this.clients[data.id].addSocket(socket);
+                    setStatus(this.clients[data.id], this.logger);
+                }).catch((error: Error) => {
+                    console.log(error.message);
+                });
+            } else {
+                this.clients[data.id].addSocket(socket);
+                setStatus(this.clients[data.id], this.logger);
+            }
+        }
 
-    constructor(app: App, data: MessagingClientData) {
-        this.app = app;
-        this.status = true;
-        this.id = data.id;
-        this.clientPhoto = data.photo;
-        this.clientName = data.name;
-        this.payedTime = parseInt(data.payedTime);
-        this.timeToPay = data.timeToPay;
-        this._coefficient = parseInt(data.coefficient);
+        get(id: string): Promise<Interfaces.ClientInterface> {
+            this.logger.debug(`Get client with ID=${id}`);
+            return new Promise((resolve, reject) => {
+                if (this.clients[id] !== undefined) {
+                    this.logger.debug(`Client with ID=${id} is in list of clients`);
+                    resolve(this.clients[id]);
+                } else {
+                    this.logger.debug(`Load client with ID=${id} by API call`);
+                    this.create(id).then((client: Client) => {
+                        resolve(client);
+                    }).catch(reject);
+                }
+            });
+        }
     }
 
-    addSocket(socket: SocketIO.Socket) {
-        this.sockets.push(socket);
-        let id = this.sockets.length - 1;
-        socket.on('disconnect', (data) => {
+    export class Client implements Interfaces.ClientInterface {
+        private sockets: SocketIO.Socket[] = [];
+        private clientId;
+        private clientPhoto;
+        private clientName;
+        private _coefficient = 1;
+        private isOnline: boolean = false;
+        private logger: Interfaces.LoggerInterface;
+        private conversationsManager: Interfaces.ConversationsManagerInterface;
+        private clientsManager: Interfaces.ClientsManagerInterface;
+
+        protected api: Interfaces.ApiConnectorInterface;
+
+        conversations: { [index: string]: Interfaces.ConversationInterface } = {};
+
+        payedTime = 0;
+        timeToPay = 0;
+
+        get coefficient() {
+            return this._coefficient;
+        }
+
+        get id() {
+            return this.clientId;
+        }
+
+        set id(value) {
+            this.clientId = value.toString();
+        }
+
+        set status(value: boolean) {
+            this.api.execute('/user', {id: this.id, action: 'setstatus', value: value});
+            this.isOnline = value;
+        }
+
+        get name() {
+            return this.clientName;
+        }
+
+        get photo() {
+            return this.clientPhoto;
+        }
+
+        get status(): boolean {
+            return this.isOnline;
+        }
+
+        constructor(api: Interfaces.ApiConnectorInterface, clientsManager: Interfaces.ClientsManagerInterface, conversationsManager: Interfaces.ConversationsManagerInterface, logger: Interfaces.LoggerInterface, data: MessagingClientData) {
+            this.api = api;
+            this.clientsManager = clientsManager;
+            this.conversationsManager = conversationsManager;
+            this.logger = logger;
+            this.status = true;
+            this.id = data.id;
+            this.clientPhoto = data.photo;
+            this.clientName = data.name;
+            this.payedTime = parseInt(data.payedTime);
+            this.timeToPay = data.timeToPay;
+            this._coefficient = parseInt(data.coefficient);
+        }
+
+        addSocket(socket: SocketIO.Socket) {
+            this.sockets.push(socket);
+            let id = this.sockets.length - 1;
+            socket.on('disconnect', (data) => {
+                this.onDisconnect(id);
+            });
+            socket.on('chat-start-conversation', this.onStartConversation.bind(this));
+            socket.on('chat-accept-conversation', this.onAcceptConversation.bind(this));
+            socket.on('chat-run-conversation', this.onRunConversation.bind(this));
+            socket.on('chat-send-message', this.onSendMessage.bind(this));
+        }
+
+        send(event: string, data: {} = {}) {
+            for (let i in this.sockets) {
+                if (this.sockets.hasOwnProperty(i)) {
+                    (function (i) {
+                        setTimeout(() => {
+                            this.sockets[i].emit(event, data)
+                        }, 0);
+                    }).bind(this)(i);
+                }
+            }
+        }
+
+        sendMessage(data: {} = {}) {
+            this.send('chat-message', data);
+        }
+
+        sendError(data: string = '') {
+            this.send('chat-error', data);
+        }
+
+        private onDisconnect(id) {
             this.sockets.splice(id, 1);
             if (this.sockets.length === 0) {
                 this.status = false;
                 for (let id in this.conversations) {
                     if (this.conversations.hasOwnProperty(id)) {
-                        console.log(`Pause ${id}`);
-                        this.conversations[id].state = Conversation.STATE_PAUSED;
+                        this.logger.debug(`Pause ${id}`);
+                        this.conversations[id].state = Interfaces.ConversationState.PAUSED();
                     }
                 }
             }
-        });
-        socket.on('chat-start-conversation', this.onStartConversation.bind(this));
-        socket.on('chat-accept-conversation', this.onAcceptConversation.bind(this));
-        socket.on('chat-run-conversation', this.onRunConversation.bind(this));
-        socket.on('chat-send-message', this.onSendMessage.bind(this));
-    }
+        }
 
-    send(event: string, data: {} = {}) {
-        for (let i in this.sockets) {
-            if (this.sockets.hasOwnProperty(i)) {
-                (function (i) {
-                    setTimeout(() => {
-                        this.sockets[i].emit(event, data)
-                    }, 0);
-                }).bind(this)(i);
+        private onStartConversation(...args) {
+            let data = args[0];
+            try {
+                if (this.id === data.withPeer) {
+                    throw new Error(`You can't start conversation with yourself`);
+                }
+                let recipientId = data.withPeer;
+                this.clientsManager.get(recipientId).then((recipient: Client) => {
+                    this.conversationsManager.create(this, recipient).then((conversation: Interfaces.ConversationInterface) => {
+                        this.conversations[conversation.id] = conversation;
+                        let responseData = {
+                            userId: this.id,
+                            senderId: this.id,
+                            photo: this.clientPhoto,
+                            title: `Пользователь ${this.clientName} хочет начать общение`,
+                            text: '',
+                            conversationId: conversation.id,
+                        };
+                        conversation.getRecipient().send('chat-start-conversation', JSON.parse(JSON.stringify(responseData)));
+                        responseData.userId = recipientId;
+                        conversation.getInitiator().send('chat-start-conversation', JSON.parse(JSON.stringify(responseData)));
+                    });
+                });
+            } catch (e) {
+                this.sendError(e.message);
             }
         }
-    }
 
-    sendMessage(data: {} = {}) {
-        this.send('chat-message', data);
-    }
-
-    sendError(data: string = '') {
-        this.send('chat-error', data);
-    }
-
-    private onStartConversation(...args) {
-        let data = args[0];
-        try {
-            if (this.id === data.withPeer) {
-                throw new Error(`You can't start conversation with yourself`);
-            }
-            let conversation = new Conversation(this.app);
-            let recipientId = data.withPeer;
-            this.app.getClient(recipientId).then((recipient: Client) => {
-                conversation.initiator = this;
-                conversation.recipient = recipient;
-                conversation.init().then((response: string) => {
-                    let data = JSON.parse(response);
-                    conversation.id = data.id;
-                    conversation.state = Conversation.STATE_INIT;
-                    this.conversations[conversation.id] = conversation
-                    let responseData = {
-                        userId: this.id,
-                        senderId: this.id,
-                        photo: this.clientPhoto,
-                        title: `Пользователь ${this.clientName} хочет начать общение`,
-                        text: '',
-                        conversationId: conversation.id,
-                    };
-                    conversation.recipient.send('chat-start-conversation', JSON.parse(JSON.stringify(responseData)));
-                    responseData.userId = recipientId;
-                    conversation.initiator.send('chat-start-conversation', JSON.parse(JSON.stringify(responseData)));
-                })
+        private onAcceptConversation(data) {
+            this.conversationsManager.get(data.id).then((conversation: Interfaces.ConversationInterface) => {
+                conversation.getInitiator().send('chat-accept-conversation', {id: data.id});
+                conversation.getRecipient().send('chat-accept-conversation', {id: data.id});
+                conversation.state = Interfaces.ConversationState.RUNNING();
             });
-        } catch (e) {
-            this.sendError(e.message);
         }
-    }
 
-    private onAcceptConversation(...args) {
-        let data = args[0];
-        let conversation = this.app.conversations[data.id];
-        conversation.initiator.send('chat-accept-conversation', {id: data.id});
-        conversation.recipient.send('chat-accept-conversation', {id: data.id});
-        conversation.state = Conversation.STATE_RUNNING;
-    }
-
-    private onSendMessage(...args) {
-        let data = args[0];
-        this.app.getConversation(data.conversation).then((conversation: Conversation) => {
-            let to;
-            if (conversation.recipient.id == this.id) {
-                to = conversation.initiator.id;
-            } else {
-                to = conversation.recipient.id;
-            }
-            conversation.newMessage(data.message, this.id, to);
-        }).catch(err => console.log(err));
-    }
+        private onSendMessage(...args) {
+            let data = args[0];
+            this.conversationsManager.get(data.conversation).then((conversation: Interfaces.ConversationInterface) => {
+                let to;
+                if (conversation.getRecipient().id === this.id) {
+                    to = conversation.getInitiator().id;
+                } else {
+                    to = conversation.getRecipient().id;
+                }
+                conversation.newMessage(data.message, this.id, to);
+            }).catch(err => console.log(err));
+        }
 
-    private onRunConversation(...args) {
-        let data = args[0];
-        this.app.getConversation(data.id).then(conversation => {
-            conversation.state = Conversation.STATE_RUNNING
-        });
+        private onRunConversation(...args) {
+            let data = args[0];
+            this.conversationsManager.get(data.id).then(conversation => {
+                conversation.state = Interfaces.ConversationState.RUNNING();
+            });
+        }
     }
 }

+ 257 - 167
src/Conversation.ts

@@ -1,191 +1,281 @@
-import {App} from "./App";
-import {Client} from "./Client";
-
-export class Conversation {
-    private app: App;
-    private interval = null;
-    private syncInterval = null;
-
-    id: string;
-    initiator: Client;
-    recipient: Client;
-
-    duration: number = 0;
-
-    conversationState: (value?: number) => number;
-
-    static readonly STATE_INIT = 0;
-    static readonly STATE_RUNNING = 1;
-    static readonly STATE_STOPPED = 2;
-    static readonly STATE_PAUSED = 3;
-
-    constructor(app: App) {
-        let state;
-        this.app = app;
-        this.conversationState = (value?: number) => {
-            if (value !== undefined)
-                state = value;
-            return state;
-        };
+import {Interfaces} from "./Interfaces";
+
+export module Conversations {
+
+    export class NullManager implements Interfaces.ConversationsManagerInterface {
+        get(id: string): Promise<Interfaces.ConversationInterface> {
+            return undefined;
+        }
+
+        add(conversation: Interfaces.ConversationInterface) {
+        }
+
+        remove(id: string) {
+        }
+
+        create(initiator: Interfaces.ClientInterface, recipient: Interfaces.ClientInterface): Promise<Interfaces.ConversationInterface> {
+            return undefined;
+        }
+
     }
 
-    init(): Promise<string> {
-        return this.app.apiCall('/conversations', {action: 'create'}).then(response => {
-            let data = JSON.parse(response);
-            return this.app.apiCall('/conversations', {
-                action: 'init',
-                id: data.id,
-                peers: [this.initiator.id, this.recipient.id]
+    export class DefaultManager implements Interfaces.ConversationsManagerInterface {
+        private apiConnector: Interfaces.ApiConnectorInterface;
+        private logger: Interfaces.LoggerInterface;
+        private clientsManager: Interfaces.ClientsManagerInterface;
+        private conversations: { [id: string]: Interfaces.ConversationInterface } = {};
+
+        constructor(apiConnector: Interfaces.ApiConnectorInterface, clientsManager: Interfaces.ClientsManagerInterface, logger: Interfaces.LoggerInterface) {
+            this.apiConnector = apiConnector;
+            this.clientsManager = clientsManager;
+            this.logger = logger;
+        }
+
+        get(id: string): Promise<Conversation> {
+            this.logger.debug(`Get conversation with ID=${id}`);
+            return new Promise((resolve, reject) => {
+                if (this.conversations.hasOwnProperty(id)) {
+                    this.logger.debug(`Conversation with ID=${id} is already in list`);
+                    resolve(this.conversations[id]);
+                } else {
+                    this.logger.debug(`Load conversation with ID=${id} by API call`);
+                    this.apiConnector.execute('/conversations', {action: 'get', id: id})
+                        .then((response: string) => {
+                            let data = JSON.parse(response);
+                            this.clientsManager.get(data.initiatorId).then(initiator => {
+                                this.clientsManager.get(data.recipientId).then(recipient => {
+                                    let conversation = new Conversation(this.apiConnector, initiator, recipient, this.logger, data.id);
+                                    conversation.duration = parseInt(data.duration);
+                                    if (data.isStarted == 1 && data.isFinished == 0) {
+                                        conversation.state = Interfaces.ConversationState.RUNNING();
+                                    }
+                                    this.add(conversation);
+                                    resolve(this.conversations[id]);
+                                });
+                            });
+                        }).catch(reject);
+                }
             });
-        });
-    }
+        }
 
-    set state(value: number) {
-        if ([Conversation.STATE_INIT, Conversation.STATE_RUNNING, Conversation.STATE_PAUSED, Conversation.STATE_STOPPED].indexOf(value) === -1) {
-            throw new Error(`State ${value} not in list of possible values.`);
+        create(initiator: Interfaces.ClientInterface, recipient: Interfaces.ClientInterface): Promise<Interfaces.ConversationInterface> {
+            this.logger.debug(`Create new conversation`);
+            return new Promise((resolve, reject) => {
+                this.apiConnector.execute('/conversations', {action: 'create'})
+                    .then(response => {
+                        let data = JSON.parse(response);
+                        this.apiConnector.execute('/conversations', {
+                            action: 'init',
+                            id: data.id,
+                            peers: [initiator.id, recipient.id]
+                        }).then(response => {
+                            let data = JSON.parse(response);
+                            let conversation = new Conversation(this.apiConnector, initiator, recipient, this.logger, data.id);
+                            this.add(conversation);
+                            resolve(conversation);
+                        }).catch(reject);
+                    })
+                    .catch(reject);
+            });
         }
-        if (value !== this.state) {
-            this.conversationState(value);
-            if (value === Conversation.STATE_INIT) {
-                this.app.conversations[this.id] = this;
-                this.initiator.conversations[this.id] = this;
-                this.recipient.conversations[this.id] = this;
-            }
-            if (value === Conversation.STATE_RUNNING) {
-                this.setRunning();
-            }
-            if (value === Conversation.STATE_STOPPED || value === Conversation.STATE_PAUSED) {
-                this.setPaused();
-            }
-            if (value === Conversation.STATE_STOPPED) {
-                this.setStopped();
-            }
+
+        add(conversation: Interfaces.ConversationInterface) {
+            this.logger.debug(`Add conversation with ID=${conversation.id}`);
+            this.conversations[conversation.id] = conversation;
+        }
+
+        remove(id: string) {
+            this.logger.debug(`Delete conversation with ID=${id}`);
+            delete this.conversations[id];
         }
     }
 
-    private setRunning()
-    {
-        if (this.interval === null) {
-            let lastIntervalTick = Date.now();
-            this.interval = setInterval(() => {
-                let now = Date.now();
-                let interval = (now - lastIntervalTick) / 1000;
-                this.duration += interval;
-                lastIntervalTick = now;
-                this.initiator.payedTime -= Math.ceil(interval * this.recipient.coeficient);
-                this.recipient.payedTime += Math.ceil(interval * this.recipient.coeficient);
-                if (this.initiator.payedTime < 0) {
-                    this.state = Conversation.STATE_STOPPED;
+    export class Conversation implements Interfaces.ConversationInterface {
+        private interval = null;
+        private syncInterval = null;
+
+        private _id: string;
+
+        duration: number = 0;
+
+        private conversationState: (value?: Interfaces.ConversationState) => Interfaces.ConversationState;
+
+        private apiConnector: Interfaces.ApiConnectorInterface;
+        private initiator: Interfaces.ClientInterface;
+        private recipient: Interfaces.ClientInterface;
+        private logger: Interfaces.LoggerInterface;
+
+        constructor(apiConnector: Interfaces.ApiConnectorInterface, initiator: Interfaces.ClientInterface, recipient: Interfaces.ClientInterface, logger: Interfaces.LoggerInterface, id: string) {
+            this.apiConnector = apiConnector;
+            this.initiator = initiator;
+            this.recipient = recipient;
+            this.logger = logger;
+            this._id = id;
+            let state = Interfaces.ConversationState.INIT();
+            this.conversationState = (value?: Interfaces.ConversationState) => {
+                if (value !== undefined)
+                    state = value;
+                return state;
+            };
+
+            this.state = Interfaces.ConversationState.INIT();
+        }
+
+        getInitiator(): Interfaces.ClientInterface {
+            return this.initiator;
+        }
+
+        getRecipient(): Interfaces.ClientInterface {
+            return this.recipient;
+        }
+
+        get id(): string {
+            return this._id;
+        }
+
+        get state() {
+            return this.conversationState();
+        }
+
+        set state(state: Interfaces.ConversationState) {
+            if (!this.state.isEqualsTo(state)) {
+                this.conversationState(state);
+                if (state.isEqualsTo(Interfaces.ConversationState.INIT())) {
+                }
+                if (state.isEqualsTo(Interfaces.ConversationState.RUNNING())) {
+                    this.setRunning();
                 }
-            }, 1000);
+                if (state.isEqualsTo(Interfaces.ConversationState.STOPPED()) || state.isEqualsTo(Interfaces.ConversationState.PAUSED())) {
+                    this.setPaused();
+                }
+                if (state.isEqualsTo(Interfaces.ConversationState.STOPPED())) {
+                    this.setStopped();
+                }
+            }
         }
-        if (this.syncInterval === null) {
-            this.syncInterval = setInterval(() => {
-                this.sync();
-            }, 5000);
+
+        private setRunning() {
+            this.logger.debug(`Set conversation with ID=${this.id} running`);
+            if (this.interval === null) {
+                let lastIntervalTick = Date.now();
+                this.interval = setInterval(() => {
+                    let now = Date.now();
+                    let interval = (now - lastIntervalTick) / 1000;
+                    this.duration += interval;
+                    lastIntervalTick = now;
+                    this.initiator.payedTime -= Math.ceil(interval * this.recipient.coefficient);
+                    this.recipient.payedTime += Math.ceil(interval * this.recipient.coefficient);
+                    if (this.initiator.payedTime < 0) {
+                        this.state = Interfaces.ConversationState.STOPPED();
+                    }
+                }, 1000);
+            }
+            if (this.syncInterval === null) {
+                this.syncInterval = setInterval(() => {
+                    this.sync();
+                }, 5000);
+            }
+
+            this.apiConnector.execute('/conversations', {id: this.id, action: 'start'});
         }
 
-        this.app.apiCall('/conversations', {id: this.id, action: 'start'});
-    }
+        private sync() {
+            this.initiator.send('chat-sync-timer', {
+                conversationId: this.id,
+                duration: this.duration
+            });
 
-    private sync() {
-        this.initiator.send('chat-sync-timer', {
-            conversationId: this.id,
-            duration: this.duration
-        });
-
-        this.recipient.send('chat-sync-timer', {
-            conversationId: this.id,
-            duration: this.duration
-        });
-
-        this.app.apiCall('/conversations', {
-            id: this.id,
-            action: 'duration',
-            duration: this.duration
-        });
-
-        this.app.apiCall('/user', {
-            action: 'updatetime',
-            id: this.initiator.id,
-            value: this.initiator.payedTime
-        });
-
-        this.app.apiCall('/user', {
-            action: 'updatetime',
-            id: this.recipient.id,
-            value: this.recipient.payedTime
-        });
-    }
+            this.recipient.send('chat-sync-timer', {
+                conversationId: this.id,
+                duration: this.duration
+            });
 
-    private setPaused()
-    {
-        clearInterval(this.interval);
-        this.interval = null;
-        clearInterval(this.syncInterval);
-        this.syncInterval = null;
-        this.initiator.send('chat-conversation-stop', {id: this.id});
-        this.recipient.send('chat-conversation-stop', {id: this.id});
-    }
+            this.apiConnector.execute('/conversations', {
+                id: this.id,
+                action: 'duration',
+                duration: this.duration
+            });
 
-    private setStopped()
-    {
-        delete this.initiator.conversations[this.id];
-        delete this.recipient.conversations[this.id];
-        delete this.app.conversations[this.id];
-        this.app.apiCall('/conversations', {id: this.id, action: 'stop', duration: this.duration});
-        this.app.apiCall('/user', {
-            action: 'updatetime',
-            id: this.initiator.id,
-            value: this.initiator.payedTime
-        });
-        this.app.apiCall('/user', {
-            action: 'updatetime', id: this.recipient.id,
-            value: this.recipient.payedTime
-        });
-    }
+            this.apiConnector.execute('/user', {
+                action: 'updatetime',
+                id: this.initiator.id,
+                value: this.initiator.payedTime
+            });
 
-    get state() {
-        return this.conversationState();
-    }
+            this.apiConnector.execute('/user', {
+                action: 'updatetime',
+                id: this.recipient.id,
+                value: this.recipient.payedTime
+            });
+        }
+
+        private setPaused() {
+            this.logger.debug(`Set conversation with ID=${this.id} paused`);
+            clearInterval(this.interval);
+            this.interval = null;
+            clearInterval(this.syncInterval);
+            this.syncInterval = null;
+            this.initiator.send('chat-conversation-stop', {id: this.id});
+            this.recipient.send('chat-conversation-stop', {id: this.id});
+        }
 
-    private getPeer(id): Client {
-        let peers = [this.initiator, this.recipient];
-        for (let i in peers) {
-            if (peers.hasOwnProperty(i) && parseInt(peers[i].id) === parseInt(id)) {
-                return peers[i];
+        private setStopped() {
+            this.logger.debug(`Set conversation with ID=${this.id} stopped`);
+            delete this.initiator.conversations[this.id];
+            delete this.recipient.conversations[this.id];
+            this.apiConnector.execute('/conversations', {id: this.id, action: 'stop', duration: this.duration});
+            this.apiConnector.execute('/user', {
+                action: 'updatetime',
+                id: this.initiator.id,
+                value: this.initiator.payedTime
+            });
+            this.apiConnector.execute('/user', {
+                action: 'updatetime', id: this.recipient.id,
+                value: this.recipient.payedTime
+            });
+        }
+
+        private getPeer(id): Interfaces.ClientInterface {
+            let peers = [this.initiator, this.recipient];
+            for (let i in peers) {
+                if (peers.hasOwnProperty(i) && parseInt(peers[i].id) === parseInt(id)) {
+                    return peers[i];
+                }
             }
         }
-    }
 
-    newMessage(message, from, to) {
-        this.app.apiCall('/messages', {
-            action: 'send',
-            conversationId: this.id,
-            senderId: from,
-            recipientId: to,
-            text: message
-        }).then(response => {
-            let data = JSON.parse(response);
-            if (data.isSent) {
-                let sender = this.getPeer(data.senderId);
-                let recipient = this.getPeer(data.recipientId);
-                let messageData = {
-                    message: data.text,
+        newMessage(message, from, to) {
+            this.logger.debug(`New message to conversation with ID=${this.id}`);
+            if (this.state.isEqualsTo(Interfaces.ConversationState.RUNNING())) {
+                this.apiConnector.execute('/messages', {
+                    action: 'send',
                     conversationId: this.id,
-                    sender: {
-                        name: sender.name,
-                        photo: sender.photo
-                    },
-                    recipient: {
-                        name: recipient.name,
-                        photo: recipient.photo
+                    senderId: from,
+                    recipientId: to,
+                    text: message
+                }).then(response => {
+                    let data = JSON.parse(response);
+                    if (data.isSent) {
+                        let sender = this.getPeer(data.senderId);
+                        let recipient = this.getPeer(data.recipientId);
+                        let messageData = {
+                            message: data.text,
+                            conversationId: this.id,
+                            sender: {
+                                name: sender.name,
+                                photo: sender.photo
+                            },
+                            recipient: {
+                                name: recipient.name,
+                                photo: recipient.photo
+                            }
+                        };
+                        this.initiator.sendMessage(messageData);
+                        this.recipient.sendMessage(messageData);
                     }
-                };
-                this.initiator.sendMessage(messageData);
-                this.recipient.sendMessage(messageData);
+                }).catch(this.logger.error);
             }
-        }).catch(err => console.log(err));
-
+        }
     }
-}
+}
+

+ 134 - 0
src/Interfaces.ts

@@ -0,0 +1,134 @@
+export module Interfaces {
+    export interface LoggerInterface {
+        debug(message);
+
+        error(message);
+    }
+
+    export interface ApiConnectorInterface {
+        execute(endpointPath: string, data: any): Promise<string>;
+    }
+
+    export interface ClientInterface {
+        id: string;
+        coefficient: number;
+        status: boolean;
+        name: string;
+        photo: string;
+        payedTime: number;
+        timeToPay: number;
+        conversations: { [id: string]: ConversationInterface }
+
+        send(event: string, data: {});
+
+        addSocket(socket: SocketIO.Socket);
+
+        sendMessage(messageData);
+    }
+
+    export interface ClientsManagerInterface {
+        get(id: string): Promise<ClientInterface>
+
+        add(socket: SocketIO.Socket, data);
+    }
+
+    export interface ConversationsManagerInterface {
+        get(id: string): Promise<ConversationInterface>;
+
+        add(conversation: ConversationInterface);
+
+        remove(id: string);
+
+        create(initiator: ClientInterface, recipient: ClientInterface): Promise<ConversationInterface>;
+    }
+
+    export interface ConversationInterface {
+        id: string;
+        duration: number;
+        state: ConversationState;
+
+        getInitiator(): ClientInterface;
+
+        getRecipient(): ClientInterface;
+
+        newMessage(message, from, to);
+    }
+
+    export class ConversationState {
+        private value;
+
+        private constructor(state) {
+            if (ConversationState.availableStates().indexOf(state) < 0) {
+                throw new Error(`State "${state}" is not valid value`);
+            }
+            this.value = state;
+        }
+
+        toString() {
+            return this.value;
+        }
+
+        public isEqualsTo(state: ConversationState) {
+            return this.value === state.value;
+        }
+
+        static create(state) {
+            return new ConversationState(state);
+        }
+
+        private static availableStates() {
+            return ['init', 'running', 'stopped', 'paused'];
+        }
+
+        static INIT() {
+            return ConversationState.create('init');
+        }
+
+        static RUNNING() {
+            return ConversationState.create('running');
+        }
+
+        static STOPPED() {
+            return ConversationState.create('stopped');
+        }
+
+        static PAUSED() {
+            return ConversationState.create('paused');
+        }
+    }
+
+    export class AppMode {
+        private value;
+
+        private constructor(mode) {
+            this.value = mode;
+        }
+
+        toString() {
+            return this.value;
+        }
+
+        public isEqualsTo(mode: AppMode) {
+            return this.value === mode.value;
+        }
+
+        static create(mode) {
+            if (AppMode.availableModes().indexOf(mode) < 0) {
+                throw new Error(`Mode "${mode}" is not valid value`);
+            }
+            return new AppMode(mode);
+        }
+
+        private static availableModes() {
+            return ['dev', 'prod'];
+        }
+
+        static DEV() {
+            return AppMode.create('dev');
+        }
+
+        static PROD() {
+            return AppMode.create('prod');
+        }
+    }
+}

+ 23 - 0
src/Log.ts

@@ -0,0 +1,23 @@
+import {Interfaces} from "./Interfaces";
+
+export namespace Log {
+
+    export class ConsoleLogger implements Interfaces.LoggerInterface {
+        private mode: Interfaces.AppMode;
+
+        constructor(mode: Interfaces.AppMode) {
+            this.mode = mode;
+        }
+
+        debug(message) {
+            if (this.mode.isEqualsTo(Interfaces.AppMode.DEV())) {
+                console.log(`\x1b[30m\x1b[43m%s\x1b[0m: %s`, '[DEBUG]', message);
+            }
+        }
+
+        error(message) {
+            console.log(`\x1b[30m\x1b[41m%s\x1b[0m: %s`, '[ERROR]', message);
+        }
+
+    }
+}

+ 19 - 4
src/main.ts

@@ -1,10 +1,25 @@
-
 import {App} from "./App";
+import {Api} from "./Api";
+import {Conversations} from "./Conversation";
+import {Clients} from "./Client";
+import {Log} from "./Log";
+import {Interfaces} from "./Interfaces"
+
+require('dotenv').config({path: __dirname + "/.env"});
 
+let logger = new Log.ConsoleLogger(Interfaces.AppMode.create(process.env.MODE));
 try {
-    let app = new App({
-        port: 5000,
+    let apiConnector = new Api.DefaultConnector('galapsy.ru', 80, '/api');
+    let clientsManager = new Clients.NullManager();
+    let conversationsManager = new Conversations.DefaultManager(apiConnector, clientsManager, logger);
+    clientsManager = new Clients.DefaultManager(apiConnector, conversationsManager, logger);
+
+    new App(apiConnector, clientsManager, conversationsManager, logger, {
+        port: process.env.PORT,
+        host: process.env.HOST,
+        mode: Interfaces.AppMode.create(process.env.MODE)
     });
+
 } catch (e) {
-    console.log(e.message);
+    logger.debug(e.message);
 }

+ 1 - 1
src/tsconfig.json

@@ -1,7 +1,7 @@
 {
   "compilerOptions": {
     "module": "commonjs",
-    "target": "es6",
+    "target": "es2015",
     "sourceMap": true,
     "outDir": "../build"
   },