alexlcdee 8 jaren geleden
commit
47d9a94df6
7 gewijzigde bestanden met toevoegingen van 439 en 0 verwijderingen
  1. 17 0
      .gitignore
  2. 0 0
      build/.gitignore
  3. 10 0
      package.json
  4. 246 0
      src/App.ts
  5. 145 0
      src/Client.ts
  6. 10 0
      src/main.ts
  7. 11 0
      src/tsconfig.json

+ 17 - 0
.gitignore

@@ -0,0 +1,17 @@
+.idea
+
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+
+build/*
+!build/.gitignore
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Optional npm cache directory
+.npm

+ 0 - 0
build/.gitignore


+ 10 - 0
package.json

@@ -0,0 +1,10 @@
+{
+  "name": "socket-io-app",
+  "version": "0.0.1",
+  "dependencies": {
+    "@types/node": "^7.0.13",
+    "@types/socket.io": "^1.4.29",
+    "@types/socket.io-client": "^1.4.29",
+    "socket.io": "^1.7.3"
+  }
+}

+ 246 - 0
src/App.ts

@@ -0,0 +1,246 @@
+/**
+ * Created by lcdee on 19.04.2017.
+ */
+
+import {Client} from "./Client";
+import * as SocketIO from "socket.io";
+
+export class App {
+    private clients: ClientsContainer = new ClientsContainer(this);
+    public conversations: { [index: string]: Conversation } = {};
+    private staticMappings = {};
+
+    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);
+            });
+        });
+        console.log(`Server started at ${config.host}:${config.port}`);
+    }
+
+    getClient(id: string): Client {
+        try {
+            return this.clients.get(id);
+        } catch (e) {
+            console.log(e.message);
+        }
+    }
+
+    addClient(socket: SocketIO.Socket, data: {} = {}) {
+        this.clients.add(socket, data);
+    }
+
+    removeClient(id: string) {
+        try {
+            this.clients.remove(id);
+        } catch (e) {
+            console.log(e.message);
+        }
+    }
+
+    addConversation(conversation: Conversation) {
+        this.conversations[conversation.id] = conversation;
+    }
+
+    removeConversation(id: string) {
+        delete this.conversations[id];
+    }
+
+    apiCall(path: string, data: any, success: (data?: string) => void = () => {
+    }, fail: (err?: string) => void = () => {
+    }) {
+        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', () => {
+                success(responseMessage);
+            }).on('error', (err) => {
+                fail(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'}, response => {
+                let data = JSON.parse(response);
+                this.clients[data.id] = new Client(this.app, data);
+                this.clients[data.id].addSocket(socket);
+                this.app.apiCall('/user', {id: data.id, action: 'setstatus', value: true});
+            });
+        } else {
+            this.clients[data.id].addSocket(socket);
+        }
+    }
+
+    get(id: string) {
+        if (this.clients !== undefined && this.clients[id] !== undefined) {
+            return this.clients[id];
+        }
+        throw new Error(`Client ${id} not found`);
+    }
+
+    remove(id: string) {
+        if (this.clients !== undefined && this.clients[id] !== undefined) {
+            delete this.clients[id];
+        } else {
+            throw new Error(`Client ${id} not found`);
+        }
+    }
+
+}
+
+export class Conversation {
+    private app: App;
+    private interval;
+    private syncInterval;
+
+    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;
+        };
+    }
+
+    init(success: (response) => void, fail: (err) => void) {
+        this.app.apiCall('/conversations', {'action': 'create'}, success, fail);
+    }
+
+    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.`);
+        }
+        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) {
+            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;
+                }
+            }, 1000);
+            this.syncInterval = setInterval(() => {
+                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
+                });
+            }, 5000);
+            this.app.apiCall('/conversations', {id: this.id, action: 'start'});
+        }
+        if (value === Conversation.STATE_STOPPED || value === Conversation.STATE_PAUSED) {
+            clearInterval(this.interval);
+            clearInterval(this.syncInterval);
+        }
+        if (value === Conversation.STATE_STOPPED) {
+            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
+            });
+        }
+    }
+
+    get state() {
+        return this.conversationState();
+    }
+
+    newMessage(message) {
+        this.initiator.sendMessage({message: message, duration: this.duration});
+        this.recipient.sendMessage({message: message, duration: this.duration});
+    }
+}
+
+export interface AppConfigInterface {
+    host?: string;
+    port: number;
+}

+ 145 - 0
src/Client.ts

@@ -0,0 +1,145 @@
+/**
+ * Created by lcdee on 19.04.2017.
+ */
+import {App} from "./App";
+import {MessagingClientData} from "./App";
+import {Conversation} from "./App";
+import * as SocketIO from "socket.io";
+
+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 } = {};
+
+    payedTime = 0;
+    timeToPay = 0;
+
+    get coeficient() {
+        return this._coefficient;
+    }
+
+    get id() {
+        return this.clientId;
+    }
+
+    set id(value) {
+        this.clientId = value.toString();
+    }
+
+    set status(value: boolean) {
+        this.app.apiCall('/user', {id: this.id, action: 'setstatus', value: value});
+        this.isOnline = value;
+    }
+
+    get status(): boolean {
+        return this.isOnline;
+    }
+
+    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);
+        console.log(this);
+    }
+
+    addSocket(socket: SocketIO.Socket) {
+        this.sockets.push(socket);
+        let id = this.sockets.length - 1;
+        socket.on('disconnect', (data) => {
+            this.sockets.splice(id, 1);
+            if (this.sockets.length === 0) {
+                this.status = false;
+                this.app.removeClient(this.id);
+                for (let id in this.conversations) {
+                    if (this.conversations.hasOwnProperty(id)) {
+                        this.conversations[id].state = Conversation.STATE_PAUSED;
+                    }
+                }
+            }
+        });
+        socket.on('chat-start-conversation', this.onStartConversation.bind(this));
+        socket.on('chat-accept-conversation', this.onAcceptConversation.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 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);
+            conversation.initiator = this;
+            conversation.recipient = this.app.getClient(data.withPeer);
+            conversation.init(response => {
+                console.log(response);
+                try {
+                    let data = JSON.parse(response);
+                    conversation.id = data.id;
+                    conversation.state = Conversation.STATE_INIT;
+                    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 = conversation.recipient.id;
+                    conversation.initiator.send('chat-start-conversation', JSON.parse(JSON.stringify(responseData)));
+                } catch (e) {
+                    console.log(e);
+                }
+            }, err => {
+                console.log(`Error: ${err}`);
+            });
+        } 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.conversations[data.conversation].newMessage(data.message);
+    }
+}

+ 10 - 0
src/main.ts

@@ -0,0 +1,10 @@
+
+import {App} from "./App";
+
+try {
+    let app = new App({
+        port: 5000,
+    });
+} catch (e) {
+    console.log(e.message);
+}

+ 11 - 0
src/tsconfig.json

@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "module": "commonjs",
+    "target": "es5",
+    "sourceMap": true,
+    "outDir": "../build"
+  },
+  "exclude": [
+    "../../node_modules"
+  ]
+}