Procházet zdrojové kódy

Chat API server for refreshing users

alexlcdee před 8 roky
rodič
revize
1eaf97213c
6 změnil soubory, kde provedl 144 přidání a 22 odebrání
  1. 2 2
      src/App.ts
  2. 56 0
      src/ChatApi.ts
  3. 70 19
      src/Client.ts
  4. 1 0
      src/Conversation.ts
  5. 7 0
      src/Interfaces.ts
  6. 8 1
      src/main.ts

+ 2 - 2
src/App.ts

@@ -10,14 +10,14 @@ export class App {
                 clientsManager: Interfaces.ClientsManagerInterface,
                 conversationsManager: Interfaces.ConversationsManagerInterface,
                 logger: Interfaces.LoggerInterface,
+                apiServer: Interfaces.ServerInterface,
                 config: AppConfigInterface) {
         this.apiConnector = apiConnector;
         this.clientsManager = clientsManager;
         this.conversationsManager = conversationsManager;
         this.logger = logger;
 
-        let httpServer = require('http').createServer((request, response) => {
-        }).listen(config.port, config.host);
+        let httpServer = require('http').createServer(apiServer.processRequest.bind(apiServer)).listen(config.port, config.host);
 
         let socketIO = require('socket.io')(httpServer);
         socketIO.on('connection', (socket) => {

+ 56 - 0
src/ChatApi.ts

@@ -0,0 +1,56 @@
+import {Interfaces} from "./Interfaces"
+import * as HTTP from "http";
+import url = require("url");
+
+export namespace ChatApi {
+
+    export class ApiServer implements Interfaces.ServerInterface {
+        private clientsManager: Interfaces.ClientsManagerInterface;
+        private conversationsManager: Interfaces.ConversationsManagerInterface;
+        private logger: Interfaces.LoggerInterface;
+        private routes: RoutesCollection = {};
+
+        constructor(clientsManager: Interfaces.ClientsManagerInterface, conversationsManager: Interfaces.ConversationsManagerInterface, logger: Interfaces.LoggerInterface, routes: RoutesCollection) {
+            this.clientsManager = clientsManager;
+            this.conversationsManager = conversationsManager;
+            this.logger = logger;
+            this.routes = routes;
+
+            this.logger.debug(`Starting API server`);
+        }
+
+        processRequest(request: HTTP.IncomingMessage, response: HTTP.ServerResponse) {
+            let path = url.parse(request.url).pathname;
+            if (this.routes.hasOwnProperty(path)) {
+                this.logger.debug(`Processing action ${path}`);
+                let action = this.routes[path];
+                action.call(this, request, response);
+            } else {
+                this.logger.debug(`Action ${path} not found`);
+                response.writeHead(404);
+                response.end(`Action ${path} not found`);
+            }
+        }
+
+        refreshUsers(request: HTTP.IncomingMessage, response: HTTP.ServerResponse) {
+            let content = '';
+            request.on('data', (chunk) => {
+                content += chunk;
+            }).on('end', () => {
+                let data = JSON.parse(content);
+                this.clientsManager.get(data.id, true).then((client: Interfaces.ClientInterface) => {
+                    response.end('Ok');
+                }).catch((err: Error) => {
+                    this.logger.error(err.message);
+                    this.logger.error(err.stack);
+                    response.writeHead(500, {'Content-type': 'text/plain'});
+                    response.end(err.message + err.stack);
+                });
+            });
+        }
+    }
+
+    export interface RoutesCollection {
+        [path: string]: (request: HTTP.IncomingMessage, response: HTTP.ServerResponse) => void
+    }
+}

+ 70 - 19
src/Client.ts

@@ -1,8 +1,10 @@
 import * as SocketIO from "socket.io";
 import {Interfaces} from "./Interfaces";
+import {Conversations} from "./Conversation";
 
 
 export namespace Clients {
+
     interface MessagingClientData {
         id: string;
         name: string;
@@ -14,7 +16,7 @@ export namespace Clients {
     }
 
     export class NullManager implements Interfaces.ClientsManagerInterface {
-        get(id: string): Promise<Interfaces.ClientInterface> {
+        get(id: string, refresh: boolean = false): Promise<Interfaces.ClientInterface> {
             return new Promise((resolve, reject) => {
                 reject("NullManger can't return client")
             });
@@ -40,10 +42,14 @@ export namespace Clients {
         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);
+                    try {
+                        let data = JSON.parse(response);
+                        this.clients[id] = new Client(this.apiConnector, this, this.conversationsManager, this.logger, data);
+                        resolve(this.clients[id]);
+                    } catch (err) {
+                        reject(err);
+                    }
+                }, reject);
             });
         }
 
@@ -58,7 +64,7 @@ export namespace Clients {
                     this.clients[data.id] = client;
                     this.clients[data.id].addSocket(socket);
                     setStatus(this.clients[data.id], this.logger);
-                }).catch((error: Error) => {
+                }, (error: Error) => {
                     console.log(error.message);
                 });
             } else {
@@ -67,22 +73,40 @@ export namespace Clients {
             }
         }
 
-        get(id: string): Promise<Interfaces.ClientInterface> {
+        get(id: string, refresh: boolean = false): Promise<Interfaces.ClientInterface> {
             this.logger.debug(`Get client with ID=${id}`);
             return new Promise((resolve, reject) => {
-                if (this.clients[id] !== undefined) {
+                if (this.clients[id] !== undefined && !refresh) {
                     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);
+                    }, reject);
                 }
             });
         }
     }
 
+    export class SocketMessage {
+        private _eventName: string;
+        private _data: {};
+
+        constructor(eventName: string, data: {} = {}) {
+            this._eventName = eventName;
+            this._data = data;
+        }
+
+        get eventName(): string {
+            return this._eventName;
+        }
+
+        get data(): {} {
+            return this._data;
+        }
+    }
+
     export class Client implements Interfaces.ClientInterface {
         private sockets: SocketIO.Socket[] = [];
         private clientId;
@@ -93,6 +117,7 @@ export namespace Clients {
         private logger: Interfaces.LoggerInterface;
         private conversationsManager: Interfaces.ConversationsManagerInterface;
         private clientsManager: Interfaces.ClientsManagerInterface;
+        private messageQueue: SocketMessage[] = [];
 
         protected api: Interfaces.ApiConnectorInterface;
 
@@ -142,6 +167,28 @@ export namespace Clients {
             this.payedTime = parseInt(data.payedTime);
             this.timeToPay = data.timeToPay;
             this._coefficient = parseInt(data.coefficient);
+            setInterval(this.releaseQueue.bind(this), 10000);
+        }
+
+        private releaseQueue() {
+            for (let i in this.messageQueue) {
+                if (this.messageQueue.hasOwnProperty(i)) {
+                    this.sendToSockets(this.messageQueue[i].eventName, this.messageQueue[i].data);
+                }
+            }
+        }
+
+        private sendToSockets(event: string, data: {} = {}) {
+            this.logger.debug(`Send event "${event}" to client with ID=${this.id}`);
+            for (let i in this.sockets) {
+                if (this.sockets.hasOwnProperty(i)) {
+                    (function (i) {
+                        setTimeout(() => {
+                            this.sockets[i].emit(event, data)
+                        }, 0);
+                    }).bind(this)(i);
+                }
+            }
         }
 
         addSocket(socket: SocketIO.Socket) {
@@ -154,17 +201,14 @@ export namespace Clients {
             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));
+            socket.on('chat-stop-conversation', this.onStopConversation.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);
-                }
+            if (this.sockets.length > 0) {
+                this.sendToSockets(event, data);
+            } else {
+                this.messageQueue.push(new SocketMessage(event, data));
             }
         }
 
@@ -182,7 +226,7 @@ export namespace Clients {
                 this.status = false;
                 for (let id in this.conversations) {
                     if (this.conversations.hasOwnProperty(id)) {
-                        this.logger.debug(`Pause ${id}`);
+                        this.logger.debug(`Pause conversation with ID=${id} because client with ID=${this.id} is disconnected`);
                         this.conversations[id].state = Interfaces.ConversationState.PAUSED();
                     }
                 }
@@ -240,9 +284,16 @@ export namespace Clients {
 
         private onRunConversation(...args) {
             let data = args[0];
-            this.conversationsManager.get(data.id).then(conversation => {
+            this.conversationsManager.get(data.id).then((conversation: Interfaces.ConversationInterface) => {
                 conversation.state = Interfaces.ConversationState.RUNNING();
             });
         }
+
+        private onStopConversation(...args) {
+            let data = args[0];
+            this.conversationsManager.get(data.id).then((conversation: Interfaces.ConversationInterface) => {
+                conversation.state = Interfaces.ConversationState.STOPPED();
+            });
+        }
     }
 }

+ 1 - 0
src/Conversation.ts

@@ -166,6 +166,7 @@ export module Conversations {
                     this.initiator.payedTime -= Math.ceil(interval * this.recipient.coefficient);
                     this.recipient.payedTime += Math.ceil(interval * this.recipient.coefficient);
                     if (this.initiator.payedTime < 0) {
+                        this.logger.debug(`User with ID=${this.initiator.id} is ran out of money`);
                         this.state = Interfaces.ConversationState.STOPPED();
                     }
                 }, 1000);

+ 7 - 0
src/Interfaces.ts

@@ -1,3 +1,5 @@
+import * as HTTP from "http";
+
 export module Interfaces {
     export interface LoggerInterface {
         debug(message);
@@ -28,6 +30,7 @@ export module Interfaces {
 
     export interface ClientsManagerInterface {
         get(id: string): Promise<ClientInterface>
+        get(id: string, refresh: boolean): Promise<ClientInterface>
 
         add(socket: SocketIO.Socket, data);
     }
@@ -131,4 +134,8 @@ export module Interfaces {
             return AppMode.create('prod');
         }
     }
+
+    export interface ServerInterface {
+        processRequest(request: HTTP.IncomingMessage, response: HTTP.ServerResponse);
+    }
 }

+ 8 - 1
src/main.ts

@@ -4,6 +4,8 @@ import {Conversations} from "./Conversation";
 import {Clients} from "./Client";
 import {Log} from "./Log";
 import {Interfaces} from "./Interfaces"
+import {ChatApi} from "./ChatApi";
+import * as HTTP from "http";
 
 require('dotenv').config({path: __dirname + "/.env"});
 
@@ -13,8 +15,13 @@ try {
     let clientsManager = new Clients.NullManager();
     let conversationsManager = new Conversations.DefaultManager(apiConnector, clientsManager, logger);
     clientsManager = new Clients.DefaultManager(apiConnector, conversationsManager, logger);
+    let apiServer = new ChatApi.ApiServer(clientsManager, conversationsManager, logger, {
+        '/api/refreshUsers': function (request: HTTP.IncomingMessage, response: HTTP.ServerResponse) {
+            this.refreshUsers(request, response);
+        }
+    });
 
-    new App(apiConnector, clientsManager, conversationsManager, logger, {
+    new App(apiConnector, clientsManager, conversationsManager, logger, apiServer, {
         port: process.env.PORT,
         host: process.env.HOST,
         mode: Interfaces.AppMode.create(process.env.MODE)