import {Interfaces} from "./Interfaces"; import ConversationStateInterface = Interfaces.ConversationState; export module Conversations { import ClientInterface = Interfaces.ClientInterface; import ConversationInterface = Interfaces.ConversationInterface; export class NullManager implements Interfaces.ConversationsManagerInterface { get(id: string): Promise { return undefined; } add(conversation: Interfaces.ConversationInterface) { } remove(id: string) { } create(initiator: Interfaces.ClientInterface, recipient: Interfaces.ClientInterface): Promise { return undefined; } } 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 { 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); Promise.all([this.clientsManager.get(data.initiatorId), this.clientsManager.get(data.recipientId)]) .then((values: [ClientInterface, ClientInterface]) => { let initiator = values[0]; let recipient = values[1]; 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); }).catch(reject); } }); } create(initiator: Interfaces.ClientInterface, recipient: Interfaces.ClientInterface): Promise { this.logger.debug(`Create new conversation`); return new Promise((resolve, reject) => { return this.apiConnector.execute('/conversations', {action: 'create'}) .then(response => { let data = JSON.parse(response); return 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); }); } 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]; } } export class Conversation implements Interfaces.ConversationInterface { private interval = null; private syncInterval = null; private _id: string; duration: number = 0; private conversationState: (value?: Interfaces.ConversationState) => ConversationStateInterface; 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?: ConversationStateInterface) => { if (value !== undefined) state = value; return state; }; this.state = ConversationStateInterface.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)) { if (state.isEqualsTo(ConversationStateInterface.INIT())) { } if (state.isEqualsTo(ConversationStateInterface.RUNNING())) { this.setRunning(); } if (state.isEqualsTo(ConversationStateInterface.PAUSED())) { this.setPaused(); } if (state.isEqualsTo(ConversationStateInterface.STOPPED())) { this.setStopped(); } this.conversationState(state); } else { this.logger.debug(`Conversation state already set to "${state.toString()}"`); } } 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.round(interval * this.recipient.coefficient); this.recipient.payedTime += Math.round(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 = ConversationStateInterface.STOPPED(); } }, 1000); } if (this.syncInterval === null) { this.syncInterval = setInterval(() => { this.sync(); }, 5000); } this.apiConnector.execute('/conversations', {id: this.id, action: 'start'}); } private sync() { this.initiator.send('chat-sync-timer', { conversationId: this.id, duration: Math.round(this.duration) }); this.recipient.send('chat-sync-timer', { conversationId: this.id, duration: Math.round(this.duration) }); this.apiConnector.execute('/conversations', { id: this.id, action: 'duration', duration: Math.round(this.duration) }); this.apiConnector.execute('/user', { action: 'updatetime', id: this.initiator.id, value: Math.round(this.initiator.payedTime) }); this.apiConnector.execute('/user', { action: 'updatetime', id: this.recipient.id, value: Math.round(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-pause', {id: this.id}); this.recipient.send('chat-conversation-pause', {id: this.id}); } private setStopped() { this.logger.debug(`Set conversation with ID=${this.id} stopped`); clearInterval(this.interval); this.interval = null; clearInterval(this.syncInterval); this.syncInterval = null; delete this.initiator.conversations[this.id]; delete this.recipient.conversations[this.id]; this.apiConnector.execute('/conversations', {id: this.id, action: 'stop', duration: Math.round(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 }); this.initiator.send('chat-conversation-stop', {id: this.id}); this.recipient.send('chat-conversation-stop', {id: this.id}); } 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.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, 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); } }).catch(this.logger.error); } } } }