UNPKG

django-rest-messaging-js

Version:

Javascript consumer for django-rest-messaging and django-rest-messaging-centrifugo

678 lines (559 loc) 20.4 kB
var AppDispatcher = require('../dispatcher/AppDispatcher'); var EventEmitter = require('events').EventEmitter; var assign = require('object-assign'); var MessageConstants = require('../constants/MessageConstants'); var MessageQueries = require('../queries/MessageQueries'); var $ = require('jquery'); // the user's login status var _loggedInParticipantId = null; // the info about the thread (name, participants, etc) var _currentThreadEmpty = { id: null, name: null, participants: [], }; var _currentThread = $.extend(true, {}, _currentThreadEmpty); // the user's messages FOR THE CURRENT THREAD // this is a json response which is paginated var _currentThreadPaginatedMessagesEmpty = { results: [], next: null, previous: null, count: 0 }; var _currentThreadPaginatedMessages = $.extend(true, {}, _currentThreadPaginatedMessagesEmpty); // we clone without assignment // the user's threads var _lastMessageOfAllThreadsPaginated = { results: [], next: null, previous: null, count: 0 }; // the notifications count var _notificationsThreadsIds = []; // we keep track of the last emit for testing purpose var _lastEmit; // we keep track of the last dispatched action for testing purpose var _lastDispatchedAction; // we track information about temporary threads when created var _threadsForm = null; // we track the potential recipients var _recipients = []; // we track the last quit thread var _lastQuitThreadId = null; // Extend ProductStore with EventEmitter to add eventing capabilities var MessageStore = assign({}, EventEmitter.prototype, { /* First part: login status of the user */ getLoggedInParticipantId: function() { /* * Returns the id of the user/participant if he is logged in, null otherwise. */ return _loggedInParticipantId; }, setLoggedInParticipantId: function(userId) { /* * Sets _LoggedInParticipantId to the provided id and emits a change. */ // we change the status _loggedInParticipantId = userId; this.callbackDispatch(MessageConstants.MESSAGES_SET_LOGIN_STATUS, userId); }, checkIfAuthenticated: function() { var _this = this; MessageQueries. checkIfAuthenticated(). done(function(participant){ _this.setLoggedInParticipantId(participant.id); }); }, addLoginStatusChangeListener: function(callback) { /* * This can be used by our components to modify their state when they mount. */ this.on(MessageConstants.MESSAGES_SET_LOGIN_STATUS, callback); }, removeLoginStatusChangeListener: function(callback) { /* * This can be used by our components to modify their state when they unmount. */ this.removeListener(MessageConstants.MESSAGES_SET_LOGIN_STATUS, callback); }, /* Second part: handling threads and messages */ getCurrentThread: function() { /* * We return the current thread. */ return _currentThread; }, _updateCurrentThread: function(thread) { /* * We set the current thread. */ // we update the thread _currentThread = thread; // we emit this.callbackDispatch(MessageConstants.MESSAGES_THREAD_INFO_UPDATED, thread); }, setCurrentThread: function(threadId) { /* * We set the current thread's id. */ var _this = this; if (this.getCurrentThread().id != threadId){ // we update the thread _currentThread.id = threadId; _currentThreadPaginatedMessages = $.extend(true, {}, _currentThreadPaginatedMessagesEmpty); // we clone without assignment // we query its content MessageQueries. retrieveThread(threadId). done(function(thread){ _this._updateCurrentThread(thread); _this.queryMessagesInThread(thread.id, null); // we mark it as read MessageQueries.markAsRead(thread.id); }); } }, initializeNewThread: function() { /* * We set the current thread's id. */ var _this = this; _currentThread = $.extend(true, {}, _currentThreadEmpty); _currentThread.id = 0; _currentThreadPaginatedMessages = $.extend(true, {}, _currentThreadPaginatedMessagesEmpty); // we clone without assignment this.callbackDispatch(MessageConstants.MESSAGES_THREAD_INFO_UPDATED, _currentThread); this.callbackDispatch(MessageConstants.MESSAGES_CURRENT_THREAD_UPDATED, _currentThreadPaginatedMessages); }, createThread: function(threadName, participantsIdsArray) { var _this = this; return MessageQueries. createThread(threadName, participantsIdsArray). done(function(thread){ _this.setCurrentThread(thread.id); }); }, addThreadParticipants: function(threadId, participantsIds) { var _this = this; MessageQueries. addParticipantsToThread(threadId, participantsIds). done(function(thread){ // if the thread is the current one, we update if (thread.id == _this.getCurrentThread().id){ _this._updateCurrentThread(thread); } }); }, removeThreadParticipant: function(threadId, participantId) { var _this = this; MessageQueries. removeParticipantFromThread(threadId, participantId). done(function(thread){ if (thread.id == _this.getCurrentThread().id){ _this._updateCurrentThread(thread); } }); }, getCurrentThreadPaginatedMessages: function() { /* * We return the current thread with its messages */ return _currentThreadPaginatedMessages; }, queryMessagesInThread: function(threadId, nextUrl) { /* * The user gets a new thread, we must query the messages in this thread. * var nextUrl is the url returned by Django's paginator. */ var _this = this; // we query the messages MessageQueries. listMessagesInThread(threadId, nextUrl). done(function(jsonResponse){ _this.setMessagesInThread(jsonResponse); }); }, _handleMessagesResults: function(currentMessages, newMessages, sortOrder) { /* * Adding two sets of messages. */ // we get all the ids of the current messages var currentMessagesIds = currentMessages.map(function(message) {return message.id; }) // we ensure we have current messages if (currentMessages && currentMessages.length > 0){ // we add the new messages if they are not duplicates $.each(newMessages, function(index, message){ if($.inArray(message.id, currentMessagesIds) === -1) currentMessages.push(message); }); } else { currentMessages = newMessages; } // we sort the messages by date if (sortOrder == "ascending") { return currentMessages.sort(function(m1,m2){ return (m1.id > m2.id); }); } else { return currentMessages.sort(function(m1,m2){ return (m1.id < m2.id); }); } }, setMessagesInThread: function(jsonResponse) { /* * This methods adds messages to the current Thread. * If messages are already present, the new one are simply added. */ // we set the current messages _currentThreadPaginatedMessages.results = this._handleMessagesResults(_currentThreadPaginatedMessages.results, jsonResponse.results, 'ascending'); _currentThreadPaginatedMessages.next = jsonResponse.next; _currentThreadPaginatedMessages.previous = jsonResponse.previous; _currentThreadPaginatedMessages.count = jsonResponse.count; // we emit this.callbackDispatch(MessageConstants.MESSAGES_CURRENT_THREAD_UPDATED, jsonResponse) }, setSingleMessagesInThread: function(message) { /* * This methods adds messages to the current Thread. * Usually single messages are pushed via sockets. */ // we add the message to the current conversation // we also check it is not already in it (may happen when using centrifuge since the message is posted to the channel) var currentMessagesIds = _currentThreadPaginatedMessages.results.map(function(message, index){ return message.id; }); if(message.thread == this.getCurrentThread().id && $.inArray(message.id, currentMessagesIds) === -1){ _currentThreadPaginatedMessages.results.push(message); _currentThreadPaginatedMessages.count = _currentThreadPaginatedMessages.count + 1; } // we emit this.callbackDispatch(MessageConstants.MESSAGES_CURRENT_THREAD_UPDATED, message) }, addThreadInfoChangeListener: function(callback) { /* * This can be used by our components to modify their state when they mount. */ this.on(MessageConstants.MESSAGES_THREAD_INFO_UPDATED, callback); }, removeThreadInfoChangeListener: function(callback) { /* * This can be used by our components to modify their state when they unmount. */ this.removeListener(MessageConstants.MESSAGES_THREAD_INFO_UPDATED, callback); }, addThreadChangeListener: function(callback) { /* * This can be used by our components to modify their state when they mount. */ this.on(MessageConstants.MESSAGES_CURRENT_THREAD_UPDATED, callback); }, removeThreadChangeListener: function(callback) { /* * This can be used by our components to modify their state when they unmount. */ this.removeListener(MessageConstants.MESSAGES_CURRENT_THREAD_UPDATED, callback); }, getLastMessageOfAllThreads: function() { /* * We return the current thread with its messages */ return _lastMessageOfAllThreadsPaginated; }, queryLastMessageOfAllThreads: function(nextUrl) { /* * We return the current thread with its messages */ var _this = this; MessageQueries. listLastMessagesOfAllThreads(nextUrl). done(function(jsonResponse){ // we set the current messages _this.setLastMessageOfAllThreads(jsonResponse); }); }, setLastMessageOfAllThreads: function(jsonResponse) { /* * We set the list of the threads with all messages */ _lastMessageOfAllThreadsPaginated.results = this._handleMessagesResults(_lastMessageOfAllThreadsPaginated.results, jsonResponse.results, 'descending'); _lastMessageOfAllThreadsPaginated.next = jsonResponse.next; _lastMessageOfAllThreadsPaginated.previous = jsonResponse.previous; _lastMessageOfAllThreadsPaginated.count = jsonResponse.count; // we check the notifications this.setNotificationsThreadsIds(_lastMessageOfAllThreadsPaginated); // we dispatch this.callbackDispatch(MessageConstants.MESSAGES_ALL_THREADS_UPDATED, jsonResponse) }, setSingleMessagesInLastThreads: function(message) { /* * This methods adds a single message to the list of all thread (being the last entry, it closes the thread). * Usually single messages are pushed via sockets. */ // we add the message to the current conversation for (var i = 0; i < _lastMessageOfAllThreadsPaginated.results.length; i++) { if(_lastMessageOfAllThreadsPaginated.results[i].thread == message.thread) { // replace and reorder list message.readers = []; // no one has read it yet _lastMessageOfAllThreadsPaginated.results[_lastMessageOfAllThreadsPaginated.results.indexOf(_lastMessageOfAllThreadsPaginated.results[i])] = message; _lastMessageOfAllThreadsPaginated.results.sort(function(a,b){ return (a.id < b.id); }); } } // we check the notifications this.setNotificationsThreadsIds(_lastMessageOfAllThreadsPaginated); // we dispacth this.callbackDispatch(MessageConstants.MESSAGES_ALL_THREADS_UPDATED, message) }, addLastMessageOfAllThreadsChangeListener: function(callback) { /* * This can be used by our components to modify their state when they mount. */ this.on(MessageConstants.MESSAGES_ALL_THREADS_UPDATED, callback); }, removeLastMessageOfAllThreadsChangeListener: function(callback) { /* * This can be used by our components to modify their state when they unmount. */ this.removeListener(MessageConstants.MESSAGES_ALL_THREADS_UPDATED, callback); }, addSingleMessage: function(message) { /* * We only have one message, we append it to the thread list and to it's conversation if it is currently featured. * This function is used when messages are added using sockets. */ // we add it to the current conversation this.setSingleMessagesInThread(message); this.setSingleMessagesInLastThreads(message); // we do not emit anything, this is done in the corresponding functions }, postMessage: function(threadId, messageBody) { /* * Post message and add it to the conversation. */ _this = this; return MessageQueries.postMessage(threadId, messageBody) .done(function(jsonResponse){ _this.addSingleMessage(jsonResponse); }); }, // chain thread and message form getThreadForm: function(){ return _threadsForm; }, setThreadForm: function(threadsForm) { _threadsForm = threadsForm; this.callbackDispatch(MessageConstants.MESSAGES_NEW_THREAD_FORM_UPDATED, threadsForm); }, submitThreadForm: function() { return _threadsForm.saveThreadWithRecipients(); }, addNewThreadFormChangeListener: function(callback) { /* * This can be used by our components to modify their state when they mount. */ this.on(MessageConstants.MESSAGES_NEW_THREAD_FORM_UPDATED, callback); }, removeNewThreadFormChangeListener: function(callback) { /* * This can be used by our components to modify their state when they unmount. */ this.removeListener(MessageConstants.MESSAGES_NEW_THREAD_FORM_UPDATED, callback); }, /* Notifications */ getNotificationsCount: function() { return _notificationsThreadsIds.length; }, _pushToNotificationsThreadsIds: function(message) { // we do not allow duplicates // we ensure the message is not posted by the current participant if ( _notificationsThreadsIds.indexOf(message.thread) == -1 && message.sender != this.getLoggedInParticipantId() ) { _notificationsThreadsIds.push(message.thread); } }, setNotificationsThreadsIds: function(messagesArrayOrInstance) { /* * Sets the notification count. * Should be called when MessageConstants.MESSAGES_ALL_THREADS_UPDATED is used. */ // we ensure the message has is_notification set as true // the python backends cares for determining when is_notification should be true (ie: not written by the current user etc.) if(messagesArrayOrInstance.hasOwnProperty('results')){ // we have an array // we try to add every notification for (var i = 0; i < messagesArrayOrInstance.results.length; i++) { if(messagesArrayOrInstance.results[i].is_notification == true && messagesArrayOrInstance.results[i].sender != this.getLoggedInParticipantId()){ this._pushToNotificationsThreadsIds(messagesArrayOrInstance.results[i]); } } } else if(messagesArrayOrInstance.hasOwnProperty('is_notification') && messagesArrayOrInstance.is_notification == true && messagesArrayOrInstance.sender != this.getLoggedInParticipantId()) { this._pushToNotificationsThreadsIds(messagesArrayOrInstance); } }, _removeNotificationStatus: function(message) { /* * Marks notifications as checked. */ message.is_notification = false; }, _iterateAllMessagesWithNotificationStatus: function(messageArray) { /* * Iterate through all messages with the is_notification attribute. * Returns true if the is_notification attribute of at least one message has been set to false. */ var modified = false; for (var i = 0; i < messageArray.length; i++) { if(messageArray[i].is_notification == true){ this._removeNotificationStatus(messageArray[i]); modified = true; } } return modified; }, notificationsChecked: function() { /* * Tells the notifications are checked. * Tells _currentThreadPaginatedMessagesEmpty and _currentThreadPaginatedMessages about it. */ _this = this; return MessageQueries. notificationsChecked(). done(function(jsonResponse){ // we mark the conversations as non notification // we check if there are modifications in the current thread before re-parsing it var notificationsRemovedFromCurrentThread = _this._iterateAllMessagesWithNotificationStatus(_currentThreadPaginatedMessages.results); if(notificationsRemovedFromCurrentThread == true) { // the current thread has been modified, // we dispatch it _this.callbackDispatch(MessageConstants.MESSAGES_CURRENT_THREAD_UPDATED, _currentThreadPaginatedMessages) } // we check if there are modifications to the last messages of all thread before re-parsing it var notificationsRemovedFromAllThreads = _this._iterateAllMessagesWithNotificationStatus(_lastMessageOfAllThreadsPaginated.results); if(notificationsRemovedFromAllThreads == true) { // the current thread has been modified, // we dispatch it _this.callbackDispatch(MessageConstants.MESSAGES_ALL_THREADS_UPDATED, _lastMessageOfAllThreadsPaginated); } // reinitialize _notificationsThreadsIds = []; }); }, getRecipients: function(recipients) { return _recipients; }, setRecipients: function(recipients) { _recipients = recipients; this.callbackDispatch(MessageConstants.MESSAGES_POTENTIAL_RECIPIENTS_UPDATED, recipients); }, addRecipientsListener: function(callback) { this.on(MessageConstants.MESSAGES_POTENTIAL_RECIPIENTS_UPDATED, callback); }, removeRecipientsListener: function(callback) { this.removeListener(MessageConstants.MESSAGES_POTENTIAL_RECIPIENTS_UPDATED, callback); }, getlastQuitThreadId: function(){ return _lastQuitThreadId; }, quitCurrentThread: function(threadId) { /* * Tells the notifications are checked. * Tells _currentThreadPaginatedMessagesEmpty and _currentThreadPaginatedMessages about it. */ if(! threadId){ return; } _this = this; return MessageQueries. removeParticipantFromThread(threadId, this.getLoggedInParticipantId()). done(function(jsonResponse){ // we have no current hread anymore _currentThread = $.extend(true, {}, _currentThreadEmpty); _currentThreadPaginatedMessages = $.extend(true, {}, _currentThreadPaginatedMessagesEmpty); // we clone without assignment // we remove the thread from the list var index = null; for (var i = 0; i < _lastMessageOfAllThreadsPaginated.results.length; i++) { if(_lastMessageOfAllThreadsPaginated.results[i].thread == threadId){ index = _lastMessageOfAllThreadsPaginated.results.indexOf(_lastMessageOfAllThreadsPaginated.results[i]); break; } } if(index != null){ _lastMessageOfAllThreadsPaginated.results.splice(index, 1); _lastMessageOfAllThreadsPaginated.count = _lastMessageOfAllThreadsPaginated.count - 1; } // we mark the thread as quit _lastQuitThreadId = threadId // we dispatch _this.callbackDispatch(MessageConstants.MESSAGES_THREAD_INFO_UPDATED, _currentThread); _this.callbackDispatch(MessageConstants.MESSAGES_CURRENT_THREAD_UPDATED, _currentThreadPaginatedMessages); _this.callbackDispatch(MessageConstants.MESSAGES_ALL_THREADS_UPDATED, _lastMessageOfAllThreadsPaginated); _this.callbackDispatch(MessageConstants.MESSAGES_THREAT_QUIT, threadId); }); }, addThreadQuitListener: function(callback) { this.on(MessageConstants.MESSAGES_THREAT_QUIT, callback); }, removeThreadQuitListener: function(callback) { this.removeListener(MessageConstants.MESSAGES_THREAT_QUIT, callback); }, /* Emit change */ emitChange: function(constant) { /* * We emit a change for the specified action. */ this.emit(constant); this.setLastEmit(constant); }, /* Small helpers used for testing emit and dispacth events. */ getLastEmit: function(){ return _lastEmit; }, setLastEmit: function(action){ _lastEmit = action; }, getLastDispatchedAction: function(){ return _lastDispatchedAction; }, setLastDispatchedAction: function(action){ _lastDispatchedAction = action; }, /* Dispatch */ callbackDispatch: function(actionType, data) { /* * Callback used to interract with the dispatcher (handy after an async call). */ // we dispatch AppDispatcher.dispatch({ actionType: actionType, data: data }); // we keep track of the last dispatched action // this helps us essentialy for testing this.setLastDispatchedAction(actionType); // we emit this.emitChange(actionType); } }); MessageStore.dispatchToken = AppDispatcher.register(function(action) { switch(action.actionType) { case MessageConstants.MESSAGES_SET_LOGIN_STATUS: break; default: return true; } return true; }); module.exports = MessageStore;