You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			303 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			303 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			JavaScript
		
	
/* global Whisper, Backbone, textsecure, libsignal, log */
 | 
						|
 | 
						|
/* eslint-disable more/no-then */
 | 
						|
 | 
						|
// eslint-disable-next-line func-names
 | 
						|
(function() {
 | 
						|
  'use strict';
 | 
						|
 | 
						|
  window.Whisper = window.Whisper || {};
 | 
						|
 | 
						|
  const conversations = new Whisper.ConversationCollection();
 | 
						|
  const inboxCollection = new (Backbone.Collection.extend({
 | 
						|
    initialize() {
 | 
						|
      this.listenTo(conversations, 'add change:active_at', this.addActive);
 | 
						|
      this.listenTo(conversations, 'reset', () => this.reset([]));
 | 
						|
      this.listenTo(conversations, 'remove', this.remove);
 | 
						|
 | 
						|
      this.startPruning();
 | 
						|
    },
 | 
						|
    addActive(model) {
 | 
						|
      if (model.get('active_at')) {
 | 
						|
        this.add(model);
 | 
						|
        model.updateLastMessage();
 | 
						|
      } else {
 | 
						|
        this.remove(model);
 | 
						|
      }
 | 
						|
    },
 | 
						|
    startPruning() {
 | 
						|
      const halfHour = 30 * 60 * 1000;
 | 
						|
      this.interval = setInterval(() => {
 | 
						|
        this.forEach(conversation => {
 | 
						|
          conversation.trigger('prune');
 | 
						|
        });
 | 
						|
      }, halfHour);
 | 
						|
    },
 | 
						|
  }))();
 | 
						|
 | 
						|
  window.getInboxCollection = () => inboxCollection;
 | 
						|
  window.getConversations = () => conversations;
 | 
						|
 | 
						|
  window.getMessagesByKey = async key => {
 | 
						|
    // loadLive gets messages live, not from the database which can lag behind.
 | 
						|
 | 
						|
    let messages = [];
 | 
						|
    const messageSet = await window.Signal.Data.getMessagesByConversation(key, {
 | 
						|
      limit: 100,
 | 
						|
      MessageCollection: Whisper.MessageCollection,
 | 
						|
    });
 | 
						|
 | 
						|
    messages = messageSet.models.map(conv => conv.attributes);
 | 
						|
    return messages;
 | 
						|
  };
 | 
						|
 | 
						|
  window.getConversationByName = name =>
 | 
						|
    conversations.find(d => d.get('name') === name);
 | 
						|
 | 
						|
  window.ConversationController = {
 | 
						|
    get(id) {
 | 
						|
      if (!this._initialFetchComplete) {
 | 
						|
        throw new Error(
 | 
						|
          'ConversationController.get() needs complete initial fetch'
 | 
						|
        );
 | 
						|
      }
 | 
						|
 | 
						|
      return conversations.get(id);
 | 
						|
    },
 | 
						|
    getOrThrow(id) {
 | 
						|
      if (!this._initialFetchComplete) {
 | 
						|
        throw new Error(
 | 
						|
          'ConversationController.get() needs complete initial fetch'
 | 
						|
        );
 | 
						|
      }
 | 
						|
 | 
						|
      const convo = conversations.get(id);
 | 
						|
 | 
						|
      if (convo) {
 | 
						|
        return convo;
 | 
						|
      }
 | 
						|
      throw new Error(
 | 
						|
        `Conversation ${id} does not exist on ConversationController.get()`
 | 
						|
      );
 | 
						|
    },
 | 
						|
    // Needed for some model setup which happens during the initial fetch() call below
 | 
						|
    getUnsafe(id) {
 | 
						|
      return conversations.get(id);
 | 
						|
    },
 | 
						|
    dangerouslyCreateAndAdd(attributes) {
 | 
						|
      return conversations.add(attributes);
 | 
						|
    },
 | 
						|
    getOrCreate(id, type) {
 | 
						|
      if (typeof id !== 'string') {
 | 
						|
        throw new TypeError("'id' must be a string");
 | 
						|
      }
 | 
						|
 | 
						|
      if (type !== 'private' && type !== 'group') {
 | 
						|
        throw new TypeError(
 | 
						|
          `'type' must be 'private' or 'group'; got: '${type}'`
 | 
						|
        );
 | 
						|
      }
 | 
						|
 | 
						|
      if (!this._initialFetchComplete) {
 | 
						|
        throw new Error(
 | 
						|
          'ConversationController.get() needs complete initial fetch'
 | 
						|
        );
 | 
						|
      }
 | 
						|
 | 
						|
      let conversation = conversations.get(id);
 | 
						|
      if (conversation) {
 | 
						|
        return conversation;
 | 
						|
      }
 | 
						|
 | 
						|
      conversation = conversations.add({
 | 
						|
        id,
 | 
						|
        type,
 | 
						|
        version: 2,
 | 
						|
      });
 | 
						|
 | 
						|
      const create = async () => {
 | 
						|
        if (!conversation.isValid()) {
 | 
						|
          const validationError = conversation.validationError || {};
 | 
						|
          window.log.error(
 | 
						|
            'Contact is not valid. Not saving, but adding to collection:',
 | 
						|
            conversation.idForLogging(),
 | 
						|
            validationError.stack
 | 
						|
          );
 | 
						|
 | 
						|
          return conversation;
 | 
						|
        }
 | 
						|
 | 
						|
        try {
 | 
						|
          await window.Signal.Data.saveConversation(conversation.attributes, {
 | 
						|
            Conversation: Whisper.Conversation,
 | 
						|
          });
 | 
						|
        } catch (error) {
 | 
						|
          window.log.error(
 | 
						|
            'Conversation save failed! ',
 | 
						|
            id,
 | 
						|
            type,
 | 
						|
            'Error:',
 | 
						|
            error && error.stack ? error.stack : error
 | 
						|
          );
 | 
						|
          throw error;
 | 
						|
        }
 | 
						|
 | 
						|
        return conversation;
 | 
						|
      };
 | 
						|
 | 
						|
      conversation.initialPromise = create();
 | 
						|
      conversation.initialPromise.then(() => {
 | 
						|
        if (!conversation.isPublic() && !conversation.isRss()) {
 | 
						|
          Promise.all([
 | 
						|
            conversation.updateProfileAvatar(),
 | 
						|
            // NOTE: we request snodes updating the cache, but ignore the result
 | 
						|
            window.SnodePool.getSnodesFor(id),
 | 
						|
          ]);
 | 
						|
        }
 | 
						|
      });
 | 
						|
 | 
						|
      return conversation;
 | 
						|
    },
 | 
						|
    async deleteContact(id) {
 | 
						|
      if (typeof id !== 'string') {
 | 
						|
        throw new TypeError("'id' must be a string");
 | 
						|
      }
 | 
						|
 | 
						|
      if (!this._initialFetchComplete) {
 | 
						|
        throw new Error(
 | 
						|
          'ConversationController.get() needs complete initial fetch'
 | 
						|
        );
 | 
						|
      }
 | 
						|
 | 
						|
      const conversation = conversations.get(id);
 | 
						|
      if (!conversation) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // Close group leaving
 | 
						|
      if (conversation.isClosedGroup()) {
 | 
						|
        await conversation.leaveGroup();
 | 
						|
      } else if (conversation.isPublic()) {
 | 
						|
        const channelAPI = await conversation.getPublicSendData();
 | 
						|
        if (channelAPI === null) {
 | 
						|
          log.warn(`Could not get API for public conversation ${id}`);
 | 
						|
        } else {
 | 
						|
          channelAPI.serverAPI.partChannel(channelAPI.channelId);
 | 
						|
        }
 | 
						|
      } else if (conversation.isPrivate()) {
 | 
						|
        const deviceIds = await textsecure.storage.protocol.getDeviceIds(id);
 | 
						|
        await Promise.all(
 | 
						|
          deviceIds.map(deviceId => {
 | 
						|
            const address = new libsignal.SignalProtocolAddress(id, deviceId);
 | 
						|
            const sessionCipher = new libsignal.SessionCipher(
 | 
						|
              textsecure.storage.protocol,
 | 
						|
              address
 | 
						|
            );
 | 
						|
            return sessionCipher.deleteAllSessionsForDevice();
 | 
						|
          })
 | 
						|
        );
 | 
						|
      }
 | 
						|
 | 
						|
      await conversation.destroyMessages();
 | 
						|
 | 
						|
      await window.Signal.Data.removeConversation(id, {
 | 
						|
        Conversation: Whisper.Conversation,
 | 
						|
      });
 | 
						|
      conversations.remove(conversation);
 | 
						|
    },
 | 
						|
    getOrCreateAndWait(id, type) {
 | 
						|
      return this._initialPromise.then(() => {
 | 
						|
        if (!id) {
 | 
						|
          return Promise.reject(
 | 
						|
            new Error('getOrCreateAndWait: invalid id passed.')
 | 
						|
          );
 | 
						|
        }
 | 
						|
        const pubkey = id && id.key ? id.key : id;
 | 
						|
        const conversation = this.getOrCreate(pubkey, type);
 | 
						|
 | 
						|
        if (conversation) {
 | 
						|
          return conversation.initialPromise.then(() => conversation);
 | 
						|
        }
 | 
						|
 | 
						|
        return Promise.reject(
 | 
						|
          new Error('getOrCreateAndWait: did not get conversation')
 | 
						|
        );
 | 
						|
      });
 | 
						|
    },
 | 
						|
    async getAllGroupsInvolvingId(id) {
 | 
						|
      const groups = await window.Signal.Data.getAllGroupsInvolvingId(id, {
 | 
						|
        ConversationCollection: Whisper.ConversationCollection,
 | 
						|
      });
 | 
						|
      return groups.map(group => conversations.add(group));
 | 
						|
    },
 | 
						|
    loadPromise() {
 | 
						|
      return this._initialPromise;
 | 
						|
    },
 | 
						|
    reset() {
 | 
						|
      this._initialPromise = Promise.resolve();
 | 
						|
      this._initialFetchComplete = false;
 | 
						|
      conversations.reset([]);
 | 
						|
    },
 | 
						|
    async load() {
 | 
						|
      window.log.info('ConversationController: starting initial fetch');
 | 
						|
 | 
						|
      if (conversations.length) {
 | 
						|
        throw new Error('ConversationController: Already loaded!');
 | 
						|
      }
 | 
						|
 | 
						|
      const load = async () => {
 | 
						|
        try {
 | 
						|
          const collection = await window.Signal.Data.getAllConversations({
 | 
						|
            ConversationCollection: Whisper.ConversationCollection,
 | 
						|
          });
 | 
						|
 | 
						|
          conversations.add(collection.models);
 | 
						|
 | 
						|
          this._initialFetchComplete = true;
 | 
						|
          const promises = [];
 | 
						|
          conversations.forEach(conversation => {
 | 
						|
            if (!conversation.get('lastMessage')) {
 | 
						|
              promises.push(conversation.updateLastMessage());
 | 
						|
            }
 | 
						|
 | 
						|
            promises.concat([
 | 
						|
              conversation.updateProfileName(),
 | 
						|
              conversation.updateProfileAvatar(),
 | 
						|
            ]);
 | 
						|
          });
 | 
						|
          await Promise.all(promises);
 | 
						|
 | 
						|
          // Remove any unused images
 | 
						|
          window.profileImages.removeImagesNotInArray(
 | 
						|
            conversations.map(c => c.id)
 | 
						|
          );
 | 
						|
 | 
						|
          window.log.info('ConversationController: done with initial fetch');
 | 
						|
        } catch (error) {
 | 
						|
          window.log.error(
 | 
						|
            'ConversationController: initial fetch failed',
 | 
						|
            error && error.stack ? error.stack : error
 | 
						|
          );
 | 
						|
          throw error;
 | 
						|
        }
 | 
						|
      };
 | 
						|
 | 
						|
      this._initialPromise = load();
 | 
						|
 | 
						|
      return this._initialPromise;
 | 
						|
    },
 | 
						|
    _handleOnline: pubKey => {
 | 
						|
      try {
 | 
						|
        const conversation = this.get(pubKey);
 | 
						|
        conversation.set({ isOnline: true });
 | 
						|
      } catch (e) {} // eslint-disable-line
 | 
						|
    },
 | 
						|
    _handleOffline: pubKey => {
 | 
						|
      try {
 | 
						|
        const conversation = this.get(pubKey);
 | 
						|
        conversation.set({ isOnline: false });
 | 
						|
      } catch (e) {} // eslint-disable-line
 | 
						|
    },
 | 
						|
  };
 | 
						|
})();
 |