2015-09-17 06:13:17 +00:00
|
|
|
/*global $, Whisper, Backbone, textsecure, extension*/
|
|
|
|
/*
|
|
|
|
* vim: ts=4:sw=4:expandtab
|
|
|
|
*/
|
|
|
|
|
|
|
|
// This script should only be included in background.html
|
|
|
|
(function () {
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
window.Whisper = window.Whisper || {};
|
|
|
|
|
|
|
|
var conversations = new Whisper.ConversationCollection();
|
|
|
|
var inboxCollection = new (Backbone.Collection.extend({
|
|
|
|
initialize: function() {
|
2016-03-25 17:39:36 +00:00
|
|
|
this.on('change:timestamp change:name change:number', this.sort);
|
2015-09-17 06:13:17 +00:00
|
|
|
|
|
|
|
this.listenTo(conversations, 'add change:active_at', this.addActive);
|
2018-03-08 01:27:58 +00:00
|
|
|
this.listenTo(conversations, 'reset', function() {
|
|
|
|
this.reset([]);
|
|
|
|
});
|
2016-04-11 18:24:18 +00:00
|
|
|
|
|
|
|
this.on('add remove change:unreadCount',
|
|
|
|
_.debounce(this.updateUnreadCount.bind(this), 1000)
|
|
|
|
);
|
2017-07-26 21:55:59 +00:00
|
|
|
this.startPruning();
|
2017-12-15 00:30:11 +00:00
|
|
|
|
|
|
|
this.collator = new Intl.Collator();
|
2015-09-17 06:13:17 +00:00
|
|
|
},
|
2016-03-18 04:42:03 +00:00
|
|
|
comparator: function(m1, m2) {
|
|
|
|
var timestamp1 = m1.get('timestamp');
|
|
|
|
var timestamp2 = m2.get('timestamp');
|
2017-12-15 00:30:11 +00:00
|
|
|
if (timestamp1 && !timestamp2) {
|
2016-03-18 04:42:03 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2017-12-15 00:30:11 +00:00
|
|
|
if (timestamp2 && !timestamp1) {
|
2016-03-18 04:42:03 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2017-12-15 00:30:11 +00:00
|
|
|
if (timestamp1 && timestamp2 && timestamp1 !== timestamp2) {
|
|
|
|
return timestamp2 - timestamp1;
|
|
|
|
}
|
|
|
|
|
2016-03-18 04:42:03 +00:00
|
|
|
var title1 = m1.getTitle().toLowerCase();
|
|
|
|
var title2 = m2.getTitle().toLowerCase();
|
2017-12-15 00:30:11 +00:00
|
|
|
return this.collator.compare(title1, title2);
|
2015-09-17 06:13:17 +00:00
|
|
|
},
|
|
|
|
addActive: function(model) {
|
|
|
|
if (model.get('active_at')) {
|
|
|
|
this.add(model);
|
|
|
|
} else {
|
|
|
|
this.remove(model);
|
|
|
|
}
|
|
|
|
},
|
2016-04-11 18:24:18 +00:00
|
|
|
updateUnreadCount: function() {
|
2016-02-20 01:19:04 +00:00
|
|
|
var newUnreadCount = _.reduce(
|
|
|
|
this.map(function(m) { return m.get('unreadCount'); }),
|
|
|
|
function(item, memo) {
|
|
|
|
return item + memo;
|
|
|
|
},
|
|
|
|
0
|
|
|
|
);
|
2015-11-10 15:56:27 +00:00
|
|
|
storage.put("unreadCount", newUnreadCount);
|
2015-11-14 11:47:21 +00:00
|
|
|
|
2017-05-04 05:29:06 +00:00
|
|
|
if (newUnreadCount > 0) {
|
|
|
|
window.setBadgeCount(newUnreadCount);
|
Beta versions support: SxS support, in-app env/instance display (#1606)
* Script for beta config; unique data dir, in-app env/type display
To release a beta build, increment the version and add -beta-N to the
end, then go through all the standard release activities.
The prepare-build npm script then updates key bits of the package.json
to ensure that the beta build can be installed alongside a production
build. This includes a new name ('Signal Beta') and a different location
for application data.
Note: Beta builds can be installed alongside production builds.
As part of this, a couple new bits of data are shown across the app:
- Environment (development or test, not shown if production)
- App Instance (disabled in production; used for multiple accounts)
These are shown in:
- The window title - both environment and app instance. You can tell
beta builds because the app name, preceding these data bits, is
different.
- The about window - both environment and app instance. You can tell
beta builds from the version number.
- The header added to the debug log - just environment. The version
number will tell us if it's a beta build, and app instance isn't
helpful.
* Turn on single-window mode in non-production modes
Because it's really frightening when you see 'unable to read from db'
errors in the console.
* aply.sh: More instructions for initial setup and testing
* Gruntfile: Get consistent with use of package.json datas
* Linux: manually update desktop keys, since macros not available
2017-10-30 20:57:13 +00:00
|
|
|
window.document.title = window.config.title + " (" + newUnreadCount + ")";
|
2017-05-04 05:29:06 +00:00
|
|
|
} else {
|
|
|
|
window.setBadgeCount(0);
|
Beta versions support: SxS support, in-app env/instance display (#1606)
* Script for beta config; unique data dir, in-app env/type display
To release a beta build, increment the version and add -beta-N to the
end, then go through all the standard release activities.
The prepare-build npm script then updates key bits of the package.json
to ensure that the beta build can be installed alongside a production
build. This includes a new name ('Signal Beta') and a different location
for application data.
Note: Beta builds can be installed alongside production builds.
As part of this, a couple new bits of data are shown across the app:
- Environment (development or test, not shown if production)
- App Instance (disabled in production; used for multiple accounts)
These are shown in:
- The window title - both environment and app instance. You can tell
beta builds because the app name, preceding these data bits, is
different.
- The about window - both environment and app instance. You can tell
beta builds from the version number.
- The header added to the debug log - just environment. The version
number will tell us if it's a beta build, and app instance isn't
helpful.
* Turn on single-window mode in non-production modes
Because it's really frightening when you see 'unable to read from db'
errors in the console.
* aply.sh: More instructions for initial setup and testing
* Gruntfile: Get consistent with use of package.json datas
* Linux: manually update desktop keys, since macros not available
2017-10-30 20:57:13 +00:00
|
|
|
window.document.title = window.config.title;
|
2017-05-04 05:29:06 +00:00
|
|
|
}
|
2018-01-17 23:27:58 +00:00
|
|
|
window.updateTrayIcon(newUnreadCount);
|
2017-07-26 21:55:59 +00:00
|
|
|
},
|
|
|
|
startPruning: function() {
|
|
|
|
var halfHour = 30 * 60 * 1000;
|
|
|
|
this.interval = setInterval(function() {
|
|
|
|
this.forEach(function(conversation) {
|
|
|
|
conversation.trigger('prune');
|
|
|
|
});
|
|
|
|
}.bind(this), halfHour);
|
2015-09-17 06:13:17 +00:00
|
|
|
}
|
|
|
|
}))();
|
|
|
|
|
|
|
|
window.getInboxCollection = function() {
|
|
|
|
return inboxCollection;
|
|
|
|
};
|
|
|
|
|
|
|
|
window.ConversationController = {
|
|
|
|
get: function(id) {
|
2017-09-07 01:18:46 +00:00
|
|
|
if (!this._initialFetchComplete) {
|
|
|
|
throw new Error('ConversationController.get() needs complete initial fetch');
|
|
|
|
}
|
|
|
|
|
|
|
|
return conversations.get(id);
|
|
|
|
},
|
|
|
|
// Needed for some model setup which happens during the initial fetch() call below
|
|
|
|
getUnsafe: function(id) {
|
2015-09-17 06:13:17 +00:00
|
|
|
return conversations.get(id);
|
|
|
|
},
|
2018-03-27 22:58:00 +00:00
|
|
|
dangerouslyCreateAndAdd: function(attributes) {
|
2017-09-01 16:10:41 +00:00
|
|
|
return conversations.add(attributes);
|
2016-09-21 23:26:42 +00:00
|
|
|
},
|
2017-09-01 16:10:41 +00:00
|
|
|
getOrCreate: function(id, type) {
|
2018-03-27 23:00:25 +00:00
|
|
|
if (typeof id !== 'string') {
|
2018-04-11 19:44:52 +00:00
|
|
|
throw new TypeError("'id' must be a string");
|
2018-03-27 23:00:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (type !== 'private' && type !== 'group') {
|
2018-04-11 19:44:52 +00:00
|
|
|
throw new TypeError(`'type' must be 'private' or 'group'; got: '${type}'`);
|
2018-03-27 23:00:25 +00:00
|
|
|
}
|
|
|
|
|
2017-09-07 01:18:46 +00:00
|
|
|
if (!this._initialFetchComplete) {
|
|
|
|
throw new Error('ConversationController.get() needs complete initial fetch');
|
|
|
|
}
|
|
|
|
|
2017-09-01 16:10:41 +00:00
|
|
|
var conversation = conversations.get(id);
|
|
|
|
if (conversation) {
|
|
|
|
return conversation;
|
2015-09-17 18:41:49 +00:00
|
|
|
}
|
2017-09-01 16:10:41 +00:00
|
|
|
|
|
|
|
conversation = conversations.add({
|
2017-07-21 17:59:41 +00:00
|
|
|
id: id,
|
2017-07-25 17:33:28 +00:00
|
|
|
type: type
|
2017-07-21 17:59:41 +00:00
|
|
|
});
|
2017-09-01 16:10:41 +00:00
|
|
|
conversation.initialPromise = new Promise(function(resolve, reject) {
|
2018-01-13 00:19:26 +00:00
|
|
|
if (!conversation.isValid()) {
|
|
|
|
var validationError = conversation.validationError || {};
|
|
|
|
console.log(
|
|
|
|
'Contact is not valid. Not saving, but adding to collection:',
|
|
|
|
conversation.idForLogging(),
|
|
|
|
validationError.stack
|
|
|
|
);
|
|
|
|
|
|
|
|
return resolve(conversation);
|
|
|
|
}
|
2017-09-01 16:10:41 +00:00
|
|
|
|
2018-01-13 00:19:26 +00:00
|
|
|
var deferred = conversation.save();
|
2017-09-01 16:10:41 +00:00
|
|
|
if (!deferred) {
|
|
|
|
console.log('Conversation save failed! ', id, type);
|
|
|
|
return reject(new Error('getOrCreate: Conversation save failed'));
|
|
|
|
}
|
|
|
|
|
|
|
|
deferred.then(function() {
|
2015-09-17 06:13:17 +00:00
|
|
|
resolve(conversation);
|
2017-09-01 16:10:41 +00:00
|
|
|
}, reject);
|
|
|
|
});
|
2017-08-30 16:35:04 +00:00
|
|
|
|
2017-09-01 16:10:41 +00:00
|
|
|
return conversation;
|
|
|
|
},
|
|
|
|
getOrCreateAndWait: function(id, type) {
|
2017-09-07 01:18:46 +00:00
|
|
|
return this._initialPromise.then(function() {
|
|
|
|
var conversation = this.getOrCreate(id, type);
|
2017-08-30 16:35:04 +00:00
|
|
|
|
2017-09-07 01:18:46 +00:00
|
|
|
if (conversation) {
|
|
|
|
return conversation.initialPromise.then(function() {
|
|
|
|
return conversation;
|
|
|
|
});
|
|
|
|
}
|
2017-09-01 16:10:41 +00:00
|
|
|
|
2017-09-07 01:18:46 +00:00
|
|
|
return Promise.reject(
|
|
|
|
new Error('getOrCreateAndWait: did not get conversation')
|
|
|
|
);
|
|
|
|
}.bind(this));
|
2015-09-17 22:25:45 +00:00
|
|
|
},
|
2017-06-15 19:27:41 +00:00
|
|
|
getAllGroupsInvolvingId: function(id) {
|
2017-07-21 18:00:25 +00:00
|
|
|
var groups = new Whisper.GroupCollection();
|
|
|
|
return groups.fetchGroups(id).then(function() {
|
|
|
|
return groups.map(function(group) {
|
|
|
|
return conversations.add(group);
|
|
|
|
});
|
2017-06-15 19:27:41 +00:00
|
|
|
});
|
|
|
|
},
|
2017-09-07 01:18:46 +00:00
|
|
|
loadPromise: function() {
|
|
|
|
return this._initialPromise;
|
|
|
|
},
|
2017-10-13 23:48:50 +00:00
|
|
|
reset: function() {
|
2018-02-24 01:40:02 +00:00
|
|
|
this._initialPromise = Promise.resolve();
|
2017-10-13 23:48:50 +00:00
|
|
|
conversations.reset([]);
|
|
|
|
},
|
2017-09-07 01:18:46 +00:00
|
|
|
load: function() {
|
|
|
|
console.log('ConversationController: starting initial fetch');
|
|
|
|
|
|
|
|
this._initialPromise = new Promise(function(resolve, reject) {
|
|
|
|
conversations.fetch().then(function() {
|
|
|
|
console.log('ConversationController: done with initial fetch');
|
|
|
|
this._initialFetchComplete = true;
|
|
|
|
resolve();
|
|
|
|
}.bind(this), function(error) {
|
|
|
|
console.log(
|
|
|
|
'ConversationController: initial fetch failed',
|
|
|
|
error && error.stack ? error.stack : error
|
|
|
|
);
|
|
|
|
reject(error);
|
|
|
|
});
|
|
|
|
}.bind(this));
|
|
|
|
|
|
|
|
return this._initialPromise;
|
2015-09-17 06:13:17 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
})();
|