More protobufjs migration
This commit is contained in:
parent
cf06e6638e
commit
ddbbe3a6b1
70 changed files with 3967 additions and 3369 deletions
|
@ -52,7 +52,6 @@ module.exports = grunt => {
|
||||||
libtextsecuretest: {
|
libtextsecuretest: {
|
||||||
src: [
|
src: [
|
||||||
'node_modules/jquery/dist/jquery.js',
|
'node_modules/jquery/dist/jquery.js',
|
||||||
'components/mock-socket/dist/mock-socket.js',
|
|
||||||
'node_modules/mocha/mocha.js',
|
'node_modules/mocha/mocha.js',
|
||||||
'node_modules/chai/chai.js',
|
'node_modules/chai/chai.js',
|
||||||
'libtextsecure/test/_test.js',
|
'libtextsecure/test/_test.js',
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
"webaudiorecorder": "https://github.com/higuma/web-audio-recorder-js.git"
|
"webaudiorecorder": "https://github.com/higuma/web-audio-recorder-js.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"mock-socket": "~0.3.2"
|
|
||||||
},
|
},
|
||||||
"preen": {
|
"preen": {
|
||||||
"bytebuffer": [
|
"bytebuffer": [
|
||||||
|
@ -20,9 +19,6 @@
|
||||||
"long": [
|
"long": [
|
||||||
"dist/Long.js"
|
"dist/Long.js"
|
||||||
],
|
],
|
||||||
"mock-socket": [
|
|
||||||
"dist/mock-socket.js"
|
|
||||||
],
|
|
||||||
"mp3lameencoder": [
|
"mp3lameencoder": [
|
||||||
"lib/Mp3LameEncoder.js"
|
"lib/Mp3LameEncoder.js"
|
||||||
],
|
],
|
||||||
|
|
635
components/mock-socket/dist/mock-socket.js
vendored
635
components/mock-socket/dist/mock-socket.js
vendored
|
@ -1,635 +0,0 @@
|
||||||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
||||||
// Starting point for browserify and throws important objects into the window object
|
|
||||||
var Service = require('./service');
|
|
||||||
var MockServer = require('./mock-server');
|
|
||||||
var MockSocket = require('./mock-socket');
|
|
||||||
var globalContext = require('./helpers/global-context');
|
|
||||||
|
|
||||||
globalContext.SocketService = Service;
|
|
||||||
globalContext.MockSocket = MockSocket;
|
|
||||||
globalContext.MockServer = MockServer;
|
|
||||||
|
|
||||||
},{"./helpers/global-context":3,"./mock-server":7,"./mock-socket":8,"./service":9}],2:[function(require,module,exports){
|
|
||||||
var globalContext = require('./global-context');
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This delay allows the thread to finish assigning its on* methods
|
|
||||||
* before invoking the delay callback. This is purely a timing hack.
|
|
||||||
* http://geekabyte.blogspot.com/2014/01/javascript-effect-of-setting-settimeout.html
|
|
||||||
*
|
|
||||||
* @param {callback: function} the callback which will be invoked after the timeout
|
|
||||||
* @parma {context: object} the context in which to invoke the function
|
|
||||||
*/
|
|
||||||
function delay(callback, context) {
|
|
||||||
globalContext.setTimeout(function(context) {
|
|
||||||
callback.call(context);
|
|
||||||
}, 4, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = delay;
|
|
||||||
|
|
||||||
},{"./global-context":3}],3:[function(require,module,exports){
|
|
||||||
(function (global){
|
|
||||||
/*
|
|
||||||
* Determines the global context. This should be either window (in the)
|
|
||||||
* case where we are in a browser) or global (in the case where we are in
|
|
||||||
* node)
|
|
||||||
*/
|
|
||||||
var globalContext;
|
|
||||||
|
|
||||||
if(typeof window === 'undefined') {
|
|
||||||
globalContext = global;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
globalContext = window;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!globalContext) {
|
|
||||||
throw new Error('Unable to set the global context to either window or global.');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = globalContext;
|
|
||||||
|
|
||||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
|
||||||
},{}],4:[function(require,module,exports){
|
|
||||||
/*
|
|
||||||
* This is a mock websocket event message that is passed into the onopen,
|
|
||||||
* opmessage, etc functions.
|
|
||||||
*
|
|
||||||
* @param {name: string} The name of the event
|
|
||||||
* @param {data: *} The data to send.
|
|
||||||
* @param {origin: string} The url of the place where the event is originating.
|
|
||||||
*/
|
|
||||||
function socketEventMessage(name, data, origin) {
|
|
||||||
var ports = null;
|
|
||||||
var source = null;
|
|
||||||
var bubbles = false;
|
|
||||||
var cancelable = false;
|
|
||||||
var lastEventId = '';
|
|
||||||
var targetPlacehold = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var messageEvent = new MessageEvent(name);
|
|
||||||
messageEvent.initMessageEvent(name, bubbles, cancelable, data, origin, lastEventId);
|
|
||||||
|
|
||||||
Object.defineProperties(messageEvent, {
|
|
||||||
target: {
|
|
||||||
get: function() { return targetPlacehold; },
|
|
||||||
set: function(value) { targetPlacehold = value; }
|
|
||||||
},
|
|
||||||
srcElement: {
|
|
||||||
get: function() { return this.target; }
|
|
||||||
},
|
|
||||||
currentTarget: {
|
|
||||||
get: function() { return this.target; }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
// We are unable to create a MessageEvent Object. This should only be happening in PhantomJS.
|
|
||||||
var messageEvent = {
|
|
||||||
type : name,
|
|
||||||
bubbles : bubbles,
|
|
||||||
cancelable : cancelable,
|
|
||||||
data : data,
|
|
||||||
origin : origin,
|
|
||||||
lastEventId : lastEventId,
|
|
||||||
source : source,
|
|
||||||
ports : ports,
|
|
||||||
defaultPrevented : false,
|
|
||||||
returnValue : true,
|
|
||||||
clipboardData : undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.defineProperties(messageEvent, {
|
|
||||||
target: {
|
|
||||||
get: function() { return targetPlacehold; },
|
|
||||||
set: function(value) { targetPlacehold = value; }
|
|
||||||
},
|
|
||||||
srcElement: {
|
|
||||||
get: function() { return this.target; }
|
|
||||||
},
|
|
||||||
currentTarget: {
|
|
||||||
get: function() { return this.target; }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = socketEventMessage;
|
|
||||||
|
|
||||||
},{}],5:[function(require,module,exports){
|
|
||||||
/*
|
|
||||||
* The native websocket object will transform urls without a pathname to have just a /.
|
|
||||||
* As an example: ws://localhost:8080 would actually be ws://localhost:8080/ but ws://example.com/foo would not
|
|
||||||
* change. This function does this transformation to stay inline with the native websocket implementation.
|
|
||||||
*
|
|
||||||
* @param {url: string} The url to transform.
|
|
||||||
*/
|
|
||||||
function urlTransform(url) {
|
|
||||||
var urlPath = urlParse('path', url);
|
|
||||||
var urlQuery = urlParse('?', url);
|
|
||||||
|
|
||||||
urlQuery = (urlQuery) ? '?' + urlQuery : '';
|
|
||||||
|
|
||||||
if(urlPath === '') {
|
|
||||||
return url.split('?')[0] + '/' + urlQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The following functions (isNumeric & urlParse) was taken from
|
|
||||||
* https://github.com/websanova/js-url/blob/764ed8d94012a79bfa91026f2a62fe3383a5a49e/url.js
|
|
||||||
* which is shared via the MIT license with minimal modifications.
|
|
||||||
*/
|
|
||||||
function isNumeric(arg) {
|
|
||||||
return !isNaN(parseFloat(arg)) && isFinite(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
function urlParse(arg, url) {
|
|
||||||
var _ls = url || window.location.toString();
|
|
||||||
|
|
||||||
if (!arg) { return _ls; }
|
|
||||||
else { arg = arg.toString(); }
|
|
||||||
|
|
||||||
if (_ls.substring(0,2) === '//') { _ls = 'http:' + _ls; }
|
|
||||||
else if (_ls.split('://').length === 1) { _ls = 'http://' + _ls; }
|
|
||||||
|
|
||||||
url = _ls.split('/');
|
|
||||||
var _l = {auth:''}, host = url[2].split('@');
|
|
||||||
|
|
||||||
if (host.length === 1) { host = host[0].split(':'); }
|
|
||||||
else { _l.auth = host[0]; host = host[1].split(':'); }
|
|
||||||
|
|
||||||
_l.protocol=url[0];
|
|
||||||
_l.hostname=host[0];
|
|
||||||
_l.port=(host[1] || ((_l.protocol.split(':')[0].toLowerCase() === 'https') ? '443' : '80'));
|
|
||||||
_l.pathname=( (url.length > 3 ? '/' : '') + url.slice(3, url.length).join('/').split('?')[0].split('#')[0]);
|
|
||||||
var _p = _l.pathname;
|
|
||||||
|
|
||||||
if (_p.charAt(_p.length-1) === '/') { _p=_p.substring(0, _p.length-1); }
|
|
||||||
var _h = _l.hostname, _hs = _h.split('.'), _ps = _p.split('/');
|
|
||||||
|
|
||||||
if (arg === 'hostname') { return _h; }
|
|
||||||
else if (arg === 'domain') {
|
|
||||||
if (/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(_h)) { return _h; }
|
|
||||||
return _hs.slice(-2).join('.');
|
|
||||||
}
|
|
||||||
//else if (arg === 'tld') { return _hs.slice(-1).join('.'); }
|
|
||||||
else if (arg === 'sub') { return _hs.slice(0, _hs.length - 2).join('.'); }
|
|
||||||
else if (arg === 'port') { return _l.port; }
|
|
||||||
else if (arg === 'protocol') { return _l.protocol.split(':')[0]; }
|
|
||||||
else if (arg === 'auth') { return _l.auth; }
|
|
||||||
else if (arg === 'user') { return _l.auth.split(':')[0]; }
|
|
||||||
else if (arg === 'pass') { return _l.auth.split(':')[1] || ''; }
|
|
||||||
else if (arg === 'path') { return _l.pathname; }
|
|
||||||
else if (arg.charAt(0) === '.') {
|
|
||||||
arg = arg.substring(1);
|
|
||||||
if(isNumeric(arg)) {arg = parseInt(arg, 10); return _hs[arg < 0 ? _hs.length + arg : arg-1] || ''; }
|
|
||||||
}
|
|
||||||
else if (isNumeric(arg)) { arg = parseInt(arg, 10); return _ps[arg < 0 ? _ps.length + arg : arg] || ''; }
|
|
||||||
else if (arg === 'file') { return _ps.slice(-1)[0]; }
|
|
||||||
else if (arg === 'filename') { return _ps.slice(-1)[0].split('.')[0]; }
|
|
||||||
else if (arg === 'fileext') { return _ps.slice(-1)[0].split('.')[1] || ''; }
|
|
||||||
else if (arg.charAt(0) === '?' || arg.charAt(0) === '#') {
|
|
||||||
var params = _ls, param = null;
|
|
||||||
|
|
||||||
if(arg.charAt(0) === '?') { params = (params.split('?')[1] || '').split('#')[0]; }
|
|
||||||
else if(arg.charAt(0) === '#') { params = (params.split('#')[1] || ''); }
|
|
||||||
|
|
||||||
if(!arg.charAt(1)) { return params; }
|
|
||||||
|
|
||||||
arg = arg.substring(1);
|
|
||||||
params = params.split('&');
|
|
||||||
|
|
||||||
for(var i=0,ii=params.length; i<ii; i++) {
|
|
||||||
param = params[i].split('=');
|
|
||||||
if(param[0] === arg) { return param[1] || ''; }
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = urlTransform;
|
|
||||||
|
|
||||||
},{}],6:[function(require,module,exports){
|
|
||||||
/*
|
|
||||||
* This defines four methods: onopen, onmessage, onerror, and onclose. This is done this way instead of
|
|
||||||
* just placing the methods on the prototype because we need to capture the callback when it is defined like so:
|
|
||||||
*
|
|
||||||
* mockSocket.onopen = function() { // this is what we need to store };
|
|
||||||
*
|
|
||||||
* The only way is to capture the callback via the custom setter below and then place them into the correct
|
|
||||||
* namespace so they get invoked at the right time.
|
|
||||||
*
|
|
||||||
* @param {websocket: object} The websocket object which we want to define these properties onto
|
|
||||||
*/
|
|
||||||
function webSocketProperties(websocket) {
|
|
||||||
var eventMessageSource = function(callback) {
|
|
||||||
return function(event) {
|
|
||||||
event.target = websocket;
|
|
||||||
callback.apply(websocket, arguments);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.defineProperties(websocket, {
|
|
||||||
onopen: {
|
|
||||||
enumerable: true,
|
|
||||||
get: function() { return this._onopen; },
|
|
||||||
set: function(callback) {
|
|
||||||
this._onopen = eventMessageSource(callback);
|
|
||||||
this.service.setCallbackObserver('clientOnOpen', this._onopen, websocket);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmessage: {
|
|
||||||
enumerable: true,
|
|
||||||
get: function() { return this._onmessage; },
|
|
||||||
set: function(callback) {
|
|
||||||
this._onmessage = eventMessageSource(callback);
|
|
||||||
this.service.setCallbackObserver('clientOnMessage', this._onmessage, websocket);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onclose: {
|
|
||||||
enumerable: true,
|
|
||||||
get: function() { return this._onclose; },
|
|
||||||
set: function(callback) {
|
|
||||||
this._onclose = eventMessageSource(callback);
|
|
||||||
this.service.setCallbackObserver('clientOnclose', this._onclose, websocket);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onerror: {
|
|
||||||
enumerable: true,
|
|
||||||
get: function() { return this._onerror; },
|
|
||||||
set: function(callback) {
|
|
||||||
this._onerror = eventMessageSource(callback);
|
|
||||||
this.service.setCallbackObserver('clientOnError', this._onerror, websocket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = webSocketProperties;
|
|
||||||
|
|
||||||
},{}],7:[function(require,module,exports){
|
|
||||||
var Service = require('./service');
|
|
||||||
var delay = require('./helpers/delay');
|
|
||||||
var urlTransform = require('./helpers/url-transform');
|
|
||||||
var socketMessageEvent = require('./helpers/message-event');
|
|
||||||
var globalContext = require('./helpers/global-context');
|
|
||||||
|
|
||||||
function MockServer(url) {
|
|
||||||
var service = new Service();
|
|
||||||
this.url = urlTransform(url);
|
|
||||||
|
|
||||||
globalContext.MockSocket.services[this.url] = service;
|
|
||||||
|
|
||||||
this.service = service;
|
|
||||||
service.server = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
MockServer.prototype = {
|
|
||||||
service: null,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is the main function for the mock server to subscribe to the on events.
|
|
||||||
*
|
|
||||||
* ie: mockServer.on('connection', function() { console.log('a mock client connected'); });
|
|
||||||
*
|
|
||||||
* @param {type: string}: The event key to subscribe to. Valid keys are: connection, message, and close.
|
|
||||||
* @param {callback: function}: The callback which should be called when a certain event is fired.
|
|
||||||
*/
|
|
||||||
on: function(type, callback) {
|
|
||||||
var observerKey;
|
|
||||||
|
|
||||||
if(typeof callback !== 'function' || typeof type !== 'string') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(type) {
|
|
||||||
case 'connection':
|
|
||||||
observerKey = 'clientHasJoined';
|
|
||||||
break;
|
|
||||||
case 'message':
|
|
||||||
observerKey = 'clientHasSentMessage';
|
|
||||||
break;
|
|
||||||
case 'close':
|
|
||||||
observerKey = 'clientHasLeft';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that the observerKey is valid before observing on it.
|
|
||||||
if(typeof observerKey === 'string') {
|
|
||||||
this.service.clearAll(observerKey);
|
|
||||||
this.service.setCallbackObserver(observerKey, callback, this);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This send function will notify all mock clients via their onmessage callbacks that the server
|
|
||||||
* has a message for them.
|
|
||||||
*
|
|
||||||
* @param {data: *}: Any javascript object which will be crafted into a MessageObject.
|
|
||||||
*/
|
|
||||||
send: function(data) {
|
|
||||||
delay(function() {
|
|
||||||
this.service.sendMessageToClients(socketMessageEvent('message', data, this.url));
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Notifies all mock clients that the server is closing and their onclose callbacks should fire.
|
|
||||||
*/
|
|
||||||
close: function() {
|
|
||||||
delay(function() {
|
|
||||||
this.service.closeConnectionFromServer(socketMessageEvent('close', null, this.url));
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = MockServer;
|
|
||||||
|
|
||||||
},{"./helpers/delay":2,"./helpers/global-context":3,"./helpers/message-event":4,"./helpers/url-transform":5,"./service":9}],8:[function(require,module,exports){
|
|
||||||
var delay = require('./helpers/delay');
|
|
||||||
var urlTransform = require('./helpers/url-transform');
|
|
||||||
var socketMessageEvent = require('./helpers/message-event');
|
|
||||||
var globalContext = require('./helpers/global-context');
|
|
||||||
var webSocketProperties = require('./helpers/websocket-properties');
|
|
||||||
|
|
||||||
function MockSocket(url) {
|
|
||||||
this.binaryType = 'blob';
|
|
||||||
this.url = urlTransform(url);
|
|
||||||
this.readyState = globalContext.MockSocket.CONNECTING;
|
|
||||||
this.service = globalContext.MockSocket.services[this.url];
|
|
||||||
|
|
||||||
webSocketProperties(this);
|
|
||||||
|
|
||||||
delay(function() {
|
|
||||||
// Let the service know that we are both ready to change our ready state and that
|
|
||||||
// this client is connecting to the mock server.
|
|
||||||
this.service.clientIsConnecting(this, this._updateReadyState);
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
MockSocket.CONNECTING = 0;
|
|
||||||
MockSocket.OPEN = 1;
|
|
||||||
MockSocket.CLOSING = 2;
|
|
||||||
MockSocket.LOADING = 3;
|
|
||||||
MockSocket.CLOSED = 4;
|
|
||||||
MockSocket.services = {};
|
|
||||||
|
|
||||||
MockSocket.prototype = {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Holds the on*** callback functions. These are really just for the custom
|
|
||||||
* getters that are defined in the helpers/websocket-properties. Accessing these properties is not advised.
|
|
||||||
*/
|
|
||||||
_onopen : null,
|
|
||||||
_onmessage : null,
|
|
||||||
_onerror : null,
|
|
||||||
_onclose : null,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This holds reference to the service object. The service object is how we can
|
|
||||||
* communicate with the backend via the pub/sub model.
|
|
||||||
*
|
|
||||||
* The service has properties which we can use to observe or notifiy with.
|
|
||||||
* this.service.notify('foo') & this.service.observe('foo', callback, context)
|
|
||||||
*/
|
|
||||||
service: null,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is a mock for the native send function found on the WebSocket object. It notifies the
|
|
||||||
* service that it has sent a message.
|
|
||||||
*
|
|
||||||
* @param {data: *}: Any javascript object which will be crafted into a MessageObject.
|
|
||||||
*/
|
|
||||||
send: function(data) {
|
|
||||||
delay(function() {
|
|
||||||
this.service.sendMessageToServer(socketMessageEvent('message', data, this.url));
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is a mock for the native close function found on the WebSocket object. It notifies the
|
|
||||||
* service that it is closing the connection.
|
|
||||||
*/
|
|
||||||
close: function() {
|
|
||||||
delay(function() {
|
|
||||||
this.service.closeConnectionFromClient(socketMessageEvent('close', null, this.url), this);
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is a private method that can be used to change the readyState. This is used
|
|
||||||
* like this: this.protocol.subject.observe('updateReadyState', this._updateReadyState, this);
|
|
||||||
* so that the service and the server can change the readyState simply be notifing a namespace.
|
|
||||||
*
|
|
||||||
* @param {newReadyState: number}: The new ready state. Must be 0-4
|
|
||||||
*/
|
|
||||||
_updateReadyState: function(newReadyState) {
|
|
||||||
if(newReadyState >= 0 && newReadyState <= 4) {
|
|
||||||
this.readyState = newReadyState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = MockSocket;
|
|
||||||
|
|
||||||
},{"./helpers/delay":2,"./helpers/global-context":3,"./helpers/message-event":4,"./helpers/url-transform":5,"./helpers/websocket-properties":6}],9:[function(require,module,exports){
|
|
||||||
var socketMessageEvent = require('./helpers/message-event');
|
|
||||||
var globalContext = require('./helpers/global-context');
|
|
||||||
|
|
||||||
function SocketService() {
|
|
||||||
this.list = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketService.prototype = {
|
|
||||||
server: null,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This notifies the mock server that a client is connecting and also sets up
|
|
||||||
* the ready state observer.
|
|
||||||
*
|
|
||||||
* @param {client: object} the context of the client
|
|
||||||
* @param {readyStateFunction: function} the function that will be invoked on a ready state change
|
|
||||||
*/
|
|
||||||
clientIsConnecting: function(client, readyStateFunction) {
|
|
||||||
this.observe('updateReadyState', readyStateFunction, client);
|
|
||||||
|
|
||||||
// if the server has not been set then we notify the onclose method of this client
|
|
||||||
if(!this.server) {
|
|
||||||
this.notify(client, 'updateReadyState', globalContext.MockSocket.CLOSED);
|
|
||||||
this.notifyOnlyFor(client, 'clientOnError');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.notifyOnlyFor(client, 'updateReadyState', globalContext.MockSocket.OPEN);
|
|
||||||
this.notify('clientHasJoined', this.server);
|
|
||||||
this.notifyOnlyFor(client, 'clientOnOpen', socketMessageEvent('open', null, this.server.url));
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Closes a connection from the server's perspective. This should
|
|
||||||
* close all clients.
|
|
||||||
*
|
|
||||||
* @param {messageEvent: object} the mock message event.
|
|
||||||
*/
|
|
||||||
closeConnectionFromServer: function(messageEvent) {
|
|
||||||
this.notify('updateReadyState', globalContext.MockSocket.CLOSING);
|
|
||||||
this.notify('clientOnclose', messageEvent);
|
|
||||||
this.notify('updateReadyState', globalContext.MockSocket.CLOSED);
|
|
||||||
this.notify('clientHasLeft');
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Closes a connection from the clients perspective. This
|
|
||||||
* should only close the client who initiated the close and not
|
|
||||||
* all of the other clients.
|
|
||||||
*
|
|
||||||
* @param {messageEvent: object} the mock message event.
|
|
||||||
* @param {client: object} the context of the client
|
|
||||||
*/
|
|
||||||
closeConnectionFromClient: function(messageEvent, client) {
|
|
||||||
if(client.readyState === globalContext.MockSocket.OPEN) {
|
|
||||||
this.notifyOnlyFor(client, 'updateReadyState', globalContext.MockSocket.CLOSING);
|
|
||||||
this.notifyOnlyFor(client, 'clientOnclose', messageEvent);
|
|
||||||
this.notifyOnlyFor(client, 'updateReadyState', globalContext.MockSocket.CLOSED);
|
|
||||||
this.notify('clientHasLeft');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Notifies the mock server that a client has sent a message.
|
|
||||||
*
|
|
||||||
* @param {messageEvent: object} the mock message event.
|
|
||||||
*/
|
|
||||||
sendMessageToServer: function(messageEvent) {
|
|
||||||
this.notify('clientHasSentMessage', messageEvent.data, messageEvent);
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Notifies all clients that the server has sent a message
|
|
||||||
*
|
|
||||||
* @param {messageEvent: object} the mock message event.
|
|
||||||
*/
|
|
||||||
sendMessageToClients: function(messageEvent) {
|
|
||||||
this.notify('clientOnMessage', messageEvent);
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Setup the callback function observers for both the server and client.
|
|
||||||
*
|
|
||||||
* @param {observerKey: string} either: connection, message or close
|
|
||||||
* @param {callback: function} the callback to be invoked
|
|
||||||
* @param {server: object} the context of the server
|
|
||||||
*/
|
|
||||||
setCallbackObserver: function(observerKey, callback, server) {
|
|
||||||
this.observe(observerKey, callback, server);
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Binds a callback to a namespace. If notify is called on a namespace all "observers" will be
|
|
||||||
* fired with the context that is passed in.
|
|
||||||
*
|
|
||||||
* @param {namespace: string}
|
|
||||||
* @param {callback: function}
|
|
||||||
* @param {context: object}
|
|
||||||
*/
|
|
||||||
observe: function(namespace, callback, context) {
|
|
||||||
|
|
||||||
// Make sure the arguments are of the correct type
|
|
||||||
if( typeof namespace !== 'string' || typeof callback !== 'function' || (context && typeof context !== 'object')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a namespace has not been created before then we need to "initialize" the namespace
|
|
||||||
if(!this.list[namespace]) {
|
|
||||||
this.list[namespace] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.list[namespace].push({callback: callback, context: context});
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Remove all observers from a given namespace.
|
|
||||||
*
|
|
||||||
* @param {namespace: string} The namespace to clear.
|
|
||||||
*/
|
|
||||||
clearAll: function(namespace) {
|
|
||||||
|
|
||||||
if(!this.verifyNamespaceArg(namespace)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.list[namespace] = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Notify all callbacks that have been bound to the given namespace.
|
|
||||||
*
|
|
||||||
* @param {namespace: string} The namespace to notify observers on.
|
|
||||||
* @param {namespace: url} The url to notify observers on.
|
|
||||||
*/
|
|
||||||
notify: function(namespace) {
|
|
||||||
|
|
||||||
// This strips the namespace from the list of args as we dont want to pass that into the callback.
|
|
||||||
var argumentsForCallback = Array.prototype.slice.call(arguments, 1);
|
|
||||||
|
|
||||||
if(!this.verifyNamespaceArg(namespace)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop over all of the observers and fire the callback function with the context.
|
|
||||||
for(var i = 0, len = this.list[namespace].length; i < len; i++) {
|
|
||||||
this.list[namespace][i].callback.apply(this.list[namespace][i].context, argumentsForCallback);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Notify only the callback of the given context and namespace.
|
|
||||||
*
|
|
||||||
* @param {context: object} the context to match against.
|
|
||||||
* @param {namespace: string} The namespace to notify observers on.
|
|
||||||
*/
|
|
||||||
notifyOnlyFor: function(context, namespace) {
|
|
||||||
|
|
||||||
// This strips the namespace from the list of args as we dont want to pass that into the callback.
|
|
||||||
var argumentsForCallback = Array.prototype.slice.call(arguments, 2);
|
|
||||||
|
|
||||||
if(!this.verifyNamespaceArg(namespace)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop over all of the observers and fire the callback function with the context.
|
|
||||||
for(var i = 0, len = this.list[namespace].length; i < len; i++) {
|
|
||||||
if(this.list[namespace][i].context === context) {
|
|
||||||
this.list[namespace][i].callback.apply(this.list[namespace][i].context, argumentsForCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Verifies that the namespace is valid.
|
|
||||||
*
|
|
||||||
* @param {namespace: string} The namespace to verify.
|
|
||||||
*/
|
|
||||||
verifyNamespaceArg: function(namespace) {
|
|
||||||
if(typeof namespace !== 'string' || !this.list[namespace]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = SocketService;
|
|
||||||
|
|
||||||
},{"./helpers/global-context":3,"./helpers/message-event":4}]},{},[1]);
|
|
|
@ -17,7 +17,7 @@ const GroupChange = require('../../ts/groupChange');
|
||||||
const IndexedDB = require('./indexeddb');
|
const IndexedDB = require('./indexeddb');
|
||||||
const Notifications = require('../../ts/notifications');
|
const Notifications = require('../../ts/notifications');
|
||||||
const OS = require('../../ts/OS');
|
const OS = require('../../ts/OS');
|
||||||
const Stickers = require('./stickers');
|
const Stickers = require('../../ts/types/Stickers');
|
||||||
const Settings = require('./settings');
|
const Settings = require('./settings');
|
||||||
const RemoteConfig = require('../../ts/RemoteConfig');
|
const RemoteConfig = require('../../ts/RemoteConfig');
|
||||||
const Util = require('../../ts/util');
|
const Util = require('../../ts/util');
|
||||||
|
|
18
js/modules/stickers.d.ts
vendored
18
js/modules/stickers.d.ts
vendored
|
@ -1,18 +0,0 @@
|
||||||
// Copyright 2019-2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
export function maybeDeletePack(packId: string): Promise<void>;
|
|
||||||
|
|
||||||
export function downloadStickerPack(
|
|
||||||
packId: string,
|
|
||||||
packKey: string,
|
|
||||||
options?: {
|
|
||||||
finalStatus?: 'installed' | 'downloaded';
|
|
||||||
messageId?: string;
|
|
||||||
fromSync?: boolean;
|
|
||||||
}
|
|
||||||
): Promise<void>;
|
|
||||||
|
|
||||||
export function isPackIdValid(packId: unknown): packId is string;
|
|
||||||
|
|
||||||
export function redactPackId(packId: string): string;
|
|
|
@ -20,8 +20,6 @@ module.exports = {
|
||||||
dcodeIO: true,
|
dcodeIO: true,
|
||||||
getString: true,
|
getString: true,
|
||||||
hexToArrayBuffer: true,
|
hexToArrayBuffer: true,
|
||||||
MockServer: true,
|
|
||||||
MockSocket: true,
|
|
||||||
PROTO_ROOT: true,
|
PROTO_ROOT: true,
|
||||||
stringToArrayBuffer: true,
|
stringToArrayBuffer: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -60,8 +60,6 @@ window.hexToArrayBuffer = str => {
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.MockSocket.prototype.addEventListener = () => null;
|
|
||||||
|
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
window.Whisper.events = {
|
window.Whisper.events = {
|
||||||
on() {},
|
on() {},
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
// Copyright 2015-2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
describe('ContactBuffer', () => {
|
|
||||||
function getTestBuffer() {
|
|
||||||
const buffer = new dcodeIO.ByteBuffer();
|
|
||||||
const avatarBuffer = new dcodeIO.ByteBuffer();
|
|
||||||
const avatarLen = 255;
|
|
||||||
for (let i = 0; i < avatarLen; i += 1) {
|
|
||||||
avatarBuffer.writeUint8(i);
|
|
||||||
}
|
|
||||||
avatarBuffer.limit = avatarBuffer.offset;
|
|
||||||
avatarBuffer.offset = 0;
|
|
||||||
const contactInfo = new window.textsecure.protobuf.ContactDetails({
|
|
||||||
name: 'Zero Cool',
|
|
||||||
number: '+10000000000',
|
|
||||||
uuid: '7198E1BD-1293-452A-A098-F982FF201902',
|
|
||||||
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
|
||||||
});
|
|
||||||
const contactInfoBuffer = contactInfo.encode().toArrayBuffer();
|
|
||||||
|
|
||||||
for (let i = 0; i < 3; i += 1) {
|
|
||||||
buffer.writeVarint32(contactInfoBuffer.byteLength);
|
|
||||||
buffer.append(contactInfoBuffer);
|
|
||||||
buffer.append(avatarBuffer.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.limit = buffer.offset;
|
|
||||||
buffer.offset = 0;
|
|
||||||
return buffer.toArrayBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
it('parses an array buffer of contacts', () => {
|
|
||||||
const arrayBuffer = getTestBuffer();
|
|
||||||
const contactBuffer = new window.textsecure.ContactBuffer(arrayBuffer);
|
|
||||||
let contact = contactBuffer.next();
|
|
||||||
let count = 0;
|
|
||||||
while (contact !== undefined) {
|
|
||||||
count += 1;
|
|
||||||
assert.strictEqual(contact.name, 'Zero Cool');
|
|
||||||
assert.strictEqual(contact.number, '+10000000000');
|
|
||||||
assert.strictEqual(contact.uuid, '7198e1bd-1293-452a-a098-f982ff201902');
|
|
||||||
assert.strictEqual(contact.avatar.contentType, 'image/jpeg');
|
|
||||||
assert.strictEqual(contact.avatar.length, 255);
|
|
||||||
assert.strictEqual(contact.avatar.data.byteLength, 255);
|
|
||||||
const avatarBytes = new Uint8Array(contact.avatar.data);
|
|
||||||
for (let j = 0; j < 255; j += 1) {
|
|
||||||
assert.strictEqual(avatarBytes[j], j);
|
|
||||||
}
|
|
||||||
contact = contactBuffer.next();
|
|
||||||
}
|
|
||||||
assert.strictEqual(count, 3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GroupBuffer', () => {
|
|
||||||
function getTestBuffer() {
|
|
||||||
const buffer = new dcodeIO.ByteBuffer();
|
|
||||||
const avatarBuffer = new dcodeIO.ByteBuffer();
|
|
||||||
const avatarLen = 255;
|
|
||||||
for (let i = 0; i < avatarLen; i += 1) {
|
|
||||||
avatarBuffer.writeUint8(i);
|
|
||||||
}
|
|
||||||
avatarBuffer.limit = avatarBuffer.offset;
|
|
||||||
avatarBuffer.offset = 0;
|
|
||||||
const groupInfo = new window.textsecure.protobuf.GroupDetails({
|
|
||||||
id: window.Signal.Crypto.typedArrayToArrayBuffer(
|
|
||||||
new Uint8Array([1, 3, 3, 7])
|
|
||||||
),
|
|
||||||
name: 'Hackers',
|
|
||||||
membersE164: ['cereal', 'burn', 'phreak', 'joey'],
|
|
||||||
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
|
||||||
});
|
|
||||||
const groupInfoBuffer = groupInfo.encode().toArrayBuffer();
|
|
||||||
|
|
||||||
for (let i = 0; i < 3; i += 1) {
|
|
||||||
buffer.writeVarint32(groupInfoBuffer.byteLength);
|
|
||||||
buffer.append(groupInfoBuffer);
|
|
||||||
buffer.append(avatarBuffer.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.limit = buffer.offset;
|
|
||||||
buffer.offset = 0;
|
|
||||||
return buffer.toArrayBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
it('parses an array buffer of groups', () => {
|
|
||||||
const arrayBuffer = getTestBuffer();
|
|
||||||
const groupBuffer = new window.textsecure.GroupBuffer(arrayBuffer);
|
|
||||||
let group = groupBuffer.next();
|
|
||||||
let count = 0;
|
|
||||||
while (group !== undefined) {
|
|
||||||
count += 1;
|
|
||||||
assert.strictEqual(group.name, 'Hackers');
|
|
||||||
assertEqualArrayBuffers(
|
|
||||||
group.id.toArrayBuffer(),
|
|
||||||
window.Signal.Crypto.typedArrayToArrayBuffer(
|
|
||||||
new Uint8Array([1, 3, 3, 7])
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert.sameMembers(group.membersE164, [
|
|
||||||
'cereal',
|
|
||||||
'burn',
|
|
||||||
'phreak',
|
|
||||||
'joey',
|
|
||||||
]);
|
|
||||||
assert.strictEqual(group.avatar.contentType, 'image/jpeg');
|
|
||||||
assert.strictEqual(group.avatar.length, 255);
|
|
||||||
assert.strictEqual(group.avatar.data.byteLength, 255);
|
|
||||||
const avatarBytes = new Uint8Array(group.avatar.data);
|
|
||||||
for (let j = 0; j < 255; j += 1) {
|
|
||||||
assert.strictEqual(avatarBytes[j], j);
|
|
||||||
}
|
|
||||||
group = groupBuffer.next();
|
|
||||||
}
|
|
||||||
assert.strictEqual(count, 3);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -39,11 +39,9 @@
|
||||||
|
|
||||||
<script type="text/javascript" src="helpers_test.js"></script>
|
<script type="text/javascript" src="helpers_test.js"></script>
|
||||||
<script type="text/javascript" src="crypto_test.js"></script>
|
<script type="text/javascript" src="crypto_test.js"></script>
|
||||||
<script type="text/javascript" src="contacts_parser_test.js"></script>
|
|
||||||
<script type="text/javascript" src="generate_keys_test.js"></script>
|
<script type="text/javascript" src="generate_keys_test.js"></script>
|
||||||
<script type="text/javascript" src="task_with_timeout_test.js"></script>
|
<script type="text/javascript" src="task_with_timeout_test.js"></script>
|
||||||
<script type="text/javascript" src="account_manager_test.js"></script>
|
<script type="text/javascript" src="account_manager_test.js"></script>
|
||||||
<script type="text/javascript" src="message_receiver_test.js"></script>
|
|
||||||
<script type="text/javascript" src="sendmessage_test.js"></script>
|
<script type="text/javascript" src="sendmessage_test.js"></script>
|
||||||
|
|
||||||
<!-- Comment out to turn off code coverage. Useful for getting real callstacks. -->
|
<!-- Comment out to turn off code coverage. Useful for getting real callstacks. -->
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
// Copyright 2015-2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
/* global textsecure */
|
|
||||||
|
|
||||||
describe('MessageReceiver', () => {
|
|
||||||
const { WebSocket } = window;
|
|
||||||
const number = '+19999999999';
|
|
||||||
const uuid = 'AAAAAAAA-BBBB-4CCC-9DDD-EEEEEEEEEEEE';
|
|
||||||
const deviceId = 1;
|
|
||||||
const signalingKey = window.Signal.Crypto.getRandomBytes(32 + 20);
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
localStorage.clear();
|
|
||||||
window.WebSocket = MockSocket;
|
|
||||||
textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name');
|
|
||||||
textsecure.storage.user.setUuidAndDeviceId(uuid, deviceId);
|
|
||||||
textsecure.storage.put('password', 'password');
|
|
||||||
textsecure.storage.put('signaling_key', signalingKey);
|
|
||||||
});
|
|
||||||
after(() => {
|
|
||||||
localStorage.clear();
|
|
||||||
window.WebSocket = WebSocket;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('connecting', () => {
|
|
||||||
let attrs;
|
|
||||||
let websocketmessage;
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
attrs = {
|
|
||||||
type: textsecure.protobuf.Envelope.Type.CIPHERTEXT,
|
|
||||||
source: number,
|
|
||||||
sourceUuid: uuid,
|
|
||||||
sourceDevice: deviceId,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
content: window.Signal.Crypto.getRandomBytes(200),
|
|
||||||
};
|
|
||||||
const body = new textsecure.protobuf.Envelope(attrs).toArrayBuffer();
|
|
||||||
|
|
||||||
websocketmessage = new textsecure.protobuf.WebSocketMessage({
|
|
||||||
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
|
||||||
request: { verb: 'PUT', path: '/api/v1/message', body },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('generates decryption-error event when it cannot decrypt', done => {
|
|
||||||
const mockServer = new MockServer('ws://localhost:8081/');
|
|
||||||
|
|
||||||
mockServer.on('connection', server => {
|
|
||||||
setTimeout(() => {
|
|
||||||
server.send(new Blob([websocketmessage.toArrayBuffer()]));
|
|
||||||
}, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
const messageReceiver = new textsecure.MessageReceiver(
|
|
||||||
'oldUsername.2',
|
|
||||||
'username.2',
|
|
||||||
'password',
|
|
||||||
'signalingKey',
|
|
||||||
{
|
|
||||||
serverTrustRoot: 'AAAAAAAA',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
messageReceiver.addEventListener('decrytion-error', done());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// For when we start testing individual MessageReceiver methods
|
|
||||||
|
|
||||||
// describe('methods', () => {
|
|
||||||
// let messageReceiver;
|
|
||||||
// let mockServer;
|
|
||||||
|
|
||||||
// beforeEach(() => {
|
|
||||||
// // Necessary to populate the server property inside of MockSocket. Without it, we
|
|
||||||
// // crash when doing any number of things to a MockSocket instance.
|
|
||||||
// mockServer = new MockServer('ws://localhost:8081');
|
|
||||||
|
|
||||||
// messageReceiver = new textsecure.MessageReceiver(
|
|
||||||
// 'oldUsername.3',
|
|
||||||
// 'username.3',
|
|
||||||
// 'password',
|
|
||||||
// 'signalingKey',
|
|
||||||
// {
|
|
||||||
// serverTrustRoot: 'AAAAAAAA',
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// afterEach(() => {
|
|
||||||
// mockServer.close();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
});
|
|
|
@ -1,40 +0,0 @@
|
||||||
// Copyright 2015-2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
/* global textsecure */
|
|
||||||
|
|
||||||
describe('Protocol', () => {
|
|
||||||
describe('Unencrypted PushMessageProto "decrypt"', () => {
|
|
||||||
// exclusive
|
|
||||||
it('works', done => {
|
|
||||||
localStorage.clear();
|
|
||||||
|
|
||||||
const textMessage = new textsecure.protobuf.DataMessage();
|
|
||||||
textMessage.body = 'Hi Mom';
|
|
||||||
const serverMessage = {
|
|
||||||
type: 4, // unencrypted
|
|
||||||
source: '+19999999999',
|
|
||||||
timestamp: 42,
|
|
||||||
message: textMessage.encode(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return textsecure.protocol_wrapper
|
|
||||||
.handleEncryptedMessage(
|
|
||||||
serverMessage.source,
|
|
||||||
serverMessage.source_device,
|
|
||||||
serverMessage.type,
|
|
||||||
serverMessage.message
|
|
||||||
)
|
|
||||||
.then(message => {
|
|
||||||
assert.equal(message.body, textMessage.body);
|
|
||||||
assert.equal(
|
|
||||||
message.attachments.length,
|
|
||||||
textMessage.attachments.length
|
|
||||||
);
|
|
||||||
assert.equal(textMessage.attachments.length, 0);
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,69 +0,0 @@
|
||||||
// Copyright 2015-2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
/* global TextSecureWebSocket */
|
|
||||||
|
|
||||||
describe('TextSecureWebSocket', () => {
|
|
||||||
const RealWebSocket = window.WebSocket;
|
|
||||||
before(() => {
|
|
||||||
window.WebSocket = MockSocket;
|
|
||||||
});
|
|
||||||
after(() => {
|
|
||||||
window.WebSocket = RealWebSocket;
|
|
||||||
});
|
|
||||||
it('connects and disconnects', done => {
|
|
||||||
const mockServer = new MockServer('ws://localhost:8080');
|
|
||||||
mockServer.on('connection', server => {
|
|
||||||
socket.close();
|
|
||||||
server.close();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
const socket = new TextSecureWebSocket('ws://localhost:8080');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sends and receives', done => {
|
|
||||||
const mockServer = new MockServer('ws://localhost:8080');
|
|
||||||
mockServer.on('connection', server => {
|
|
||||||
server.on('message', () => {
|
|
||||||
server.send('ack');
|
|
||||||
server.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const socket = new TextSecureWebSocket('ws://localhost:8080');
|
|
||||||
socket.onmessage = response => {
|
|
||||||
assert.strictEqual(response.data, 'ack');
|
|
||||||
socket.close();
|
|
||||||
done();
|
|
||||||
};
|
|
||||||
socket.send('syn');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('exposes the socket status', done => {
|
|
||||||
const mockServer = new MockServer('ws://localhost:8082');
|
|
||||||
mockServer.on('connection', server => {
|
|
||||||
assert.strictEqual(socket.getStatus(), WebSocket.OPEN);
|
|
||||||
server.close();
|
|
||||||
socket.close();
|
|
||||||
});
|
|
||||||
const socket = new TextSecureWebSocket('ws://localhost:8082');
|
|
||||||
socket.onclose = () => {
|
|
||||||
assert.strictEqual(socket.getStatus(), WebSocket.CLOSING);
|
|
||||||
done();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('reconnects', function thisNeeded(done) {
|
|
||||||
this.timeout(60000);
|
|
||||||
const mockServer = new MockServer('ws://localhost:8082');
|
|
||||||
const socket = new TextSecureWebSocket('ws://localhost:8082');
|
|
||||||
socket.onclose = () => {
|
|
||||||
const secondServer = new MockServer('ws://localhost:8082');
|
|
||||||
secondServer.on('connection', server => {
|
|
||||||
socket.close();
|
|
||||||
server.close();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
mockServer.close();
|
|
||||||
});
|
|
||||||
});
|
|
19
preload.js
19
preload.js
|
@ -509,25 +509,6 @@ try {
|
||||||
// https://stackoverflow.com/a/23299989
|
// https://stackoverflow.com/a/23299989
|
||||||
window.isValidE164 = maybeE164 => /^\+?[1-9]\d{1,14}$/.test(maybeE164);
|
window.isValidE164 = maybeE164 => /^\+?[1-9]\d{1,14}$/.test(maybeE164);
|
||||||
|
|
||||||
window.normalizeUuids = (obj, paths, context) => {
|
|
||||||
if (!obj) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
paths.forEach(path => {
|
|
||||||
const val = _.get(obj, path);
|
|
||||||
if (val) {
|
|
||||||
if (!val || !window.isValidGuid(val)) {
|
|
||||||
window.log.warn(
|
|
||||||
`Normalizing invalid uuid: ${val} at path ${path} in context "${context}"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (val && val.toLowerCase) {
|
|
||||||
_.set(obj, path, val.toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.React = require('react');
|
window.React = require('react');
|
||||||
window.ReactDOM = require('react-dom');
|
window.ReactDOM = require('react-dom');
|
||||||
window.moment = require('moment');
|
window.moment = require('moment');
|
||||||
|
|
|
@ -15,8 +15,6 @@ module.exports = {
|
||||||
dcodeIO: true,
|
dcodeIO: true,
|
||||||
getString: true,
|
getString: true,
|
||||||
hexToArrayBuffer: true,
|
hexToArrayBuffer: true,
|
||||||
MockServer: true,
|
|
||||||
MockSocket: true,
|
|
||||||
PROTO_ROOT: true,
|
PROTO_ROOT: true,
|
||||||
stringToArrayBuffer: true,
|
stringToArrayBuffer: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,58 +7,58 @@ const { Stickers } = Signal;
|
||||||
|
|
||||||
describe('Stickers', () => {
|
describe('Stickers', () => {
|
||||||
describe('getDataFromLink', () => {
|
describe('getDataFromLink', () => {
|
||||||
it('returns null for invalid URLs', () => {
|
it('returns undefined for invalid URLs', () => {
|
||||||
assert.isNull(Stickers.getDataFromLink('https://'));
|
assert.isUndefined(Stickers.getDataFromLink('https://'));
|
||||||
assert.isNull(Stickers.getDataFromLink('signal.art/addstickers/'));
|
assert.isUndefined(Stickers.getDataFromLink('signal.art/addstickers/'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns null for URLs that don't have a hash", () => {
|
it("returns undefined for URLs that don't have a hash", () => {
|
||||||
assert.isNull(
|
assert.isUndefined(
|
||||||
Stickers.getDataFromLink('https://signal.art/addstickers/')
|
Stickers.getDataFromLink('https://signal.art/addstickers/')
|
||||||
);
|
);
|
||||||
assert.isNull(
|
assert.isUndefined(
|
||||||
Stickers.getDataFromLink('https://signal.art/addstickers/#')
|
Stickers.getDataFromLink('https://signal.art/addstickers/#')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns null when no key or pack ID is found', () => {
|
it('returns undefined when no key or pack ID is found', () => {
|
||||||
assert.isNull(
|
assert.isUndefined(
|
||||||
Stickers.getDataFromLink(
|
Stickers.getDataFromLink(
|
||||||
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a'
|
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert.isNull(
|
assert.isUndefined(
|
||||||
Stickers.getDataFromLink(
|
Stickers.getDataFromLink(
|
||||||
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a&pack_key='
|
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a&pack_key='
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert.isNull(
|
assert.isUndefined(
|
||||||
Stickers.getDataFromLink(
|
Stickers.getDataFromLink(
|
||||||
'https://signal.art/addstickers/#pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
|
'https://signal.art/addstickers/#pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert.isNull(
|
assert.isUndefined(
|
||||||
Stickers.getDataFromLink(
|
Stickers.getDataFromLink(
|
||||||
'https://signal.art/addstickers/#pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e&pack_id='
|
'https://signal.art/addstickers/#pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e&pack_id='
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns null when the pack ID is invalid', () => {
|
it('returns undefined when the pack ID is invalid', () => {
|
||||||
assert.isNull(
|
assert.isUndefined(
|
||||||
Stickers.getDataFromLink(
|
Stickers.getDataFromLink(
|
||||||
'https://signal.art/addstickers/#pack_id=garbage&pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
|
'https://signal.art/addstickers/#pack_id=garbage&pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns null if the ID or key are passed as arrays', () => {
|
it('returns undefined if the ID or key are passed as arrays', () => {
|
||||||
assert.isNull(
|
assert.isUndefined(
|
||||||
Stickers.getDataFromLink(
|
Stickers.getDataFromLink(
|
||||||
'https://signal.art/addstickers/#pack_id[]=c8c83285b547872ac4c589d64a6edd6a&pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
|
'https://signal.art/addstickers/#pack_id[]=c8c83285b547872ac4c589d64a6edd6a&pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert.isNull(
|
assert.isUndefined(
|
||||||
Stickers.getDataFromLink(
|
Stickers.getDataFromLink(
|
||||||
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a&pack_key[]=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
|
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a&pack_key[]=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
|
||||||
)
|
)
|
||||||
|
|
453
ts/background.ts
453
ts/background.ts
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { isNumber } from 'lodash';
|
import { isNumber, noop } from 'lodash';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import {
|
import {
|
||||||
|
@ -9,16 +9,23 @@ import {
|
||||||
PlaintextContent,
|
PlaintextContent,
|
||||||
} from '@signalapp/signal-client';
|
} from '@signalapp/signal-client';
|
||||||
|
|
||||||
import { DataMessageClass, SyncMessageClass } from './textsecure.d';
|
import MessageReceiver from './textsecure/MessageReceiver';
|
||||||
import { SessionResetsType } from './textsecure/Types.d';
|
import { SessionResetsType, ProcessedDataMessage } from './textsecure/Types.d';
|
||||||
import { MessageAttributesType } from './model-types.d';
|
import {
|
||||||
|
MessageAttributesType,
|
||||||
|
ConversationAttributesType,
|
||||||
|
} from './model-types.d';
|
||||||
|
import * as Bytes from './Bytes';
|
||||||
|
import { typedArrayToArrayBuffer } from './Crypto';
|
||||||
import { WhatIsThis } from './window.d';
|
import { WhatIsThis } from './window.d';
|
||||||
import { getTitleBarVisibility, TitleBarVisibility } from './types/Settings';
|
import { getTitleBarVisibility, TitleBarVisibility } from './types/Settings';
|
||||||
import { SocketStatus } from './types/SocketStatus';
|
import { SocketStatus } from './types/SocketStatus';
|
||||||
import { DEFAULT_CONVERSATION_COLOR } from './types/Colors';
|
import { DEFAULT_CONVERSATION_COLOR } from './types/Colors';
|
||||||
import { ChallengeHandler } from './challenge';
|
import { ChallengeHandler } from './challenge';
|
||||||
import { isWindowDragElement } from './util/isWindowDragElement';
|
import { isWindowDragElement } from './util/isWindowDragElement';
|
||||||
import { assert } from './util/assert';
|
import { assert, strictAssert } from './util/assert';
|
||||||
|
import { dropNull } from './util/dropNull';
|
||||||
|
import { normalizeUuid } from './util/normalizeUuid';
|
||||||
import { filter } from './util/iterables';
|
import { filter } from './util/iterables';
|
||||||
import { isNotNil } from './util/isNotNil';
|
import { isNotNil } from './util/isNotNil';
|
||||||
import { senderCertificateService } from './services/senderCertificate';
|
import { senderCertificateService } from './services/senderCertificate';
|
||||||
|
@ -36,9 +43,30 @@ import { shouldRespondWithProfileKey } from './util/shouldRespondWithProfileKey'
|
||||||
import { LatestQueue } from './util/LatestQueue';
|
import { LatestQueue } from './util/LatestQueue';
|
||||||
import { parseIntOrThrow } from './util/parseIntOrThrow';
|
import { parseIntOrThrow } from './util/parseIntOrThrow';
|
||||||
import {
|
import {
|
||||||
DecryptionErrorType,
|
TypingEvent,
|
||||||
RetryRequestType,
|
ErrorEvent,
|
||||||
} from './textsecure/MessageReceiver';
|
DeliveryEvent,
|
||||||
|
DecryptionErrorEvent,
|
||||||
|
DecryptionErrorEventData,
|
||||||
|
SentEvent,
|
||||||
|
SentEventData,
|
||||||
|
ProfileKeyUpdateEvent,
|
||||||
|
MessageEvent,
|
||||||
|
MessageEventData,
|
||||||
|
RetryRequestEvent,
|
||||||
|
RetryRequestEventData,
|
||||||
|
ReadEvent,
|
||||||
|
ConfigurationEvent,
|
||||||
|
ViewSyncEvent,
|
||||||
|
MessageRequestResponseEvent,
|
||||||
|
FetchLatestEvent,
|
||||||
|
KeysEvent,
|
||||||
|
StickerPackEvent,
|
||||||
|
VerifiedEvent,
|
||||||
|
ReadSyncEvent,
|
||||||
|
ContactEvent,
|
||||||
|
GroupEvent,
|
||||||
|
} from './textsecure/messageReceiverEvents';
|
||||||
import { connectToServerWithStoredCredentials } from './util/connectToServerWithStoredCredentials';
|
import { connectToServerWithStoredCredentials } from './util/connectToServerWithStoredCredentials';
|
||||||
import * as universalExpireTimer from './util/universalExpireTimer';
|
import * as universalExpireTimer from './util/universalExpireTimer';
|
||||||
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
||||||
|
@ -59,6 +87,7 @@ import {
|
||||||
SystemTraySetting,
|
SystemTraySetting,
|
||||||
parseSystemTraySetting,
|
parseSystemTraySetting,
|
||||||
} from './types/SystemTraySetting';
|
} from './types/SystemTraySetting';
|
||||||
|
import * as Stickers from './types/Stickers';
|
||||||
import { SignalService as Proto } from './protobuf';
|
import { SignalService as Proto } from './protobuf';
|
||||||
|
|
||||||
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
||||||
|
@ -149,7 +178,7 @@ export async function startApp(): Promise<void> {
|
||||||
name: 'Whisper.deliveryReceiptBatcher',
|
name: 'Whisper.deliveryReceiptBatcher',
|
||||||
wait: 500,
|
wait: 500,
|
||||||
maxSize: 500,
|
maxSize: 500,
|
||||||
processBatch: async (items: WhatIsThis) => {
|
processBatch: async items => {
|
||||||
const byConversationId = window._.groupBy(items, item =>
|
const byConversationId = window._.groupBy(items, item =>
|
||||||
window.ConversationController.ensureContactIds({
|
window.ConversationController.ensureContactIds({
|
||||||
e164: item.source,
|
e164: item.source,
|
||||||
|
@ -320,7 +349,7 @@ export async function startApp(): Promise<void> {
|
||||||
window.getAccountManager()!.refreshPreKeys();
|
window.getAccountManager()!.refreshPreKeys();
|
||||||
});
|
});
|
||||||
|
|
||||||
let messageReceiver: WhatIsThis;
|
let messageReceiver: MessageReceiver | undefined;
|
||||||
let preMessageReceiverStatus: SocketStatus | undefined;
|
let preMessageReceiverStatus: SocketStatus | undefined;
|
||||||
window.getSocketStatus = () => {
|
window.getSocketStatus = () => {
|
||||||
if (messageReceiver) {
|
if (messageReceiver) {
|
||||||
|
@ -589,7 +618,7 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
if (messageReceiver) {
|
if (messageReceiver) {
|
||||||
messageReceiver.unregisterBatchers();
|
messageReceiver.unregisterBatchers();
|
||||||
messageReceiver = null;
|
messageReceiver = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A number of still-to-queue database queries might be waiting inside batchers.
|
// A number of still-to-queue database queries might be waiting inside batchers.
|
||||||
|
@ -619,14 +648,14 @@ export async function startApp(): Promise<void> {
|
||||||
window.isShowingModal = true;
|
window.isShowingModal = true;
|
||||||
|
|
||||||
// Kick off the download
|
// Kick off the download
|
||||||
window.Signal.Stickers.downloadEphemeralPack(packId, key);
|
Stickers.downloadEphemeralPack(packId, key);
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
packId,
|
packId,
|
||||||
onClose: async () => {
|
onClose: async () => {
|
||||||
window.isShowingModal = false;
|
window.isShowingModal = false;
|
||||||
stickerPreviewModalView.remove();
|
stickerPreviewModalView.remove();
|
||||||
await window.Signal.Stickers.removeEphemeralPack(packId);
|
await Stickers.removeEphemeralPack(packId);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -703,7 +732,7 @@ export async function startApp(): Promise<void> {
|
||||||
},
|
},
|
||||||
|
|
||||||
installStickerPack: async (packId: string, key: string) => {
|
installStickerPack: async (packId: string, key: string) => {
|
||||||
window.Signal.Stickers.downloadStickerPack(packId, key, {
|
Stickers.downloadStickerPack(packId, key, {
|
||||||
finalStatus: 'installed',
|
finalStatus: 'installed',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -894,7 +923,7 @@ export async function startApp(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
window.ConversationController.load(),
|
window.ConversationController.load(),
|
||||||
window.Signal.Stickers.load(),
|
Stickers.load(),
|
||||||
window.Signal.Emojis.load(),
|
window.Signal.Emojis.load(),
|
||||||
window.textsecure.storage.protocol.hydrateCaches(),
|
window.textsecure.storage.protocol.hydrateCaches(),
|
||||||
]);
|
]);
|
||||||
|
@ -963,7 +992,7 @@ export async function startApp(): Promise<void> {
|
||||||
},
|
},
|
||||||
emojis: window.Signal.Emojis.getInitialState(),
|
emojis: window.Signal.Emojis.getInitialState(),
|
||||||
items: window.storage.getItemsState(),
|
items: window.storage.getItemsState(),
|
||||||
stickers: window.Signal.Stickers.getInitialState(),
|
stickers: Stickers.getInitialState(),
|
||||||
user: {
|
user: {
|
||||||
attachmentsPath: window.baseAttachmentsPath,
|
attachmentsPath: window.baseAttachmentsPath,
|
||||||
stickersPath: window.baseStickersPath,
|
stickersPath: window.baseStickersPath,
|
||||||
|
@ -1850,6 +1879,8 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.getSyncRequest = (timeoutMillis?: number) => {
|
window.getSyncRequest = (timeoutMillis?: number) => {
|
||||||
|
strictAssert(messageReceiver, 'MessageReceiver not initialized');
|
||||||
|
|
||||||
const syncRequest = new window.textsecure.SyncRequest(
|
const syncRequest = new window.textsecure.SyncRequest(
|
||||||
window.textsecure.messaging,
|
window.textsecure.messaging,
|
||||||
messageReceiver,
|
messageReceiver,
|
||||||
|
@ -1859,8 +1890,8 @@ export async function startApp(): Promise<void> {
|
||||||
return syncRequest;
|
return syncRequest;
|
||||||
};
|
};
|
||||||
|
|
||||||
let disconnectTimer: WhatIsThis | null = null;
|
let disconnectTimer: NodeJS.Timeout | undefined;
|
||||||
let reconnectTimer: WhatIsThis | null = null;
|
let reconnectTimer: number | undefined;
|
||||||
function onOffline() {
|
function onOffline() {
|
||||||
window.log.info('offline');
|
window.log.info('offline');
|
||||||
|
|
||||||
|
@ -1886,12 +1917,12 @@ export async function startApp(): Promise<void> {
|
||||||
if (disconnectTimer && isSocketOnline()) {
|
if (disconnectTimer && isSocketOnline()) {
|
||||||
window.log.warn('Already online. Had a blip in online/offline status.');
|
window.log.warn('Already online. Had a blip in online/offline status.');
|
||||||
clearTimeout(disconnectTimer);
|
clearTimeout(disconnectTimer);
|
||||||
disconnectTimer = null;
|
disconnectTimer = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (disconnectTimer) {
|
if (disconnectTimer) {
|
||||||
clearTimeout(disconnectTimer);
|
clearTimeout(disconnectTimer);
|
||||||
disconnectTimer = null;
|
disconnectTimer = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect();
|
connect();
|
||||||
|
@ -1909,7 +1940,7 @@ export async function startApp(): Promise<void> {
|
||||||
window.log.info('disconnect');
|
window.log.info('disconnect');
|
||||||
|
|
||||||
// Clear timer, since we're only called when the timer is expired
|
// Clear timer, since we're only called when the timer is expired
|
||||||
disconnectTimer = null;
|
disconnectTimer = undefined;
|
||||||
|
|
||||||
AttachmentDownloads.stop();
|
AttachmentDownloads.stop();
|
||||||
if (messageReceiver) {
|
if (messageReceiver) {
|
||||||
|
@ -1934,7 +1965,7 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
if (reconnectTimer) {
|
if (reconnectTimer) {
|
||||||
clearTimeout(reconnectTimer);
|
clearTimeout(reconnectTimer);
|
||||||
reconnectTimer = null;
|
reconnectTimer = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bootstrap our online/offline detection, only the first time we connect
|
// Bootstrap our online/offline detection, only the first time we connect
|
||||||
|
@ -1964,7 +1995,7 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
if (messageReceiver) {
|
if (messageReceiver) {
|
||||||
messageReceiver.unregisterBatchers();
|
messageReceiver.unregisterBatchers();
|
||||||
messageReceiver = null;
|
messageReceiver = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OLD_USERNAME = window.storage.get('number_id', '');
|
const OLD_USERNAME = window.storage.get('number_id', '');
|
||||||
|
@ -2043,8 +2074,11 @@ export async function startApp(): Promise<void> {
|
||||||
preMessageReceiverStatus = undefined;
|
preMessageReceiverStatus = undefined;
|
||||||
|
|
||||||
// eslint-disable-next-line no-inner-declarations
|
// eslint-disable-next-line no-inner-declarations
|
||||||
function addQueuedEventListener(name: string, handler: WhatIsThis) {
|
function queuedEventListener<Args extends Array<unknown>>(
|
||||||
messageReceiver.addEventListener(name, (...args: Array<WhatIsThis>) =>
|
handler: (...args: Args) => Promise<void> | void,
|
||||||
|
track = true
|
||||||
|
): (...args: Args) => void {
|
||||||
|
return (...args: Args): void => {
|
||||||
eventHandlerQueue.add(async () => {
|
eventHandlerQueue.add(async () => {
|
||||||
try {
|
try {
|
||||||
await handler(...args);
|
await handler(...args);
|
||||||
|
@ -2052,40 +2086,97 @@ export async function startApp(): Promise<void> {
|
||||||
// message/sent: Message.handleDataMessage has its own queue and will
|
// message/sent: Message.handleDataMessage has its own queue and will
|
||||||
// trigger this event itself when complete.
|
// trigger this event itself when complete.
|
||||||
// error: Error processing (below) also has its own queue and self-trigger.
|
// error: Error processing (below) also has its own queue and self-trigger.
|
||||||
if (name !== 'message' && name !== 'sent' && name !== 'error') {
|
if (track) {
|
||||||
window.Whisper.events.trigger('incrementProgress');
|
window.Whisper.events.trigger('incrementProgress');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
);
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
addQueuedEventListener('message', onMessageReceived);
|
messageReceiver.addEventListener(
|
||||||
addQueuedEventListener('delivery', onDeliveryReceipt);
|
'message',
|
||||||
addQueuedEventListener('contact', onContactReceived);
|
queuedEventListener(onMessageReceived, false)
|
||||||
addQueuedEventListener('contactsync', onContactSyncComplete);
|
|
||||||
addQueuedEventListener('group', onGroupReceived);
|
|
||||||
addQueuedEventListener('groupsync', onGroupSyncComplete);
|
|
||||||
addQueuedEventListener('sent', onSentMessage);
|
|
||||||
addQueuedEventListener('readSync', onReadSync);
|
|
||||||
addQueuedEventListener('read', onReadReceipt);
|
|
||||||
addQueuedEventListener('verified', onVerified);
|
|
||||||
addQueuedEventListener('error', onError);
|
|
||||||
addQueuedEventListener('decryption-error', onDecryptionError);
|
|
||||||
addQueuedEventListener('retry-request', onRetryRequest);
|
|
||||||
addQueuedEventListener('empty', onEmpty);
|
|
||||||
addQueuedEventListener('reconnect', onReconnect);
|
|
||||||
addQueuedEventListener('configuration', onConfiguration);
|
|
||||||
addQueuedEventListener('typing', onTyping);
|
|
||||||
addQueuedEventListener('sticker-pack', onStickerPack);
|
|
||||||
addQueuedEventListener('viewSync', onViewSync);
|
|
||||||
addQueuedEventListener(
|
|
||||||
'messageRequestResponse',
|
|
||||||
onMessageRequestResponse
|
|
||||||
);
|
);
|
||||||
addQueuedEventListener('profileKeyUpdate', onProfileKeyUpdate);
|
messageReceiver.addEventListener(
|
||||||
addQueuedEventListener('fetchLatest', onFetchLatestSync);
|
'delivery',
|
||||||
addQueuedEventListener('keys', onKeysSync);
|
queuedEventListener(onDeliveryReceipt)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'contact',
|
||||||
|
queuedEventListener(onContactReceived)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'contactSync',
|
||||||
|
queuedEventListener(onContactSyncComplete)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'group',
|
||||||
|
queuedEventListener(onGroupReceived)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'groupSync',
|
||||||
|
queuedEventListener(onGroupSyncComplete)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'sent',
|
||||||
|
queuedEventListener(onSentMessage, false)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'readSync',
|
||||||
|
queuedEventListener(onReadSync)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'read',
|
||||||
|
queuedEventListener(onReadReceipt)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'verified',
|
||||||
|
queuedEventListener(onVerified)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'error',
|
||||||
|
queuedEventListener(onError, false)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'decryption-error',
|
||||||
|
queuedEventListener(onDecryptionError)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'retry-request',
|
||||||
|
queuedEventListener(onRetryRequest)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener('empty', queuedEventListener(onEmpty));
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'reconnect',
|
||||||
|
queuedEventListener(onReconnect)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'configuration',
|
||||||
|
queuedEventListener(onConfiguration)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener('typing', queuedEventListener(onTyping));
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'sticker-pack',
|
||||||
|
queuedEventListener(onStickerPack)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'viewSync',
|
||||||
|
queuedEventListener(onViewSync)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'messageRequestResponse',
|
||||||
|
queuedEventListener(onMessageRequestResponse)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'profileKeyUpdate',
|
||||||
|
queuedEventListener(onProfileKeyUpdate)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'fetchLatest',
|
||||||
|
queuedEventListener(onFetchLatestSync)
|
||||||
|
);
|
||||||
|
messageReceiver.addEventListener('keys', queuedEventListener(onKeysSync));
|
||||||
|
|
||||||
AttachmentDownloads.start({
|
AttachmentDownloads.start({
|
||||||
getMessageReceiver: () => messageReceiver,
|
getMessageReceiver: () => messageReceiver,
|
||||||
|
@ -2093,7 +2184,7 @@ export async function startApp(): Promise<void> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (connectCount === 1) {
|
if (connectCount === 1) {
|
||||||
window.Signal.Stickers.downloadQueuedPacks();
|
Stickers.downloadQueuedPacks();
|
||||||
if (!newVersion) {
|
if (!newVersion) {
|
||||||
runStorageService();
|
runStorageService();
|
||||||
}
|
}
|
||||||
|
@ -2229,9 +2320,9 @@ export async function startApp(): Promise<void> {
|
||||||
syncMessage: true,
|
syncMessage: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const installedStickerPacks = window.Signal.Stickers.getInstalledStickerPacks();
|
const installedStickerPacks = Stickers.getInstalledStickerPacks();
|
||||||
if (installedStickerPacks.length) {
|
if (installedStickerPacks.length) {
|
||||||
const operations = installedStickerPacks.map((pack: WhatIsThis) => ({
|
const operations = installedStickerPacks.map(pack => ({
|
||||||
packId: pack.id,
|
packId: pack.id,
|
||||||
packKey: pack.key,
|
packKey: pack.key,
|
||||||
installed: true,
|
installed: true,
|
||||||
|
@ -2313,18 +2404,22 @@ export async function startApp(): Promise<void> {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'waitForEmptyEventQueue: Waiting for MessageReceiver empty event...'
|
'waitForEmptyEventQueue: Waiting for MessageReceiver empty event...'
|
||||||
);
|
);
|
||||||
let resolve: WhatIsThis;
|
let resolve: undefined | (() => void);
|
||||||
let reject: WhatIsThis;
|
let reject: undefined | ((error: Error) => void);
|
||||||
const promise = new Promise((innerResolve, innerReject) => {
|
const promise = new Promise<void>((innerResolve, innerReject) => {
|
||||||
resolve = innerResolve;
|
resolve = innerResolve;
|
||||||
reject = innerReject;
|
reject = innerReject;
|
||||||
});
|
});
|
||||||
|
|
||||||
const timeout = setTimeout(reject, FIVE_MINUTES);
|
const timeout = reject && setTimeout(reject, FIVE_MINUTES);
|
||||||
const onEmptyOnce = () => {
|
const onEmptyOnce = () => {
|
||||||
messageReceiver.removeEventListener('empty', onEmptyOnce);
|
if (messageReceiver) {
|
||||||
|
messageReceiver.removeEventListener('empty', onEmptyOnce);
|
||||||
|
}
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
resolve();
|
if (resolve) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
messageReceiver.addEventListener('empty', onEmptyOnce);
|
messageReceiver.addEventListener('empty', onEmptyOnce);
|
||||||
|
|
||||||
|
@ -2459,7 +2554,7 @@ export async function startApp(): Promise<void> {
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onConfiguration(ev: WhatIsThis) {
|
function onConfiguration(ev: ConfigurationEvent) {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
const { configuration } = ev;
|
const { configuration } = ev;
|
||||||
|
@ -2470,7 +2565,7 @@ export async function startApp(): Promise<void> {
|
||||||
linkPreviews,
|
linkPreviews,
|
||||||
} = configuration;
|
} = configuration;
|
||||||
|
|
||||||
window.storage.put('read-receipt-setting', readReceipts);
|
window.storage.put('read-receipt-setting', Boolean(readReceipts));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
unidentifiedDeliveryIndicators === true ||
|
unidentifiedDeliveryIndicators === true ||
|
||||||
|
@ -2491,7 +2586,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTyping(ev: WhatIsThis) {
|
function onTyping(ev: TypingEvent) {
|
||||||
// Note: this type of message is automatically removed from cache in MessageReceiver
|
// Note: this type of message is automatically removed from cache in MessageReceiver
|
||||||
|
|
||||||
const { typing, sender, senderUuid, senderDevice } = ev;
|
const { typing, sender, senderUuid, senderDevice } = ev;
|
||||||
|
@ -2557,12 +2652,12 @@ export async function startApp(): Promise<void> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onStickerPack(ev: WhatIsThis) {
|
async function onStickerPack(ev: StickerPackEvent) {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
const packs = ev.stickerPacks || [];
|
const packs = ev.stickerPacks;
|
||||||
|
|
||||||
packs.forEach((pack: WhatIsThis) => {
|
packs.forEach(pack => {
|
||||||
const { id, key, isInstall, isRemove } = pack || {};
|
const { id, key, isInstall, isRemove } = pack || {};
|
||||||
|
|
||||||
if (!id || !key || (!isInstall && !isRemove)) {
|
if (!id || !key || (!isInstall && !isRemove)) {
|
||||||
|
@ -2572,7 +2667,7 @@ export async function startApp(): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const status = window.Signal.Stickers.getStickerPackStatus(id);
|
const status = Stickers.getStickerPackStatus(id);
|
||||||
|
|
||||||
if (status === 'installed' && isRemove) {
|
if (status === 'installed' && isRemove) {
|
||||||
window.reduxActions.stickers.uninstallStickerPack(id, key, {
|
window.reduxActions.stickers.uninstallStickerPack(id, key, {
|
||||||
|
@ -2584,7 +2679,7 @@ export async function startApp(): Promise<void> {
|
||||||
fromSync: true,
|
fromSync: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
window.Signal.Stickers.downloadStickerPack(id, key, {
|
Stickers.downloadStickerPack(id, key, {
|
||||||
finalStatus: 'installed',
|
finalStatus: 'installed',
|
||||||
fromSync: true,
|
fromSync: true,
|
||||||
});
|
});
|
||||||
|
@ -2598,7 +2693,7 @@ export async function startApp(): Promise<void> {
|
||||||
await window.storage.put('synced_at', Date.now());
|
await window.storage.put('synced_at', Date.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onContactReceived(ev: WhatIsThis) {
|
async function onContactReceived(ev: ContactEvent) {
|
||||||
const details = ev.contactDetails;
|
const details = ev.contactDetails;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -2610,20 +2705,20 @@ export async function startApp(): Promise<void> {
|
||||||
// special case for syncing details about ourselves
|
// special case for syncing details about ourselves
|
||||||
if (details.profileKey) {
|
if (details.profileKey) {
|
||||||
window.log.info('Got sync message with our own profile key');
|
window.log.info('Got sync message with our own profile key');
|
||||||
ourProfileKeyService.set(details.profileKey);
|
ourProfileKeyService.set(typedArrayToArrayBuffer(details.profileKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const c = new window.Whisper.Conversation({
|
const c = new window.Whisper.Conversation(({
|
||||||
e164: details.number,
|
e164: details.number,
|
||||||
uuid: details.uuid,
|
uuid: details.uuid,
|
||||||
type: 'private',
|
type: 'private',
|
||||||
} as WhatIsThis);
|
} as Partial<ConversationAttributesType>) as WhatIsThis);
|
||||||
const validationError = c.validate();
|
const validationError = c.validate();
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'Invalid contact received:',
|
'Invalid contact received:',
|
||||||
Errors.toLogFormat(validationError as WhatIsThis)
|
Errors.toLogFormat(validationError)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2638,9 +2733,7 @@ export async function startApp(): Promise<void> {
|
||||||
const conversation = window.ConversationController.get(detailsId)!;
|
const conversation = window.ConversationController.get(detailsId)!;
|
||||||
|
|
||||||
if (details.profileKey) {
|
if (details.profileKey) {
|
||||||
const profileKey = window.Signal.Crypto.arrayBufferToBase64(
|
const profileKey = Bytes.toBase64(details.profileKey);
|
||||||
details.profileKey
|
|
||||||
);
|
|
||||||
conversation.setProfileKey(profileKey);
|
conversation.setProfileKey(profileKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2698,14 +2791,18 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
if (details.verified) {
|
if (details.verified) {
|
||||||
const { verified } = details;
|
const { verified } = details;
|
||||||
const verifiedEvent = new Event('verified');
|
const verifiedEvent = new VerifiedEvent(
|
||||||
verifiedEvent.verified = {
|
{
|
||||||
state: verified.state,
|
state: dropNull(verified.state),
|
||||||
destination: verified.destination,
|
destination: dropNull(verified.destination),
|
||||||
destinationUuid: verified.destinationUuid,
|
destinationUuid: dropNull(verified.destinationUuid),
|
||||||
identityKey: verified.identityKey.toArrayBuffer(),
|
identityKey: verified.identityKey
|
||||||
};
|
? typedArrayToArrayBuffer(verified.identityKey)
|
||||||
(verifiedEvent as WhatIsThis).viaContactSync = true;
|
: undefined,
|
||||||
|
viaContactSync: true,
|
||||||
|
},
|
||||||
|
noop
|
||||||
|
);
|
||||||
await onVerified(verifiedEvent);
|
await onVerified(verifiedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2726,11 +2823,11 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: this handler is only for v1 groups received via 'group sync' messages
|
// Note: this handler is only for v1 groups received via 'group sync' messages
|
||||||
async function onGroupReceived(ev: WhatIsThis) {
|
async function onGroupReceived(ev: GroupEvent) {
|
||||||
const details = ev.groupDetails;
|
const details = ev.groupDetails;
|
||||||
const { id } = details;
|
const { id } = details;
|
||||||
|
|
||||||
const idBuffer = window.Signal.Crypto.fromEncodedBinaryToArrayBuffer(id);
|
const idBuffer = id;
|
||||||
const idBytes = idBuffer.byteLength;
|
const idBytes = idBuffer.byteLength;
|
||||||
if (idBytes !== 16) {
|
if (idBytes !== 16) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
|
@ -2740,7 +2837,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversation = await window.ConversationController.getOrCreateAndWait(
|
const conversation = await window.ConversationController.getOrCreateAndWait(
|
||||||
id,
|
Bytes.toBinary(id),
|
||||||
'group'
|
'group'
|
||||||
);
|
);
|
||||||
if (isGroupV2(conversation.attributes)) {
|
if (isGroupV2(conversation.attributes)) {
|
||||||
|
@ -2751,18 +2848,18 @@ export async function startApp(): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const memberConversations = details.membersE164.map((e164: WhatIsThis) =>
|
const memberConversations = details.membersE164.map(e164 =>
|
||||||
window.ConversationController.getOrCreate(e164, 'private')
|
window.ConversationController.getOrCreate(e164, 'private')
|
||||||
);
|
);
|
||||||
|
|
||||||
const members = memberConversations.map((c: WhatIsThis) => c.get('id'));
|
const members = memberConversations.map(c => c.get('id'));
|
||||||
|
|
||||||
const updates = {
|
const updates: Partial<ConversationAttributesType> = {
|
||||||
name: details.name,
|
name: details.name,
|
||||||
members,
|
members,
|
||||||
type: 'group',
|
type: 'group',
|
||||||
inbox_position: details.inboxPosition,
|
inbox_position: details.inboxPosition,
|
||||||
} as WhatIsThis;
|
};
|
||||||
|
|
||||||
if (details.active) {
|
if (details.active) {
|
||||||
updates.left = false;
|
updates.left = false;
|
||||||
|
@ -2823,8 +2920,16 @@ export async function startApp(): Promise<void> {
|
||||||
data,
|
data,
|
||||||
confirm,
|
confirm,
|
||||||
messageDescriptor,
|
messageDescriptor,
|
||||||
}: WhatIsThis) {
|
}: {
|
||||||
const profileKey = data.message.profileKey.toString('base64');
|
data: MessageEventData;
|
||||||
|
confirm: () => void;
|
||||||
|
messageDescriptor: MessageDescriptor;
|
||||||
|
}) {
|
||||||
|
const { profileKey } = data.message;
|
||||||
|
strictAssert(
|
||||||
|
profileKey !== undefined,
|
||||||
|
'handleMessageReceivedProfileUpdate: missing profileKey'
|
||||||
|
);
|
||||||
const sender = window.ConversationController.get(messageDescriptor.id);
|
const sender = window.ConversationController.get(messageDescriptor.id);
|
||||||
|
|
||||||
if (sender) {
|
if (sender) {
|
||||||
|
@ -2864,7 +2969,7 @@ export async function startApp(): Promise<void> {
|
||||||
// Note: We do very little in this function, since everything in handleDataMessage is
|
// Note: We do very little in this function, since everything in handleDataMessage is
|
||||||
// inside a conversation-specific queue(). Any code here might run before an earlier
|
// inside a conversation-specific queue(). Any code here might run before an earlier
|
||||||
// message is processed in handleDataMessage().
|
// message is processed in handleDataMessage().
|
||||||
function onMessageReceived(event: WhatIsThis) {
|
function onMessageReceived(event: MessageEvent) {
|
||||||
const { data, confirm } = event;
|
const { data, confirm } = event;
|
||||||
|
|
||||||
const messageDescriptor = getMessageDescriptor({
|
const messageDescriptor = getMessageDescriptor({
|
||||||
|
@ -2903,10 +3008,13 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.message.reaction) {
|
if (data.message.reaction) {
|
||||||
window.normalizeUuids(
|
strictAssert(
|
||||||
data.message.reaction,
|
data.message.reaction.targetAuthorUuid,
|
||||||
['targetAuthorUuid'],
|
'Reaction without targetAuthorUuid'
|
||||||
'background::onMessageReceived'
|
);
|
||||||
|
const targetAuthorUuid = normalizeUuid(
|
||||||
|
data.message.reaction.targetAuthorUuid,
|
||||||
|
'DataMessage.Reaction.targetAuthorUuid'
|
||||||
);
|
);
|
||||||
|
|
||||||
const { reaction } = data.message;
|
const { reaction } = data.message;
|
||||||
|
@ -2924,7 +3032,7 @@ export async function startApp(): Promise<void> {
|
||||||
const reactionModel = Reactions.getSingleton().add({
|
const reactionModel = Reactions.getSingleton().add({
|
||||||
emoji: reaction.emoji,
|
emoji: reaction.emoji,
|
||||||
remove: reaction.remove,
|
remove: reaction.remove,
|
||||||
targetAuthorUuid: reaction.targetAuthorUuid,
|
targetAuthorUuid,
|
||||||
targetTimestamp: reaction.targetTimestamp,
|
targetTimestamp: reaction.targetTimestamp,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
fromId: window.ConversationController.ensureContactIds({
|
fromId: window.ConversationController.ensureContactIds({
|
||||||
|
@ -2965,7 +3073,7 @@ export async function startApp(): Promise<void> {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onProfileKeyUpdate({ data, confirm }: WhatIsThis) {
|
async function onProfileKeyUpdate({ data, confirm }: ProfileKeyUpdateEvent) {
|
||||||
const conversationId = window.ConversationController.ensureContactIds({
|
const conversationId = window.ConversationController.ensureContactIds({
|
||||||
e164: data.source,
|
e164: data.source,
|
||||||
uuid: data.sourceUuid,
|
uuid: data.sourceUuid,
|
||||||
|
@ -3007,7 +3115,11 @@ export async function startApp(): Promise<void> {
|
||||||
data,
|
data,
|
||||||
confirm,
|
confirm,
|
||||||
messageDescriptor,
|
messageDescriptor,
|
||||||
}: WhatIsThis) {
|
}: {
|
||||||
|
data: SentEventData;
|
||||||
|
confirm: () => void;
|
||||||
|
messageDescriptor: MessageDescriptor;
|
||||||
|
}) {
|
||||||
// First set profileSharing = true for the conversation we sent to
|
// First set profileSharing = true for the conversation we sent to
|
||||||
const { id } = messageDescriptor;
|
const { id } = messageDescriptor;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
@ -3020,7 +3132,11 @@ export async function startApp(): Promise<void> {
|
||||||
const ourId = window.ConversationController.getOurConversationId();
|
const ourId = window.ConversationController.getOurConversationId();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const me = window.ConversationController.get(ourId)!;
|
const me = window.ConversationController.get(ourId)!;
|
||||||
const profileKey = data.message.profileKey.toString('base64');
|
const { profileKey } = data.message;
|
||||||
|
strictAssert(
|
||||||
|
profileKey !== undefined,
|
||||||
|
'handleMessageSentProfileUpdate: missing profileKey'
|
||||||
|
);
|
||||||
|
|
||||||
// Will do the save for us if needed
|
// Will do the save for us if needed
|
||||||
await me.setProfileKey(profileKey);
|
await me.setProfileKey(profileKey);
|
||||||
|
@ -3028,18 +3144,17 @@ export async function startApp(): Promise<void> {
|
||||||
return confirm();
|
return confirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSentMessage(data: WhatIsThis, descriptor: MessageDescriptor) {
|
function createSentMessage(
|
||||||
|
data: SentEventData,
|
||||||
|
descriptor: MessageDescriptor
|
||||||
|
) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const timestamp = data.timestamp || now;
|
const timestamp = data.timestamp || now;
|
||||||
|
|
||||||
const unidentifiedStatus: Array<SyncMessageClass.Sent.UnidentifiedDeliveryStatus> = Array.isArray(
|
const { unidentifiedStatus = [] } = data;
|
||||||
data.unidentifiedStatus
|
|
||||||
)
|
|
||||||
? data.unidentifiedStatus
|
|
||||||
: [];
|
|
||||||
|
|
||||||
let sentTo: Array<string> = [];
|
let sentTo: Array<string> = [];
|
||||||
|
|
||||||
|
let unidentifiedDeliveries: Array<string> = [];
|
||||||
if (unidentifiedStatus.length) {
|
if (unidentifiedStatus.length) {
|
||||||
sentTo = unidentifiedStatus
|
sentTo = unidentifiedStatus
|
||||||
.map(item => item.destinationUuid || item.destination)
|
.map(item => item.destinationUuid || item.destination)
|
||||||
|
@ -3048,13 +3163,12 @@ export async function startApp(): Promise<void> {
|
||||||
const unidentified = window._.filter(data.unidentifiedStatus, item =>
|
const unidentified = window._.filter(data.unidentifiedStatus, item =>
|
||||||
Boolean(item.unidentified)
|
Boolean(item.unidentified)
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line no-param-reassign
|
unidentifiedDeliveries = unidentified
|
||||||
data.unidentifiedDeliveries = unidentified.map(
|
.map(item => item.destinationUuid || item.destination)
|
||||||
item => item.destinationUuid || item.destination
|
.filter(isNotNil);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new window.Whisper.Message({
|
return new window.Whisper.Message(({
|
||||||
source: window.textsecure.storage.user.getNumber(),
|
source: window.textsecure.storage.user.getNumber(),
|
||||||
sourceUuid: window.textsecure.storage.user.getUuid(),
|
sourceUuid: window.textsecure.storage.user.getUuid(),
|
||||||
sourceDevice: data.device,
|
sourceDevice: data.device,
|
||||||
|
@ -3067,12 +3181,12 @@ export async function startApp(): Promise<void> {
|
||||||
timestamp,
|
timestamp,
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
sent: true,
|
sent: true,
|
||||||
unidentifiedDeliveries: data.unidentifiedDeliveries || [],
|
unidentifiedDeliveries,
|
||||||
expirationStartTimestamp: Math.min(
|
expirationStartTimestamp: Math.min(
|
||||||
data.expirationStartTimestamp || timestamp,
|
data.expirationStartTimestamp || timestamp,
|
||||||
now
|
now
|
||||||
),
|
),
|
||||||
} as WhatIsThis);
|
} as Partial<MessageAttributesType>) as WhatIsThis);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Works with 'sent' and 'message' data sent from MessageReceiver, with a little massage
|
// Works with 'sent' and 'message' data sent from MessageReceiver, with a little massage
|
||||||
|
@ -3084,11 +3198,11 @@ export async function startApp(): Promise<void> {
|
||||||
destination,
|
destination,
|
||||||
destinationUuid,
|
destinationUuid,
|
||||||
}: {
|
}: {
|
||||||
message: DataMessageClass;
|
message: ProcessedDataMessage;
|
||||||
source: string;
|
source?: string;
|
||||||
sourceUuid: string;
|
sourceUuid?: string;
|
||||||
destination: string;
|
destination?: string;
|
||||||
destinationUuid: string;
|
destinationUuid?: string;
|
||||||
}): MessageDescriptor => {
|
}): MessageDescriptor => {
|
||||||
if (message.groupV2) {
|
if (message.groupV2) {
|
||||||
const { id } = message.groupV2;
|
const { id } = message.groupV2;
|
||||||
|
@ -3186,14 +3300,19 @@ export async function startApp(): Promise<void> {
|
||||||
// Note: We do very little in this function, since everything in handleDataMessage is
|
// Note: We do very little in this function, since everything in handleDataMessage is
|
||||||
// inside a conversation-specific queue(). Any code here might run before an earlier
|
// inside a conversation-specific queue(). Any code here might run before an earlier
|
||||||
// message is processed in handleDataMessage().
|
// message is processed in handleDataMessage().
|
||||||
function onSentMessage(event: WhatIsThis) {
|
function onSentMessage(event: SentEvent) {
|
||||||
const { data, confirm } = event;
|
const { data, confirm } = event;
|
||||||
|
|
||||||
|
const source = window.textsecure.storage.user.getNumber();
|
||||||
|
const sourceUuid = window.textsecure.storage.user.getUuid();
|
||||||
|
strictAssert(source && sourceUuid, 'Missing user number and uuid');
|
||||||
|
|
||||||
const messageDescriptor = getMessageDescriptor({
|
const messageDescriptor = getMessageDescriptor({
|
||||||
...data,
|
...data,
|
||||||
|
|
||||||
// 'sent' event: the sender is always us!
|
// 'sent' event: the sender is always us!
|
||||||
source: window.textsecure.storage.user.getNumber(),
|
source,
|
||||||
sourceUuid: window.textsecure.storage.user.getUuid(),
|
sourceUuid,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { PROFILE_KEY_UPDATE } = Proto.DataMessage.Flags;
|
const { PROFILE_KEY_UPDATE } = Proto.DataMessage.Flags;
|
||||||
|
@ -3210,10 +3329,13 @@ export async function startApp(): Promise<void> {
|
||||||
const message = createSentMessage(data, messageDescriptor);
|
const message = createSentMessage(data, messageDescriptor);
|
||||||
|
|
||||||
if (data.message.reaction) {
|
if (data.message.reaction) {
|
||||||
window.normalizeUuids(
|
strictAssert(
|
||||||
data.message.reaction,
|
data.message.reaction.targetAuthorUuid,
|
||||||
['targetAuthorUuid'],
|
'Reaction without targetAuthorUuid'
|
||||||
'background::onSentMessage'
|
);
|
||||||
|
const targetAuthorUuid = normalizeUuid(
|
||||||
|
data.message.reaction.targetAuthorUuid,
|
||||||
|
'DataMessage.Reaction.targetAuthorUuid'
|
||||||
);
|
);
|
||||||
|
|
||||||
const { reaction } = data.message;
|
const { reaction } = data.message;
|
||||||
|
@ -3228,7 +3350,7 @@ export async function startApp(): Promise<void> {
|
||||||
const reactionModel = Reactions.getSingleton().add({
|
const reactionModel = Reactions.getSingleton().add({
|
||||||
emoji: reaction.emoji,
|
emoji: reaction.emoji,
|
||||||
remove: reaction.remove,
|
remove: reaction.remove,
|
||||||
targetAuthorUuid: reaction.targetAuthorUuid,
|
targetAuthorUuid,
|
||||||
targetTimestamp: reaction.targetTimestamp,
|
targetTimestamp: reaction.targetTimestamp,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
fromId: window.ConversationController.getOurConversationId(),
|
fromId: window.ConversationController.getOurConversationId(),
|
||||||
|
@ -3246,7 +3368,7 @@ export async function startApp(): Promise<void> {
|
||||||
window.log.info('Queuing sent DOE for', del.targetSentTimestamp);
|
window.log.info('Queuing sent DOE for', del.targetSentTimestamp);
|
||||||
const deleteModel = Deletes.getSingleton().add({
|
const deleteModel = Deletes.getSingleton().add({
|
||||||
targetSentTimestamp: del.targetSentTimestamp,
|
targetSentTimestamp: del.targetSentTimestamp,
|
||||||
serverTimestamp: del.serverTimestamp,
|
serverTimestamp: data.serverTimestamp,
|
||||||
fromId: window.ConversationController.getOurConversationId(),
|
fromId: window.ConversationController.getOurConversationId(),
|
||||||
});
|
});
|
||||||
// Note: We do not wait for completion here
|
// Note: We do not wait for completion here
|
||||||
|
@ -3274,14 +3396,14 @@ export async function startApp(): Promise<void> {
|
||||||
};
|
};
|
||||||
|
|
||||||
function initIncomingMessage(
|
function initIncomingMessage(
|
||||||
data: WhatIsThis,
|
data: MessageEventData,
|
||||||
descriptor: MessageDescriptor
|
descriptor: MessageDescriptor
|
||||||
) {
|
) {
|
||||||
assert(
|
assert(
|
||||||
Boolean(data.receivedAtCounter),
|
Boolean(data.receivedAtCounter),
|
||||||
`Did not receive receivedAtCounter for message: ${data.timestamp}`
|
`Did not receive receivedAtCounter for message: ${data.timestamp}`
|
||||||
);
|
);
|
||||||
return new window.Whisper.Message({
|
return new window.Whisper.Message(({
|
||||||
source: data.source,
|
source: data.source,
|
||||||
sourceUuid: data.sourceUuid,
|
sourceUuid: data.sourceUuid,
|
||||||
sourceDevice: data.sourceDevice,
|
sourceDevice: data.sourceDevice,
|
||||||
|
@ -3293,13 +3415,14 @@ export async function startApp(): Promise<void> {
|
||||||
conversationId: descriptor.id,
|
conversationId: descriptor.id,
|
||||||
unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived,
|
unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived,
|
||||||
type: 'incoming',
|
type: 'incoming',
|
||||||
unread: 1,
|
unread: true,
|
||||||
} as WhatIsThis);
|
timestamp: data.timestamp,
|
||||||
|
} as Partial<MessageAttributesType>) as WhatIsThis);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns `false` if this message isn't a group call message.
|
// Returns `false` if this message isn't a group call message.
|
||||||
function handleGroupCallUpdateMessage(
|
function handleGroupCallUpdateMessage(
|
||||||
message: DataMessageClass,
|
message: ProcessedDataMessage,
|
||||||
messageDescriptor: MessageDescriptor
|
messageDescriptor: MessageDescriptor
|
||||||
): boolean {
|
): boolean {
|
||||||
if (message.groupCallUpdate) {
|
if (message.groupCallUpdate) {
|
||||||
|
@ -3329,7 +3452,7 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
if (messageReceiver) {
|
if (messageReceiver) {
|
||||||
messageReceiver.unregisterBatchers();
|
messageReceiver.unregisterBatchers();
|
||||||
messageReceiver = null;
|
messageReceiver = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
onEmpty();
|
onEmpty();
|
||||||
|
@ -3399,7 +3522,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onError(ev: WhatIsThis) {
|
function onError(ev: ErrorEvent) {
|
||||||
const { error } = ev;
|
const { error } = ev;
|
||||||
window.log.error('background onError:', Errors.toLogFormat(error));
|
window.log.error('background onError:', Errors.toLogFormat(error));
|
||||||
|
|
||||||
|
@ -3436,10 +3559,6 @@ export async function startApp(): Promise<void> {
|
||||||
window.log.warn('background onError: Doing nothing with incoming error');
|
window.log.warn('background onError: Doing nothing with incoming error');
|
||||||
}
|
}
|
||||||
|
|
||||||
type RetryRequestEventType = Event & {
|
|
||||||
retryRequest: RetryRequestType;
|
|
||||||
};
|
|
||||||
|
|
||||||
function isInList(
|
function isInList(
|
||||||
conversation: ConversationModel,
|
conversation: ConversationModel,
|
||||||
list: Array<string | undefined | null> | undefined
|
list: Array<string | undefined | null> | undefined
|
||||||
|
@ -3471,7 +3590,7 @@ export async function startApp(): Promise<void> {
|
||||||
requesterUuid,
|
requesterUuid,
|
||||||
requesterDevice,
|
requesterDevice,
|
||||||
senderDevice,
|
senderDevice,
|
||||||
}: RetryRequestType): Promise<void> {
|
}: RetryRequestEventData): Promise<void> {
|
||||||
const ourDeviceId = parseIntOrThrow(
|
const ourDeviceId = parseIntOrThrow(
|
||||||
window.textsecure.storage.user.getDeviceId(),
|
window.textsecure.storage.user.getDeviceId(),
|
||||||
'archiveSessionOnMatch/getDeviceId'
|
'archiveSessionOnMatch/getDeviceId'
|
||||||
|
@ -3486,7 +3605,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendDistributionMessageOrNullMessage(
|
async function sendDistributionMessageOrNullMessage(
|
||||||
options: RetryRequestType
|
options: RetryRequestEventData
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { groupId, requesterUuid } = options;
|
const { groupId, requesterUuid } = options;
|
||||||
let sentDistributionMessage = false;
|
let sentDistributionMessage = false;
|
||||||
|
@ -3558,7 +3677,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onRetryRequest(event: RetryRequestEventType) {
|
async function onRetryRequest(event: RetryRequestEvent) {
|
||||||
const { retryRequest } = event;
|
const { retryRequest } = event;
|
||||||
const {
|
const {
|
||||||
requesterDevice,
|
requesterDevice,
|
||||||
|
@ -3637,11 +3756,7 @@ export async function startApp(): Promise<void> {
|
||||||
await targetMessage.resend(requesterUuid);
|
await targetMessage.resend(requesterUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
type DecryptionErrorEventType = Event & {
|
async function onDecryptionError(event: DecryptionErrorEvent) {
|
||||||
decryptionError: DecryptionErrorType;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function onDecryptionError(event: DecryptionErrorEventType) {
|
|
||||||
const { decryptionError } = event;
|
const { decryptionError } = event;
|
||||||
const { senderUuid, senderDevice, timestamp } = decryptionError;
|
const { senderUuid, senderDevice, timestamp } = decryptionError;
|
||||||
const logId = `${senderUuid}.${senderDevice} ${timestamp}`;
|
const logId = `${senderUuid}.${senderDevice} ${timestamp}`;
|
||||||
|
@ -3666,7 +3781,7 @@ export async function startApp(): Promise<void> {
|
||||||
window.log.info(`onDecryptionError/${logId}: ...complete`);
|
window.log.info(`onDecryptionError/${logId}: ...complete`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestResend(decryptionError: DecryptionErrorType) {
|
async function requestResend(decryptionError: DecryptionErrorEventData) {
|
||||||
const {
|
const {
|
||||||
cipherTextBytes,
|
cipherTextBytes,
|
||||||
cipherTextType,
|
cipherTextType,
|
||||||
|
@ -3784,7 +3899,9 @@ export async function startApp(): Promise<void> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function startAutomaticSessionReset(decryptionError: DecryptionErrorType) {
|
function startAutomaticSessionReset(
|
||||||
|
decryptionError: DecryptionErrorEventData
|
||||||
|
) {
|
||||||
const { senderUuid, senderDevice, timestamp } = decryptionError;
|
const { senderUuid, senderDevice, timestamp } = decryptionError;
|
||||||
const logId = `${senderUuid}.${senderDevice} ${timestamp}`;
|
const logId = `${senderUuid}.${senderDevice} ${timestamp}`;
|
||||||
|
|
||||||
|
@ -3818,7 +3935,7 @@ export async function startApp(): Promise<void> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onViewSync(ev: WhatIsThis) {
|
async function onViewSync(ev: ViewSyncEvent) {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
const { source, sourceUuid, timestamp } = ev;
|
const { source, sourceUuid, timestamp } = ev;
|
||||||
|
@ -3833,7 +3950,7 @@ export async function startApp(): Promise<void> {
|
||||||
ViewSyncs.getSingleton().onSync(sync);
|
ViewSyncs.getSingleton().onSync(sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onFetchLatestSync(ev: WhatIsThis) {
|
async function onFetchLatestSync(ev: FetchLatestEvent) {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
const { eventType } = ev;
|
const { eventType } = ev;
|
||||||
|
@ -3856,7 +3973,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onKeysSync(ev: WhatIsThis) {
|
async function onKeysSync(ev: KeysEvent) {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
const { storageServiceKey } = ev;
|
const { storageServiceKey } = ev;
|
||||||
|
@ -3877,7 +3994,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onMessageRequestResponse(ev: WhatIsThis) {
|
async function onMessageRequestResponse(ev: MessageRequestResponseEvent) {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -3907,9 +4024,9 @@ export async function startApp(): Promise<void> {
|
||||||
MessageRequests.getSingleton().onResponse(sync);
|
MessageRequests.getSingleton().onResponse(sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReadReceipt(ev: WhatIsThis) {
|
function onReadReceipt(ev: ReadEvent) {
|
||||||
const readAt = ev.timestamp;
|
|
||||||
const { envelopeTimestamp, timestamp, source, sourceUuid } = ev.read;
|
const { envelopeTimestamp, timestamp, source, sourceUuid } = ev.read;
|
||||||
|
const readAt = envelopeTimestamp;
|
||||||
const reader = window.ConversationController.ensureContactIds({
|
const reader = window.ConversationController.ensureContactIds({
|
||||||
e164: source,
|
e164: source,
|
||||||
uuid: sourceUuid,
|
uuid: sourceUuid,
|
||||||
|
@ -3941,9 +4058,9 @@ export async function startApp(): Promise<void> {
|
||||||
ReadReceipts.getSingleton().onReceipt(receipt);
|
ReadReceipts.getSingleton().onReceipt(receipt);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReadSync(ev: WhatIsThis) {
|
function onReadSync(ev: ReadSyncEvent) {
|
||||||
const readAt = ev.timestamp;
|
|
||||||
const { envelopeTimestamp, sender, senderUuid, timestamp } = ev.read;
|
const { envelopeTimestamp, sender, senderUuid, timestamp } = ev.read;
|
||||||
|
const readAt = envelopeTimestamp;
|
||||||
const senderId = window.ConversationController.ensureContactIds({
|
const senderId = window.ConversationController.ensureContactIds({
|
||||||
e164: sender,
|
e164: sender,
|
||||||
uuid: senderUuid,
|
uuid: senderUuid,
|
||||||
|
@ -3974,7 +4091,7 @@ export async function startApp(): Promise<void> {
|
||||||
return ReadSyncs.getSingleton().onReceipt(receipt);
|
return ReadSyncs.getSingleton().onReceipt(receipt);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onVerified(ev: WhatIsThis) {
|
async function onVerified(ev: VerifiedEvent) {
|
||||||
const e164 = ev.verified.destination;
|
const e164 = ev.verified.destination;
|
||||||
const uuid = ev.verified.destinationUuid;
|
const uuid = ev.verified.destinationUuid;
|
||||||
const key = ev.verified.identityKey;
|
const key = ev.verified.identityKey;
|
||||||
|
@ -3984,18 +4101,18 @@ export async function startApp(): Promise<void> {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
const c = new window.Whisper.Conversation({
|
const c = new window.Whisper.Conversation(({
|
||||||
e164,
|
e164,
|
||||||
uuid,
|
uuid,
|
||||||
type: 'private',
|
type: 'private',
|
||||||
} as WhatIsThis);
|
} as Partial<ConversationAttributesType>) as WhatIsThis);
|
||||||
const error = c.validate();
|
const error = c.validate();
|
||||||
if (error) {
|
if (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'Invalid verified sync received:',
|
'Invalid verified sync received:',
|
||||||
e164,
|
e164,
|
||||||
uuid,
|
uuid,
|
||||||
Errors.toLogFormat(error as WhatIsThis)
|
Errors.toLogFormat(error)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -4019,7 +4136,7 @@ export async function startApp(): Promise<void> {
|
||||||
e164,
|
e164,
|
||||||
uuid,
|
uuid,
|
||||||
state,
|
state,
|
||||||
ev.viaContactSync ? 'via contact sync' : ''
|
ev.verified.viaContactSync ? 'via contact sync' : ''
|
||||||
);
|
);
|
||||||
|
|
||||||
const verifiedId = window.ConversationController.ensureContactIds({
|
const verifiedId = window.ConversationController.ensureContactIds({
|
||||||
|
@ -4031,7 +4148,7 @@ export async function startApp(): Promise<void> {
|
||||||
const contact = window.ConversationController.get(verifiedId)!;
|
const contact = window.ConversationController.get(verifiedId)!;
|
||||||
const options = {
|
const options = {
|
||||||
viaSyncMessage: true,
|
viaSyncMessage: true,
|
||||||
viaContactSync: ev.viaContactSync,
|
viaContactSync: ev.verified.viaContactSync,
|
||||||
key,
|
key,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4044,7 +4161,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDeliveryReceipt(ev: WhatIsThis) {
|
function onDeliveryReceipt(ev: DeliveryEvent) {
|
||||||
const { deliveryReceipt } = ev;
|
const { deliveryReceipt } = ev;
|
||||||
const {
|
const {
|
||||||
envelopeTimestamp,
|
envelopeTimestamp,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { storiesOf } from '@storybook/react';
|
||||||
import { setup as setupI18n } from '../../../js/modules/i18n';
|
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||||
import enMessages from '../../../_locales/en/messages.json';
|
import enMessages from '../../../_locales/en/messages.json';
|
||||||
import { GroupV2ChangeType } from '../../groups';
|
import { GroupV2ChangeType } from '../../groups';
|
||||||
|
import { SignalService as Proto } from '../../protobuf';
|
||||||
import { SmartContactRendererType } from '../../groupChange';
|
import { SmartContactRendererType } from '../../groupChange';
|
||||||
import { GroupV2Change } from './GroupV2Change';
|
import { GroupV2Change } from './GroupV2Change';
|
||||||
|
|
||||||
|
@ -20,25 +21,8 @@ const CONTACT_C = 'CONTACT_C';
|
||||||
const ADMIN_A = 'ADMIN_A';
|
const ADMIN_A = 'ADMIN_A';
|
||||||
const INVITEE_A = 'INVITEE_A';
|
const INVITEE_A = 'INVITEE_A';
|
||||||
|
|
||||||
class AccessControlEnum {
|
const AccessControlEnum = Proto.AccessControl.AccessRequired;
|
||||||
static UNKNOWN = 0;
|
const RoleEnum = Proto.Member.Role;
|
||||||
|
|
||||||
static ANY = 1;
|
|
||||||
|
|
||||||
static MEMBER = 2;
|
|
||||||
|
|
||||||
static ADMINISTRATOR = 3;
|
|
||||||
|
|
||||||
static UNSATISFIABLE = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RoleEnum {
|
|
||||||
static UNKNOWN = 0;
|
|
||||||
|
|
||||||
static ADMINISTRATOR = 1;
|
|
||||||
|
|
||||||
static DEFAULT = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderContact: SmartContactRendererType = (conversationId: string) => (
|
const renderContact: SmartContactRendererType = (conversationId: string) => (
|
||||||
<React.Fragment key={conversationId}>
|
<React.Fragment key={conversationId}>
|
||||||
|
@ -48,13 +32,11 @@ const renderContact: SmartContactRendererType = (conversationId: string) => (
|
||||||
|
|
||||||
const renderChange = (change: GroupV2ChangeType, groupName?: string) => (
|
const renderChange = (change: GroupV2ChangeType, groupName?: string) => (
|
||||||
<GroupV2Change
|
<GroupV2Change
|
||||||
AccessControlEnum={AccessControlEnum}
|
|
||||||
change={change}
|
change={change}
|
||||||
groupName={groupName}
|
groupName={groupName}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
ourConversationId={OUR_ID}
|
ourConversationId={OUR_ID}
|
||||||
renderContact={renderContact}
|
renderContact={renderContact}
|
||||||
RoleEnum={RoleEnum}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,10 @@ import { GroupV2ChangeType, GroupV2DescriptionChangeType } from '../../groups';
|
||||||
import { renderChange, SmartContactRendererType } from '../../groupChange';
|
import { renderChange, SmartContactRendererType } from '../../groupChange';
|
||||||
import { Modal } from '../Modal';
|
import { Modal } from '../Modal';
|
||||||
|
|
||||||
import { AccessControlClass, MemberClass } from '../../textsecure.d';
|
|
||||||
|
|
||||||
export type PropsDataType = {
|
export type PropsDataType = {
|
||||||
groupName?: string;
|
groupName?: string;
|
||||||
ourConversationId: string;
|
ourConversationId: string;
|
||||||
change: GroupV2ChangeType;
|
change: GroupV2ChangeType;
|
||||||
AccessControlEnum: typeof AccessControlClass.AccessRequired;
|
|
||||||
RoleEnum: typeof MemberClass.Role;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PropsHousekeepingType = {
|
export type PropsHousekeepingType = {
|
||||||
|
@ -40,15 +36,7 @@ function renderStringToIntl(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GroupV2Change(props: PropsType): ReactElement {
|
export function GroupV2Change(props: PropsType): ReactElement {
|
||||||
const {
|
const { change, groupName, i18n, ourConversationId, renderContact } = props;
|
||||||
AccessControlEnum,
|
|
||||||
change,
|
|
||||||
groupName,
|
|
||||||
i18n,
|
|
||||||
ourConversationId,
|
|
||||||
renderContact,
|
|
||||||
RoleEnum,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [
|
const [
|
||||||
isGroupDescriptionDialogOpen,
|
isGroupDescriptionDialogOpen,
|
||||||
|
@ -64,12 +52,10 @@ export function GroupV2Change(props: PropsType): ReactElement {
|
||||||
<div className="module-group-v2-change">
|
<div className="module-group-v2-change">
|
||||||
<div className="module-group-v2-change--icon" />
|
<div className="module-group-v2-change--icon" />
|
||||||
{renderChange(change, {
|
{renderChange(change, {
|
||||||
AccessControlEnum,
|
|
||||||
i18n,
|
i18n,
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
renderContact,
|
renderContact,
|
||||||
renderString: renderStringToIntl,
|
renderString: renderStringToIntl,
|
||||||
RoleEnum,
|
|
||||||
}).map((item: FullJSXType, index: number) => (
|
}).map((item: FullJSXType, index: number) => (
|
||||||
// Difficult to find a unique key for this type
|
// Difficult to find a unique key for this type
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { action } from '@storybook/addon-actions';
|
||||||
import { setup as setupI18n } from '../../../../js/modules/i18n';
|
import { setup as setupI18n } from '../../../../js/modules/i18n';
|
||||||
import enMessages from '../../../../_locales/en/messages.json';
|
import enMessages from '../../../../_locales/en/messages.json';
|
||||||
import { GroupLinkManagement, PropsType } from './GroupLinkManagement';
|
import { GroupLinkManagement, PropsType } from './GroupLinkManagement';
|
||||||
|
import { SignalService as Proto } from '../../../protobuf';
|
||||||
import { ConversationType } from '../../../state/ducks/conversations';
|
import { ConversationType } from '../../../state/ducks/conversations';
|
||||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||||
|
|
||||||
|
@ -19,17 +20,7 @@ const story = storiesOf(
|
||||||
module
|
module
|
||||||
);
|
);
|
||||||
|
|
||||||
class AccessEnum {
|
const AccessControlEnum = Proto.AccessControl.AccessRequired;
|
||||||
static ANY = 0;
|
|
||||||
|
|
||||||
static UNKNOWN = 1;
|
|
||||||
|
|
||||||
static MEMBER = 2;
|
|
||||||
|
|
||||||
static ADMINISTRATOR = 3;
|
|
||||||
|
|
||||||
static UNSATISFIABLE = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConversation(
|
function getConversation(
|
||||||
groupLink?: string,
|
groupLink?: string,
|
||||||
|
@ -47,7 +38,7 @@ function getConversation(
|
||||||
accessControlAddFromInviteLink:
|
accessControlAddFromInviteLink:
|
||||||
accessControlAddFromInviteLink !== undefined
|
accessControlAddFromInviteLink !== undefined
|
||||||
? accessControlAddFromInviteLink
|
? accessControlAddFromInviteLink
|
||||||
: AccessEnum.UNSATISFIABLE,
|
: AccessControlEnum.UNSATISFIABLE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +46,6 @@ const createProps = (
|
||||||
conversation?: ConversationType,
|
conversation?: ConversationType,
|
||||||
isAdmin = false
|
isAdmin = false
|
||||||
): PropsType => ({
|
): PropsType => ({
|
||||||
accessEnum: AccessEnum,
|
|
||||||
changeHasGroupLink: action('changeHasGroupLink'),
|
changeHasGroupLink: action('changeHasGroupLink'),
|
||||||
conversation: conversation || getConversation(),
|
conversation: conversation || getConversation(),
|
||||||
copyGroupLink: action('copyGroupLink'),
|
copyGroupLink: action('copyGroupLink'),
|
||||||
|
@ -75,7 +65,7 @@ story.add('Off (Admin)', () => {
|
||||||
|
|
||||||
story.add('On (Admin)', () => {
|
story.add('On (Admin)', () => {
|
||||||
const props = createProps(
|
const props = createProps(
|
||||||
getConversation('https://signal.group/1', AccessEnum.ANY),
|
getConversation('https://signal.group/1', AccessControlEnum.ANY),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -84,7 +74,7 @@ story.add('On (Admin)', () => {
|
||||||
|
|
||||||
story.add('On (Admin + Admin Approval Needed)', () => {
|
story.add('On (Admin + Admin Approval Needed)', () => {
|
||||||
const props = createProps(
|
const props = createProps(
|
||||||
getConversation('https://signal.group/1', AccessEnum.ADMINISTRATOR),
|
getConversation('https://signal.group/1', AccessControlEnum.ADMINISTRATOR),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -93,7 +83,7 @@ story.add('On (Admin + Admin Approval Needed)', () => {
|
||||||
|
|
||||||
story.add('On (Non-admin)', () => {
|
story.add('On (Non-admin)', () => {
|
||||||
const props = createProps(
|
const props = createProps(
|
||||||
getConversation('https://signal.group/1', AccessEnum.ANY)
|
getConversation('https://signal.group/1', AccessControlEnum.ANY)
|
||||||
);
|
);
|
||||||
|
|
||||||
return <GroupLinkManagement {...props} />;
|
return <GroupLinkManagement {...props} />;
|
||||||
|
|
|
@ -4,15 +4,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { ConversationDetailsIcon } from './ConversationDetailsIcon';
|
import { ConversationDetailsIcon } from './ConversationDetailsIcon';
|
||||||
|
import { SignalService as Proto } from '../../../protobuf';
|
||||||
import { ConversationType } from '../../../state/ducks/conversations';
|
import { ConversationType } from '../../../state/ducks/conversations';
|
||||||
import { LocalizerType } from '../../../types/Util';
|
import { LocalizerType } from '../../../types/Util';
|
||||||
import { PanelRow } from './PanelRow';
|
import { PanelRow } from './PanelRow';
|
||||||
import { PanelSection } from './PanelSection';
|
import { PanelSection } from './PanelSection';
|
||||||
import { AccessControlClass } from '../../../textsecure.d';
|
|
||||||
import { Select } from '../../Select';
|
import { Select } from '../../Select';
|
||||||
|
|
||||||
|
const AccessControlEnum = Proto.AccessControl.AccessRequired;
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
accessEnum: typeof AccessControlClass.AccessRequired;
|
|
||||||
changeHasGroupLink: (value: boolean) => void;
|
changeHasGroupLink: (value: boolean) => void;
|
||||||
conversation?: ConversationType;
|
conversation?: ConversationType;
|
||||||
copyGroupLink: (groupLink: string) => void;
|
copyGroupLink: (groupLink: string) => void;
|
||||||
|
@ -23,7 +24,6 @@ export type PropsType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupLinkManagement: React.ComponentType<PropsType> = ({
|
export const GroupLinkManagement: React.ComponentType<PropsType> = ({
|
||||||
accessEnum,
|
|
||||||
changeHasGroupLink,
|
changeHasGroupLink,
|
||||||
conversation,
|
conversation,
|
||||||
copyGroupLink,
|
copyGroupLink,
|
||||||
|
@ -43,11 +43,13 @@ export const GroupLinkManagement: React.ComponentType<PropsType> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const membersNeedAdminApproval =
|
const membersNeedAdminApproval =
|
||||||
conversation.accessControlAddFromInviteLink === accessEnum.ADMINISTRATOR;
|
conversation.accessControlAddFromInviteLink ===
|
||||||
|
AccessControlEnum.ADMINISTRATOR;
|
||||||
|
|
||||||
const hasGroupLink =
|
const hasGroupLink =
|
||||||
conversation.groupLink &&
|
conversation.groupLink &&
|
||||||
conversation.accessControlAddFromInviteLink !== accessEnum.UNSATISFIABLE;
|
conversation.accessControlAddFromInviteLink !==
|
||||||
|
AccessControlEnum.UNSATISFIABLE;
|
||||||
const groupLinkInfo = hasGroupLink ? conversation.groupLink : '';
|
const groupLinkInfo = hasGroupLink ? conversation.groupLink : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -29,20 +29,7 @@ const conversation: ConversationType = getDefaultConversation({
|
||||||
sharedGroupNames: [],
|
sharedGroupNames: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
class AccessEnum {
|
|
||||||
static ANY = 0;
|
|
||||||
|
|
||||||
static UNKNOWN = 1;
|
|
||||||
|
|
||||||
static MEMBER = 2;
|
|
||||||
|
|
||||||
static ADMINISTRATOR = 3;
|
|
||||||
|
|
||||||
static UNSATISFIABLE = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
const createProps = (): PropsType => ({
|
const createProps = (): PropsType => ({
|
||||||
accessEnum: AccessEnum,
|
|
||||||
conversation,
|
conversation,
|
||||||
i18n,
|
i18n,
|
||||||
setAccessControlAttributesSetting: action(
|
setAccessControlAttributesSetting: action(
|
||||||
|
|
|
@ -6,14 +6,12 @@ import React from 'react';
|
||||||
import { ConversationType } from '../../../state/ducks/conversations';
|
import { ConversationType } from '../../../state/ducks/conversations';
|
||||||
import { LocalizerType } from '../../../types/Util';
|
import { LocalizerType } from '../../../types/Util';
|
||||||
import { getAccessControlOptions } from '../../../util/getAccessControlOptions';
|
import { getAccessControlOptions } from '../../../util/getAccessControlOptions';
|
||||||
import { AccessControlClass } from '../../../textsecure.d';
|
|
||||||
|
|
||||||
import { PanelRow } from './PanelRow';
|
import { PanelRow } from './PanelRow';
|
||||||
import { PanelSection } from './PanelSection';
|
import { PanelSection } from './PanelSection';
|
||||||
import { Select } from '../../Select';
|
import { Select } from '../../Select';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
accessEnum: typeof AccessControlClass.AccessRequired;
|
|
||||||
conversation?: ConversationType;
|
conversation?: ConversationType;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
setAccessControlAttributesSetting: (value: number) => void;
|
setAccessControlAttributesSetting: (value: number) => void;
|
||||||
|
@ -21,7 +19,6 @@ export type PropsType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupV2Permissions: React.ComponentType<PropsType> = ({
|
export const GroupV2Permissions: React.ComponentType<PropsType> = ({
|
||||||
accessEnum,
|
|
||||||
conversation,
|
conversation,
|
||||||
i18n,
|
i18n,
|
||||||
setAccessControlAttributesSetting,
|
setAccessControlAttributesSetting,
|
||||||
|
@ -37,7 +34,7 @@ export const GroupV2Permissions: React.ComponentType<PropsType> = ({
|
||||||
const updateAccessControlMembers = (value: string) => {
|
const updateAccessControlMembers = (value: string) => {
|
||||||
setAccessControlMembersSetting(Number(value));
|
setAccessControlMembersSetting(Number(value));
|
||||||
};
|
};
|
||||||
const accessControlOptions = getAccessControlOptions(accessEnum, i18n);
|
const accessControlOptions = getAccessControlOptions(i18n);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelSection>
|
<PanelSection>
|
||||||
|
|
|
@ -6,8 +6,8 @@ import { LocalizerType } from './types/Util';
|
||||||
import { ReplacementValuesType } from './types/I18N';
|
import { ReplacementValuesType } from './types/I18N';
|
||||||
import { missingCaseError } from './util/missingCaseError';
|
import { missingCaseError } from './util/missingCaseError';
|
||||||
|
|
||||||
import { AccessControlClass, MemberClass } from './textsecure.d';
|
|
||||||
import { GroupV2ChangeDetailType, GroupV2ChangeType } from './groups';
|
import { GroupV2ChangeDetailType, GroupV2ChangeType } from './groups';
|
||||||
|
import { SignalService as Proto } from './protobuf';
|
||||||
|
|
||||||
export type SmartContactRendererType = (conversationId: string) => FullJSXType;
|
export type SmartContactRendererType = (conversationId: string) => FullJSXType;
|
||||||
export type StringRendererType = (
|
export type StringRendererType = (
|
||||||
|
@ -17,15 +17,16 @@ export type StringRendererType = (
|
||||||
) => FullJSXType;
|
) => FullJSXType;
|
||||||
|
|
||||||
export type RenderOptionsType = {
|
export type RenderOptionsType = {
|
||||||
AccessControlEnum: typeof AccessControlClass.AccessRequired;
|
|
||||||
from?: string;
|
from?: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
ourConversationId: string;
|
ourConversationId: string;
|
||||||
renderContact: SmartContactRendererType;
|
renderContact: SmartContactRendererType;
|
||||||
renderString: StringRendererType;
|
renderString: StringRendererType;
|
||||||
RoleEnum: typeof MemberClass.Role;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AccessControlEnum = Proto.AccessControl.AccessRequired;
|
||||||
|
const RoleEnum = Proto.Member.Role;
|
||||||
|
|
||||||
export function renderChange(
|
export function renderChange(
|
||||||
change: GroupV2ChangeType,
|
change: GroupV2ChangeType,
|
||||||
options: RenderOptionsType
|
options: RenderOptionsType
|
||||||
|
@ -45,13 +46,11 @@ export function renderChangeDetail(
|
||||||
options: RenderOptionsType
|
options: RenderOptionsType
|
||||||
): FullJSXType {
|
): FullJSXType {
|
||||||
const {
|
const {
|
||||||
AccessControlEnum,
|
|
||||||
from,
|
from,
|
||||||
i18n,
|
i18n,
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
renderContact,
|
renderContact,
|
||||||
renderString,
|
renderString,
|
||||||
RoleEnum,
|
|
||||||
} = options;
|
} = options;
|
||||||
const fromYou = Boolean(from && from === ourConversationId);
|
const fromYou = Boolean(from && from === ourConversationId);
|
||||||
|
|
||||||
|
|
|
@ -1370,6 +1370,13 @@ export function idForLogging(groupId: string | undefined): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deriveGroupFields(masterKey: Uint8Array): GroupFields {
|
export function deriveGroupFields(masterKey: Uint8Array): GroupFields {
|
||||||
|
if (masterKey.length !== MASTER_KEY_LENGTH) {
|
||||||
|
throw new Error(
|
||||||
|
`deriveGroupFields: masterKey had length ${masterKey.length}, ` +
|
||||||
|
`expected ${MASTER_KEY_LENGTH}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const cacheKey = Bytes.toBase64(masterKey);
|
const cacheKey = Bytes.toBase64(masterKey);
|
||||||
const cached = groupFieldsCache.get(cacheKey);
|
const cached = groupFieldsCache.get(cacheKey);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
|
|
4
ts/model-types.d.ts
vendored
4
ts/model-types.d.ts
vendored
|
@ -58,7 +58,7 @@ export type QuotedMessageType = {
|
||||||
author?: string;
|
author?: string;
|
||||||
authorUuid?: string;
|
authorUuid?: string;
|
||||||
bodyRanges?: BodyRangesType;
|
bodyRanges?: BodyRangesType;
|
||||||
id: string;
|
id: number;
|
||||||
referencedMessageNotFound: boolean;
|
referencedMessageNotFound: boolean;
|
||||||
isViewOnce: boolean;
|
isViewOnce: boolean;
|
||||||
text?: string;
|
text?: string;
|
||||||
|
@ -190,6 +190,8 @@ export type MessageAttributesType = {
|
||||||
// Backwards-compatibility with prerelease data schema
|
// Backwards-compatibility with prerelease data schema
|
||||||
invitedGV2Members?: Array<GroupV2PendingMemberType>;
|
invitedGV2Members?: Array<GroupV2PendingMemberType>;
|
||||||
droppedGV2MemberIds?: Array<string>;
|
droppedGV2MemberIds?: Array<string>;
|
||||||
|
|
||||||
|
sendHQImages?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConversationAttributesTypeType = 'private' | 'group';
|
export type ConversationAttributesTypeType = 'private' | 'group';
|
||||||
|
|
|
@ -14,7 +14,9 @@ import {
|
||||||
VerificationOptions,
|
VerificationOptions,
|
||||||
WhatIsThis,
|
WhatIsThis,
|
||||||
} from '../model-types.d';
|
} from '../model-types.d';
|
||||||
|
import { AttachmentType } from '../types/Attachment';
|
||||||
import { CallMode, CallHistoryDetailsType } from '../types/Calling';
|
import { CallMode, CallHistoryDetailsType } from '../types/Calling';
|
||||||
|
import * as Stickers from '../types/Stickers';
|
||||||
import { CallbackResultType, GroupV2InfoType } from '../textsecure/SendMessage';
|
import { CallbackResultType, GroupV2InfoType } from '../textsecure/SendMessage';
|
||||||
import { ConversationType } from '../state/ducks/conversations';
|
import { ConversationType } from '../state/ducks/conversations';
|
||||||
import {
|
import {
|
||||||
|
@ -3100,7 +3102,7 @@ export class ConversationModel extends window.Backbone
|
||||||
? [{ contentType: 'image/jpeg', fileName: null }]
|
? [{ contentType: 'image/jpeg', fileName: null }]
|
||||||
: await this.getQuoteAttachment(attachments, preview, sticker),
|
: await this.getQuoteAttachment(attachments, preview, sticker),
|
||||||
bodyRanges: quotedMessage.get('bodyRanges'),
|
bodyRanges: quotedMessage.get('bodyRanges'),
|
||||||
id: String(quotedMessage.get('sent_at')),
|
id: quotedMessage.get('sent_at'),
|
||||||
isViewOnce: isTapToView(quotedMessage.attributes),
|
isViewOnce: isTapToView(quotedMessage.attributes),
|
||||||
messageId: quotedMessage.get('id'),
|
messageId: quotedMessage.get('id'),
|
||||||
referencedMessageNotFound: false,
|
referencedMessageNotFound: false,
|
||||||
|
@ -3109,8 +3111,8 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendStickerMessage(packId: string, stickerId: number): Promise<void> {
|
async sendStickerMessage(packId: string, stickerId: number): Promise<void> {
|
||||||
const packData = window.Signal.Stickers.getStickerPack(packId);
|
const packData = Stickers.getStickerPack(packId);
|
||||||
const stickerData = window.Signal.Stickers.getSticker(packId, stickerId);
|
const stickerData = Stickers.getSticker(packId, stickerId);
|
||||||
if (!stickerData || !packData) {
|
if (!stickerData || !packData) {
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
`Attempted to send nonexistent (${packId}, ${stickerId}) sticker!`
|
`Attempted to send nonexistent (${packId}, ${stickerId}) sticker!`
|
||||||
|
@ -3152,7 +3154,7 @@ export class ConversationModel extends window.Backbone
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.sendMessage(null, [], null, [], sticker);
|
this.sendMessage(undefined, [], undefined, [], sticker);
|
||||||
window.reduxActions.stickers.useSticker(packId, stickerId);
|
window.reduxActions.stickers.useSticker(packId, stickerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3451,10 +3453,10 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(
|
sendMessage(
|
||||||
body: string | null,
|
body: string | undefined,
|
||||||
attachments: Array<WhatIsThis>,
|
attachments: Array<AttachmentType>,
|
||||||
quote: WhatIsThis,
|
quote?: QuotedMessageType,
|
||||||
preview: WhatIsThis,
|
preview?: WhatIsThis,
|
||||||
sticker?: WhatIsThis,
|
sticker?: WhatIsThis,
|
||||||
mentions?: BodyRangesType,
|
mentions?: BodyRangesType,
|
||||||
{
|
{
|
||||||
|
@ -3503,6 +3505,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
// Here we move attachments to disk
|
// Here we move attachments to disk
|
||||||
const messageWithSchema = await upgradeMessageSchema({
|
const messageWithSchema = await upgradeMessageSchema({
|
||||||
|
timestamp: now,
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
body,
|
body,
|
||||||
conversationId: this.id,
|
conversationId: this.id,
|
||||||
|
@ -3575,7 +3578,7 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachmentsWithData = await Promise.all(
|
const attachmentsWithData = await Promise.all(
|
||||||
messageWithSchema.attachments.map(loadAttachmentData)
|
messageWithSchema.attachments?.map(loadAttachmentData) ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -5086,7 +5089,7 @@ export class ConversationModel extends window.Backbone
|
||||||
isTyping: boolean;
|
isTyping: boolean;
|
||||||
senderId: string;
|
senderId: string;
|
||||||
fromMe: boolean;
|
fromMe: boolean;
|
||||||
senderDevice: string;
|
senderDevice: number;
|
||||||
}): void {
|
}): void {
|
||||||
const { isTyping, senderId, fromMe, senderDevice } = options;
|
const { isTyping, senderId, fromMe, senderDevice } = options;
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,10 @@ import {
|
||||||
QuotedMessageType,
|
QuotedMessageType,
|
||||||
WhatIsThis,
|
WhatIsThis,
|
||||||
} from '../model-types.d';
|
} from '../model-types.d';
|
||||||
|
import { strictAssert } from '../util/assert';
|
||||||
|
import { dropNull } from '../util/dropNull';
|
||||||
import { map, filter, find } from '../util/iterables';
|
import { map, filter, find } from '../util/iterables';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
import { DataMessageClass } from '../textsecure.d';
|
|
||||||
import { ConversationModel } from './conversations';
|
import { ConversationModel } from './conversations';
|
||||||
import { MessageStatusType } from '../components/conversation/Message';
|
import { MessageStatusType } from '../components/conversation/Message';
|
||||||
import {
|
import {
|
||||||
|
@ -23,9 +24,17 @@ import {
|
||||||
} from '../state/smart/MessageDetail';
|
} from '../state/smart/MessageDetail';
|
||||||
import { getCallingNotificationText } from '../util/callingNotification';
|
import { getCallingNotificationText } from '../util/callingNotification';
|
||||||
import { CallbackResultType } from '../textsecure/SendMessage';
|
import { CallbackResultType } from '../textsecure/SendMessage';
|
||||||
|
import { ProcessedDataMessage, ProcessedQuote } from '../textsecure/Types.d';
|
||||||
import * as expirationTimer from '../util/expirationTimer';
|
import * as expirationTimer from '../util/expirationTimer';
|
||||||
|
|
||||||
import { ReactionType } from '../types/Reactions';
|
import { ReactionType } from '../types/Reactions';
|
||||||
|
import {
|
||||||
|
copyStickerToAttachments,
|
||||||
|
deletePackReference,
|
||||||
|
savePackMetadata,
|
||||||
|
getStickerPackStatus,
|
||||||
|
} from '../types/Stickers';
|
||||||
|
import * as Stickers from '../types/Stickers';
|
||||||
import { AttachmentType, isImage, isVideo } from '../types/Attachment';
|
import { AttachmentType, isImage, isVideo } from '../types/Attachment';
|
||||||
import { MIMEType, IMAGE_WEBP } from '../types/MIME';
|
import { MIMEType, IMAGE_WEBP } from '../types/MIME';
|
||||||
import { ourProfileKeyService } from '../services/ourProfileKey';
|
import { ourProfileKeyService } from '../services/ourProfileKey';
|
||||||
|
@ -107,12 +116,6 @@ const {
|
||||||
loadStickerData,
|
loadStickerData,
|
||||||
upgradeMessageSchema,
|
upgradeMessageSchema,
|
||||||
} = window.Signal.Migrations;
|
} = window.Signal.Migrations;
|
||||||
const {
|
|
||||||
copyStickerToAttachments,
|
|
||||||
deletePackReference,
|
|
||||||
savePackMetadata,
|
|
||||||
getStickerPackStatus,
|
|
||||||
} = window.Signal.Stickers;
|
|
||||||
const { getTextWithMentions, GoogleChrome } = window.Signal.Util;
|
const { getTextWithMentions, GoogleChrome } = window.Signal.Util;
|
||||||
|
|
||||||
const { addStickerPackReference, getMessageBySender } = window.Signal.Data;
|
const { addStickerPackReference, getMessageBySender } = window.Signal.Data;
|
||||||
|
@ -124,7 +127,7 @@ const includesAny = <T>(haystack: Array<T>, ...needles: Array<T>) =>
|
||||||
export function isQuoteAMatch(
|
export function isQuoteAMatch(
|
||||||
message: MessageModel | null | undefined,
|
message: MessageModel | null | undefined,
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
quote: QuotedMessageType | DataMessageClass.Quote
|
quote: QuotedMessageType
|
||||||
): message is MessageModel {
|
): message is MessageModel {
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -614,7 +617,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
|
|
||||||
const stickerData = this.get('sticker');
|
const stickerData = this.get('sticker');
|
||||||
if (stickerData) {
|
if (stickerData) {
|
||||||
const sticker = window.Signal.Stickers.getSticker(
|
const sticker = Stickers.getSticker(
|
||||||
stickerData.packId,
|
stickerData.packId,
|
||||||
stickerData.stickerId
|
stickerData.stickerId
|
||||||
);
|
);
|
||||||
|
@ -624,7 +627,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
text: window.i18n('message--getNotificationText--stickers'),
|
text: window.i18n('message--getNotificationText--stickers'),
|
||||||
emoji,
|
emoji: dropNull(emoji),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1460,11 +1463,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
senderKeyInfo.distributionId
|
senderKeyInfo.distributionId
|
||||||
);
|
);
|
||||||
|
|
||||||
contentMessage.senderKeyDistributionMessage = window.dcodeIO.ByteBuffer.wrap(
|
contentMessage.senderKeyDistributionMessage = senderKeyDistributionMessage.serialize();
|
||||||
window.Signal.Crypto.typedArrayToArrayBuffer(
|
|
||||||
senderKeyDistributionMessage.serialize()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2217,18 +2216,48 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
async copyFromQuotedMessage(
|
async copyFromQuotedMessage(
|
||||||
message: DataMessageClass,
|
quote: ProcessedQuote | undefined,
|
||||||
conversationId: string
|
conversationId: string
|
||||||
): Promise<DataMessageClass> {
|
): Promise<QuotedMessageType | undefined> {
|
||||||
const { quote } = message;
|
|
||||||
if (!quote) {
|
if (!quote) {
|
||||||
return message;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = quote;
|
const { id } = quote;
|
||||||
|
strictAssert(id, 'Quote must have an id');
|
||||||
|
|
||||||
|
const result: QuotedMessageType = {
|
||||||
|
...quote,
|
||||||
|
|
||||||
|
id,
|
||||||
|
|
||||||
|
attachments: quote.attachments.slice(),
|
||||||
|
bodyRanges: quote.bodyRanges.map(({ start, length, mentionUuid }) => {
|
||||||
|
strictAssert(
|
||||||
|
start !== undefined && start !== null,
|
||||||
|
'Received quote with a bodyRange.start == null'
|
||||||
|
);
|
||||||
|
strictAssert(
|
||||||
|
length !== undefined && length !== null,
|
||||||
|
'Received quote with a bodyRange.length == null'
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
length,
|
||||||
|
mentionUuid: dropNull(mentionUuid),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Just placeholder values for the fields
|
||||||
|
referencedMessageNotFound: false,
|
||||||
|
isViewOnce: false,
|
||||||
|
messageId: '',
|
||||||
|
};
|
||||||
|
|
||||||
const inMemoryMessages = window.MessageController.filterBySentAt(id);
|
const inMemoryMessages = window.MessageController.filterBySentAt(id);
|
||||||
const matchingMessage = find(inMemoryMessages, item =>
|
const matchingMessage = find(inMemoryMessages, item =>
|
||||||
isQuoteAMatch(item, conversationId, quote)
|
isQuoteAMatch(item, conversationId, result)
|
||||||
);
|
);
|
||||||
|
|
||||||
let queryMessage: undefined | MessageModel;
|
let queryMessage: undefined | MessageModel;
|
||||||
|
@ -2241,35 +2270,35 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
MessageCollection: window.Whisper.MessageCollection,
|
MessageCollection: window.Whisper.MessageCollection,
|
||||||
});
|
});
|
||||||
const found = collection.find(item =>
|
const found = collection.find(item =>
|
||||||
isQuoteAMatch(item, conversationId, quote)
|
isQuoteAMatch(item, conversationId, result)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
quote.referencedMessageNotFound = true;
|
result.referencedMessageNotFound = true;
|
||||||
return message;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
queryMessage = window.MessageController.register(found.id, found);
|
queryMessage = window.MessageController.register(found.id, found);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queryMessage) {
|
if (queryMessage) {
|
||||||
await this.copyQuoteContentFromOriginal(queryMessage, quote);
|
await this.copyQuoteContentFromOriginal(queryMessage, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
async copyQuoteContentFromOriginal(
|
async copyQuoteContentFromOriginal(
|
||||||
originalMessage: MessageModel,
|
originalMessage: MessageModel,
|
||||||
quote: QuotedMessageType | DataMessageClass.Quote
|
quote: QuotedMessageType
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { attachments } = quote;
|
const { attachments } = quote;
|
||||||
const firstAttachment = attachments ? attachments[0] : undefined;
|
const firstAttachment = attachments ? attachments[0] : undefined;
|
||||||
|
|
||||||
if (isTapToView(originalMessage.attributes)) {
|
if (isTapToView(originalMessage.attributes)) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
quote.text = null;
|
quote.text = undefined;
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
quote.attachments = [
|
quote.attachments = [
|
||||||
{
|
{
|
||||||
|
@ -2362,7 +2391,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDataMessage(
|
handleDataMessage(
|
||||||
initialMessage: DataMessageClass,
|
initialMessage: ProcessedDataMessage,
|
||||||
confirm: () => void,
|
confirm: () => void,
|
||||||
options: { data?: typeof window.WhatIsThis } = {}
|
options: { data?: typeof window.WhatIsThis } = {}
|
||||||
): WhatIsThis {
|
): WhatIsThis {
|
||||||
|
@ -2631,16 +2660,19 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const withQuoteReference = await this.copyFromQuotedMessage(
|
const withQuoteReference = {
|
||||||
initialMessage,
|
...initialMessage,
|
||||||
conversation.id
|
quote: await this.copyFromQuotedMessage(
|
||||||
);
|
initialMessage.quote,
|
||||||
|
conversation.id
|
||||||
|
),
|
||||||
|
};
|
||||||
const dataMessage = await upgradeMessageSchema(withQuoteReference);
|
const dataMessage = await upgradeMessageSchema(withQuoteReference);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
|
|
||||||
const urls = LinkPreview.findLinks(dataMessage.body);
|
const urls = LinkPreview.findLinks(dataMessage.body || '');
|
||||||
const incomingPreview = dataMessage.preview || [];
|
const incomingPreview = dataMessage.preview || [];
|
||||||
const preview = incomingPreview.filter(
|
const preview = incomingPreview.filter(
|
||||||
(item: typeof window.WhatIsThis) =>
|
(item: typeof window.WhatIsThis) =>
|
||||||
|
|
|
@ -22,7 +22,6 @@ import {
|
||||||
GumVideoCapturer,
|
GumVideoCapturer,
|
||||||
HangupMessage,
|
HangupMessage,
|
||||||
HangupType,
|
HangupType,
|
||||||
OfferType,
|
|
||||||
OpaqueMessage,
|
OpaqueMessage,
|
||||||
PeekInfo,
|
PeekInfo,
|
||||||
RingRTC,
|
RingRTC,
|
||||||
|
@ -38,7 +37,6 @@ import {
|
||||||
GroupCallPeekInfoType,
|
GroupCallPeekInfoType,
|
||||||
} from '../state/ducks/calling';
|
} from '../state/ducks/calling';
|
||||||
import { getConversationCallMode } from '../state/ducks/conversations';
|
import { getConversationCallMode } from '../state/ducks/conversations';
|
||||||
import { EnvelopeClass } from '../textsecure.d';
|
|
||||||
import {
|
import {
|
||||||
CallMode,
|
CallMode,
|
||||||
AudioDevice,
|
AudioDevice,
|
||||||
|
@ -57,12 +55,14 @@ import {
|
||||||
typedArrayToArrayBuffer,
|
typedArrayToArrayBuffer,
|
||||||
} from '../Crypto';
|
} from '../Crypto';
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
|
import { dropNull, shallowDropNull } from '../util/dropNull';
|
||||||
import { getOwn } from '../util/getOwn';
|
import { getOwn } from '../util/getOwn';
|
||||||
import {
|
import {
|
||||||
fetchMembershipProof,
|
fetchMembershipProof,
|
||||||
getMembershipList,
|
getMembershipList,
|
||||||
wrapWithSyncMessageSend,
|
wrapWithSyncMessageSend,
|
||||||
} from '../groups';
|
} from '../groups';
|
||||||
|
import { ProcessedEnvelope } from '../textsecure/Types.d';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
import { normalizeGroupCallTimestamp } from '../util/ringrtc/normalizeGroupCallTimestamp';
|
import { normalizeGroupCallTimestamp } from '../util/ringrtc/normalizeGroupCallTimestamp';
|
||||||
import {
|
import {
|
||||||
|
@ -74,6 +74,9 @@ import { notify } from './notify';
|
||||||
import { getSendOptions } from '../util/getSendOptions';
|
import { getSendOptions } from '../util/getSendOptions';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
|
// TODO: remove once we move away from ArrayBuffers
|
||||||
|
const FIXMEU8 = Uint8Array;
|
||||||
|
|
||||||
const RINGRTC_HTTP_METHOD_TO_OUR_HTTP_METHOD: Map<
|
const RINGRTC_HTTP_METHOD_TO_OUR_HTTP_METHOD: Map<
|
||||||
HttpMethod,
|
HttpMethod,
|
||||||
'GET' | 'PUT' | 'POST' | 'DELETE'
|
'GET' | 'PUT' | 'POST' | 'DELETE'
|
||||||
|
@ -121,6 +124,135 @@ function translateSourceName(
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function protoToCallingMessage({
|
||||||
|
offer,
|
||||||
|
answer,
|
||||||
|
iceCandidates,
|
||||||
|
legacyHangup,
|
||||||
|
busy,
|
||||||
|
hangup,
|
||||||
|
supportsMultiRing,
|
||||||
|
destinationDeviceId,
|
||||||
|
opaque,
|
||||||
|
}: Proto.ICallingMessage): CallingMessage {
|
||||||
|
return {
|
||||||
|
offer: offer
|
||||||
|
? {
|
||||||
|
...shallowDropNull(offer),
|
||||||
|
|
||||||
|
type: dropNull(offer.type) as number,
|
||||||
|
opaque: offer.opaque ? Buffer.from(offer.opaque) : undefined,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
answer: answer
|
||||||
|
? {
|
||||||
|
...shallowDropNull(answer),
|
||||||
|
opaque: answer.opaque ? Buffer.from(answer.opaque) : undefined,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
iceCandidates: iceCandidates
|
||||||
|
? iceCandidates.map(candidate => {
|
||||||
|
return {
|
||||||
|
...shallowDropNull(candidate),
|
||||||
|
opaque: candidate.opaque
|
||||||
|
? Buffer.from(candidate.opaque)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
legacyHangup: legacyHangup
|
||||||
|
? {
|
||||||
|
...shallowDropNull(legacyHangup),
|
||||||
|
type: dropNull(legacyHangup.type) as number,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
busy: shallowDropNull(busy),
|
||||||
|
hangup: hangup
|
||||||
|
? {
|
||||||
|
...shallowDropNull(hangup),
|
||||||
|
type: dropNull(hangup.type) as number,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
supportsMultiRing: dropNull(supportsMultiRing),
|
||||||
|
destinationDeviceId: dropNull(destinationDeviceId),
|
||||||
|
opaque: opaque
|
||||||
|
? {
|
||||||
|
data: opaque.data ? Buffer.from(opaque.data) : undefined,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function bufferToProto(
|
||||||
|
value: Buffer | { toArrayBuffer(): ArrayBuffer } | undefined
|
||||||
|
): Uint8Array | undefined {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (value instanceof Uint8Array) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FIXMEU8(value.toArrayBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
function callingMessageToProto({
|
||||||
|
offer,
|
||||||
|
answer,
|
||||||
|
iceCandidates,
|
||||||
|
legacyHangup,
|
||||||
|
busy,
|
||||||
|
hangup,
|
||||||
|
supportsMultiRing,
|
||||||
|
destinationDeviceId,
|
||||||
|
opaque,
|
||||||
|
}: CallingMessage): Proto.ICallingMessage {
|
||||||
|
return {
|
||||||
|
offer: offer
|
||||||
|
? {
|
||||||
|
...offer,
|
||||||
|
type: offer.type as number,
|
||||||
|
opaque: bufferToProto(offer.opaque),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
answer: answer
|
||||||
|
? {
|
||||||
|
...answer,
|
||||||
|
opaque: bufferToProto(answer.opaque),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
iceCandidates: iceCandidates
|
||||||
|
? iceCandidates.map(candidate => {
|
||||||
|
return {
|
||||||
|
...candidate,
|
||||||
|
opaque: bufferToProto(candidate.opaque),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
legacyHangup: legacyHangup
|
||||||
|
? {
|
||||||
|
...legacyHangup,
|
||||||
|
type: legacyHangup.type as number,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
busy,
|
||||||
|
hangup: hangup
|
||||||
|
? {
|
||||||
|
...hangup,
|
||||||
|
type: hangup.type as number,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
supportsMultiRing,
|
||||||
|
destinationDeviceId,
|
||||||
|
opaque: opaque
|
||||||
|
? {
|
||||||
|
...opaque,
|
||||||
|
data: bufferToProto(opaque.data),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export class CallingClass {
|
export class CallingClass {
|
||||||
readonly videoCapturer: GumVideoCapturer;
|
readonly videoCapturer: GumVideoCapturer;
|
||||||
|
|
||||||
|
@ -1231,8 +1363,8 @@ export class CallingClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCallingMessage(
|
async handleCallingMessage(
|
||||||
envelope: EnvelopeClass,
|
envelope: ProcessedEnvelope,
|
||||||
callingMessage: CallingMessage
|
callingMessage: Proto.ICallingMessage
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
window.log.info('CallingClass.handleCallingMessage()');
|
window.log.info('CallingClass.handleCallingMessage()');
|
||||||
|
|
||||||
|
@ -1298,9 +1430,10 @@ export class CallingClass {
|
||||||
|
|
||||||
await this.handleOutgoingSignaling(remoteUserId, message);
|
await this.handleOutgoingSignaling(remoteUserId, message);
|
||||||
|
|
||||||
|
const ProtoOfferType = Proto.CallingMessage.Offer.Type;
|
||||||
this.addCallHistoryForFailedIncomingCall(
|
this.addCallHistoryForFailedIncomingCall(
|
||||||
conversation,
|
conversation,
|
||||||
callingMessage.offer.type === OfferType.VideoCall,
|
callingMessage.offer.type === ProtoOfferType.OFFER_VIDEO_CALL,
|
||||||
envelope.timestamp
|
envelope.timestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1321,7 +1454,7 @@ export class CallingClass {
|
||||||
remoteDeviceId,
|
remoteDeviceId,
|
||||||
this.localDeviceId,
|
this.localDeviceId,
|
||||||
messageAgeSec,
|
messageAgeSec,
|
||||||
callingMessage,
|
protoToCallingMessage(callingMessage),
|
||||||
Buffer.from(senderIdentityKey),
|
Buffer.from(senderIdentityKey),
|
||||||
Buffer.from(receiverIdentityKey)
|
Buffer.from(receiverIdentityKey)
|
||||||
);
|
);
|
||||||
|
@ -1428,7 +1561,7 @@ export class CallingClass {
|
||||||
try {
|
try {
|
||||||
await window.textsecure.messaging.sendCallingMessage(
|
await window.textsecure.messaging.sendCallingMessage(
|
||||||
remoteUserId,
|
remoteUserId,
|
||||||
message,
|
callingMessageToProto(message),
|
||||||
sendOptions
|
sendOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
waitThenRespondToGroupV2Migration,
|
waitThenRespondToGroupV2Migration,
|
||||||
} from '../groups';
|
} from '../groups';
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
|
import { normalizeUuid } from '../util/normalizeUuid';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
import {
|
import {
|
||||||
PhoneNumberSharingMode,
|
PhoneNumberSharingMode,
|
||||||
|
@ -719,13 +720,18 @@ export async function mergeGroupV2Record(
|
||||||
|
|
||||||
export async function mergeContactRecord(
|
export async function mergeContactRecord(
|
||||||
storageID: string,
|
storageID: string,
|
||||||
contactRecord: ContactRecordClass
|
originalContactRecord: ContactRecordClass
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
window.normalizeUuids(
|
const contactRecord = {
|
||||||
contactRecord,
|
...originalContactRecord,
|
||||||
['serviceUuid'],
|
|
||||||
'storageService.mergeContactRecord'
|
serviceUuid: originalContactRecord.serviceUuid
|
||||||
);
|
? normalizeUuid(
|
||||||
|
originalContactRecord.serviceUuid,
|
||||||
|
'ContactRecord.serviceUuid'
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
const e164 = contactRecord.serviceE164 || undefined;
|
const e164 = contactRecord.serviceE164 || undefined;
|
||||||
const uuid = contactRecord.serviceUuid || undefined;
|
const uuid = contactRecord.serviceUuid || undefined;
|
||||||
|
|
|
@ -4,19 +4,19 @@
|
||||||
/* eslint-disable @typescript-eslint/ban-types */
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import {
|
import type {
|
||||||
ConversationAttributesType,
|
ConversationAttributesType,
|
||||||
ConversationModelCollectionType,
|
ConversationModelCollectionType,
|
||||||
MessageAttributesType,
|
MessageAttributesType,
|
||||||
MessageModelCollectionType,
|
MessageModelCollectionType,
|
||||||
} from '../model-types.d';
|
} from '../model-types.d';
|
||||||
import { MessageModel } from '../models/messages';
|
import type { MessageModel } from '../models/messages';
|
||||||
import { ConversationModel } from '../models/conversations';
|
import type { ConversationModel } from '../models/conversations';
|
||||||
import { StoredJob } from '../jobs/types';
|
import type { StoredJob } from '../jobs/types';
|
||||||
import { ReactionType } from '../types/Reactions';
|
import type { ReactionType } from '../types/Reactions';
|
||||||
import { ConversationColorType, CustomColorType } from '../types/Colors';
|
import type { ConversationColorType, CustomColorType } from '../types/Colors';
|
||||||
import { StorageAccessType } from '../types/Storage.d';
|
import { StorageAccessType } from '../types/Storage.d';
|
||||||
import { AttachmentType } from '../types/Attachment';
|
import type { AttachmentType } from '../types/Attachment';
|
||||||
|
|
||||||
export type AttachmentDownloadJobTypeType =
|
export type AttachmentDownloadJobTypeType =
|
||||||
| 'long-message'
|
| 'long-message'
|
||||||
|
@ -111,41 +111,48 @@ export type SignedPreKeyType = {
|
||||||
privateKey: ArrayBuffer;
|
privateKey: ArrayBuffer;
|
||||||
publicKey: ArrayBuffer;
|
publicKey: ArrayBuffer;
|
||||||
};
|
};
|
||||||
export type StickerPackStatusType =
|
|
||||||
| 'known'
|
|
||||||
| 'ephemeral'
|
|
||||||
| 'downloaded'
|
|
||||||
| 'installed'
|
|
||||||
| 'pending'
|
|
||||||
| 'error';
|
|
||||||
|
|
||||||
export type StickerType = {
|
export type StickerType = Readonly<{
|
||||||
id: number;
|
id: number;
|
||||||
packId: string;
|
packId: string;
|
||||||
|
|
||||||
emoji: string | null;
|
emoji?: string;
|
||||||
isCoverOnly: boolean;
|
isCoverOnly: boolean;
|
||||||
lastUsed?: number;
|
lastUsed?: number;
|
||||||
path: string;
|
path: string;
|
||||||
|
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
};
|
}>;
|
||||||
export type StickerPackType = {
|
|
||||||
|
export const StickerPackStatuses = [
|
||||||
|
'known',
|
||||||
|
'ephemeral',
|
||||||
|
'downloaded',
|
||||||
|
'installed',
|
||||||
|
'pending',
|
||||||
|
'error',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type StickerPackStatusType = typeof StickerPackStatuses[number];
|
||||||
|
|
||||||
|
export type StickerPackType = Readonly<{
|
||||||
id: string;
|
id: string;
|
||||||
key: string;
|
key: string;
|
||||||
|
|
||||||
attemptedStatus: 'downloaded' | 'installed' | 'ephemeral';
|
attemptedStatus?: 'downloaded' | 'installed' | 'ephemeral';
|
||||||
author: string;
|
author: string;
|
||||||
coverStickerId: number;
|
coverStickerId: number;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
downloadAttempts: number;
|
downloadAttempts: number;
|
||||||
installedAt: number | null;
|
installedAt?: number;
|
||||||
lastUsed: number;
|
lastUsed?: number;
|
||||||
status: StickerPackStatusType;
|
status: StickerPackStatusType;
|
||||||
stickerCount: number;
|
stickerCount: number;
|
||||||
stickers: ReadonlyArray<string>;
|
stickers: Record<string, StickerType>;
|
||||||
title: string;
|
title: string;
|
||||||
};
|
}>;
|
||||||
|
|
||||||
export type UnprocessedType = {
|
export type UnprocessedType = {
|
||||||
id: string;
|
id: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { ReactionType } from '../types/Reactions';
|
||||||
import { StoredJob } from '../jobs/types';
|
import { StoredJob } from '../jobs/types';
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
import { combineNames } from '../util/combineNames';
|
import { combineNames } from '../util/combineNames';
|
||||||
|
import { dropNull } from '../util/dropNull';
|
||||||
import { isNormalNumber } from '../util/isNormalNumber';
|
import { isNormalNumber } from '../util/isNormalNumber';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
import { ConversationColorType, CustomColorType } from '../types/Colors';
|
import { ConversationColorType, CustomColorType } from '../types/Colors';
|
||||||
|
@ -301,6 +302,7 @@ function rowToSticker(row: StickerRow): StickerType {
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
isCoverOnly: Boolean(row.isCoverOnly),
|
isCoverOnly: Boolean(row.isCoverOnly),
|
||||||
|
emoji: dropNull(row.emoji),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4416,13 +4418,13 @@ async function createOrUpdateStickerPack(pack: StickerPackType): Promise<void> {
|
||||||
)
|
)
|
||||||
.all({ id });
|
.all({ id });
|
||||||
const payload = {
|
const payload = {
|
||||||
attemptedStatus,
|
attemptedStatus: attemptedStatus ?? null,
|
||||||
author,
|
author,
|
||||||
coverStickerId,
|
coverStickerId,
|
||||||
createdAt: createdAt || Date.now(),
|
createdAt: createdAt || Date.now(),
|
||||||
downloadAttempts: downloadAttempts || 1,
|
downloadAttempts: downloadAttempts || 1,
|
||||||
id,
|
id,
|
||||||
installedAt,
|
installedAt: installedAt ?? null,
|
||||||
key,
|
key,
|
||||||
lastUsed: lastUsed || null,
|
lastUsed: lastUsed || null,
|
||||||
status,
|
status,
|
||||||
|
@ -4563,7 +4565,7 @@ async function createOrUpdateSticker(sticker: StickerType): Promise<void> {
|
||||||
)
|
)
|
||||||
`
|
`
|
||||||
).run({
|
).run({
|
||||||
emoji,
|
emoji: emoji ?? null,
|
||||||
height,
|
height,
|
||||||
id,
|
id,
|
||||||
isCoverOnly: isCoverOnly ? 1 : 0,
|
isCoverOnly: isCoverOnly ? 1 : 0,
|
||||||
|
|
|
@ -2,11 +2,17 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { Dictionary, omit, reject } from 'lodash';
|
import { Dictionary, omit, reject } from 'lodash';
|
||||||
|
import type {
|
||||||
|
StickerPackStatusType,
|
||||||
|
StickerType as StickerDBType,
|
||||||
|
StickerPackType as StickerPackDBType,
|
||||||
|
} from '../../sql/Interface';
|
||||||
import dataInterface from '../../sql/Client';
|
import dataInterface from '../../sql/Client';
|
||||||
import {
|
import {
|
||||||
downloadStickerPack as externalDownloadStickerPack,
|
downloadStickerPack as externalDownloadStickerPack,
|
||||||
maybeDeletePack,
|
maybeDeletePack,
|
||||||
} from '../../../js/modules/stickers';
|
RecentStickerType,
|
||||||
|
} from '../../types/Stickers';
|
||||||
import { sendStickerPackSync } from '../../shims/textsecure';
|
import { sendStickerPackSync } from '../../shims/textsecure';
|
||||||
import { trigger } from '../../shims/events';
|
import { trigger } from '../../shims/events';
|
||||||
|
|
||||||
|
@ -20,49 +26,6 @@ const {
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
export type StickerDBType = {
|
|
||||||
readonly id: number;
|
|
||||||
readonly packId: string;
|
|
||||||
|
|
||||||
readonly emoji: string | null;
|
|
||||||
readonly isCoverOnly: boolean;
|
|
||||||
readonly lastUsed: number;
|
|
||||||
readonly path: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StickerPackStatuses = [
|
|
||||||
'known',
|
|
||||||
'ephemeral',
|
|
||||||
'downloaded',
|
|
||||||
'installed',
|
|
||||||
'pending',
|
|
||||||
'error',
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export type StickerPackStatus = typeof StickerPackStatuses[number];
|
|
||||||
|
|
||||||
export type StickerPackDBType = {
|
|
||||||
readonly id: string;
|
|
||||||
readonly key: string;
|
|
||||||
|
|
||||||
readonly attemptedStatus: 'downloaded' | 'installed' | 'ephemeral';
|
|
||||||
readonly author: string;
|
|
||||||
readonly coverStickerId: number;
|
|
||||||
readonly createdAt: number;
|
|
||||||
readonly downloadAttempts: number;
|
|
||||||
readonly installedAt: number | null;
|
|
||||||
readonly lastUsed: number;
|
|
||||||
readonly status: StickerPackStatus;
|
|
||||||
readonly stickerCount: number;
|
|
||||||
readonly stickers: Dictionary<StickerDBType>;
|
|
||||||
readonly title: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RecentStickerType = {
|
|
||||||
readonly stickerId: number;
|
|
||||||
readonly packId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type StickersStateType = {
|
export type StickersStateType = {
|
||||||
readonly installedPack: string | null;
|
readonly installedPack: string | null;
|
||||||
readonly packs: Dictionary<StickerPackDBType>;
|
readonly packs: Dictionary<StickerPackDBType>;
|
||||||
|
@ -75,23 +38,23 @@ export type StickersStateType = {
|
||||||
export type StickerType = {
|
export type StickerType = {
|
||||||
readonly id: number;
|
readonly id: number;
|
||||||
readonly packId: string;
|
readonly packId: string;
|
||||||
readonly emoji: string | null;
|
readonly emoji?: string;
|
||||||
readonly url: string;
|
readonly url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StickerPackType = {
|
export type StickerPackType = Readonly<{
|
||||||
readonly id: string;
|
id: string;
|
||||||
readonly key: string;
|
key: string;
|
||||||
readonly title: string;
|
title: string;
|
||||||
readonly author: string;
|
author: string;
|
||||||
readonly isBlessed: boolean;
|
isBlessed: boolean;
|
||||||
readonly cover?: StickerType;
|
cover?: StickerType;
|
||||||
readonly lastUsed: number;
|
lastUsed?: number;
|
||||||
readonly attemptedStatus?: 'downloaded' | 'installed' | 'ephemeral';
|
attemptedStatus?: 'downloaded' | 'installed' | 'ephemeral';
|
||||||
readonly status: StickerPackStatus;
|
status: StickerPackStatusType;
|
||||||
readonly stickers: Array<StickerType>;
|
stickers: Array<StickerType>;
|
||||||
readonly stickerCount: number;
|
stickerCount: number;
|
||||||
};
|
}>;
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
|
@ -128,7 +91,7 @@ type UninstallStickerPackPayloadType = {
|
||||||
packId: string;
|
packId: string;
|
||||||
fromSync: boolean;
|
fromSync: boolean;
|
||||||
status: 'downloaded';
|
status: 'downloaded';
|
||||||
installedAt: null;
|
installedAt?: undefined;
|
||||||
recentStickers: Array<RecentStickerType>;
|
recentStickers: Array<RecentStickerType>;
|
||||||
};
|
};
|
||||||
type UninstallStickerPackAction = {
|
type UninstallStickerPackAction = {
|
||||||
|
@ -306,7 +269,7 @@ async function doUninstallStickerPack(
|
||||||
packId,
|
packId,
|
||||||
fromSync,
|
fromSync,
|
||||||
status,
|
status,
|
||||||
installedAt: null,
|
installedAt: undefined,
|
||||||
recentStickers: recentStickers.map(item => ({
|
recentStickers: recentStickers.map(item => ({
|
||||||
packId: item.packId,
|
packId: item.packId,
|
||||||
stickerId: item.id,
|
stickerId: item.id,
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { BodyRangesType } from '../../types/Util';
|
||||||
import { LinkPreviewType } from '../../types/message/LinkPreviews';
|
import { LinkPreviewType } from '../../types/message/LinkPreviews';
|
||||||
import { ConversationColors } from '../../types/Colors';
|
import { ConversationColors } from '../../types/Colors';
|
||||||
import { CallMode } from '../../types/Calling';
|
import { CallMode } from '../../types/Calling';
|
||||||
|
import { SignalService as Proto } from '../../protobuf';
|
||||||
import { AttachmentType, isVoiceMessage } from '../../types/Attachment';
|
import { AttachmentType, isVoiceMessage } from '../../types/Attachment';
|
||||||
|
|
||||||
import { CallingNotificationType } from '../../util/callingNotification';
|
import { CallingNotificationType } from '../../util/callingNotification';
|
||||||
|
@ -430,8 +431,7 @@ function getPropsForUnsupportedMessage(
|
||||||
ourNumber: string | undefined,
|
ourNumber: string | undefined,
|
||||||
ourUuid: string | undefined
|
ourUuid: string | undefined
|
||||||
): PropsForUnsupportedMessage {
|
): PropsForUnsupportedMessage {
|
||||||
const CURRENT_PROTOCOL_VERSION =
|
const CURRENT_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.CURRENT;
|
||||||
window.textsecure.protobuf.DataMessage.ProtocolVersion.CURRENT;
|
|
||||||
|
|
||||||
const requiredVersion = message.requiredProtocolVersion;
|
const requiredVersion = message.requiredProtocolVersion;
|
||||||
const canProcessNow = Boolean(
|
const canProcessNow = Boolean(
|
||||||
|
@ -463,9 +463,6 @@ function getPropsForGroupV2Change(
|
||||||
conversationSelector: GetConversationByIdType,
|
conversationSelector: GetConversationByIdType,
|
||||||
ourConversationId: string
|
ourConversationId: string
|
||||||
): GroupsV2Props {
|
): GroupsV2Props {
|
||||||
const AccessControlEnum =
|
|
||||||
window.textsecure.protobuf.AccessControl.AccessRequired;
|
|
||||||
const RoleEnum = window.textsecure.protobuf.Member.Role;
|
|
||||||
const change = message.groupV2Change;
|
const change = message.groupV2Change;
|
||||||
|
|
||||||
if (!change) {
|
if (!change) {
|
||||||
|
@ -476,8 +473,6 @@ function getPropsForGroupV2Change(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
groupName: conversation?.type === 'group' ? conversation?.name : undefined,
|
groupName: conversation?.type === 'group' ? conversation?.name : undefined,
|
||||||
AccessControlEnum,
|
|
||||||
RoleEnum,
|
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
change,
|
change,
|
||||||
};
|
};
|
||||||
|
@ -547,8 +542,7 @@ export function isMessageHistoryUnsynced(
|
||||||
export function isExpirationTimerUpdate(
|
export function isExpirationTimerUpdate(
|
||||||
message: Pick<MessageAttributesType, 'flags'>
|
message: Pick<MessageAttributesType, 'flags'>
|
||||||
): boolean {
|
): boolean {
|
||||||
const flag =
|
const flag = Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
|
||||||
window.textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
|
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
return Boolean(message.flags && message.flags & flag);
|
return Boolean(message.flags && message.flags & flag);
|
||||||
}
|
}
|
||||||
|
@ -734,7 +728,7 @@ function getPropsForGroupNotification(
|
||||||
export function isEndSession(
|
export function isEndSession(
|
||||||
message: Pick<MessageAttributesType, 'flags'>
|
message: Pick<MessageAttributesType, 'flags'>
|
||||||
): boolean {
|
): boolean {
|
||||||
const flag = window.textsecure.protobuf.DataMessage.Flags.END_SESSION;
|
const flag = Proto.DataMessage.Flags.END_SESSION;
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
return Boolean(message.flags && message.flags & flag);
|
return Boolean(message.flags && message.flags & flag);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,15 @@ import {
|
||||||
} from 'lodash';
|
} from 'lodash';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import type { RecentStickerType } from '../../types/Stickers';
|
||||||
|
import type {
|
||||||
|
StickerType as StickerDBType,
|
||||||
|
StickerPackType as StickerPackDBType,
|
||||||
|
} from '../../sql/Interface';
|
||||||
import { StateType } from '../reducer';
|
import { StateType } from '../reducer';
|
||||||
import {
|
import {
|
||||||
RecentStickerType,
|
|
||||||
StickerDBType,
|
|
||||||
StickerPackDBType,
|
|
||||||
StickerPackType,
|
|
||||||
StickersStateType,
|
StickersStateType,
|
||||||
|
StickerPackType,
|
||||||
StickerType,
|
StickerType,
|
||||||
} from '../ducks/stickers';
|
} from '../ducks/stickers';
|
||||||
import { getStickersPath, getTempPath } from './user';
|
import { getStickersPath, getTempPath } from './user';
|
||||||
|
@ -95,7 +97,7 @@ export const translatePackFromDB = (
|
||||||
const filterAndTransformPacks = (
|
const filterAndTransformPacks = (
|
||||||
packs: Dictionary<StickerPackDBType>,
|
packs: Dictionary<StickerPackDBType>,
|
||||||
packFilter: (sticker: StickerPackDBType) => boolean,
|
packFilter: (sticker: StickerPackDBType) => boolean,
|
||||||
packSort: (sticker: StickerPackDBType) => number | null,
|
packSort: (sticker: StickerPackDBType) => number | undefined,
|
||||||
blessedPacks: Dictionary<boolean>,
|
blessedPacks: Dictionary<boolean>,
|
||||||
stickersPath: string,
|
stickersPath: string,
|
||||||
tempPath: string
|
tempPath: string
|
||||||
|
|
|
@ -10,10 +10,8 @@ import {
|
||||||
} from '../../components/conversation/conversation-details/GroupLinkManagement';
|
} from '../../components/conversation/conversation-details/GroupLinkManagement';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { AccessControlClass } from '../../textsecure.d';
|
|
||||||
|
|
||||||
export type SmartGroupLinkManagementProps = {
|
export type SmartGroupLinkManagementProps = {
|
||||||
accessEnum: typeof AccessControlClass.AccessRequired;
|
|
||||||
changeHasGroupLink: (value: boolean) => void;
|
changeHasGroupLink: (value: boolean) => void;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
copyGroupLink: (groupLink: string) => void;
|
copyGroupLink: (groupLink: string) => void;
|
||||||
|
|
|
@ -10,10 +10,8 @@ import {
|
||||||
} from '../../components/conversation/conversation-details/GroupV2Permissions';
|
} from '../../components/conversation/conversation-details/GroupV2Permissions';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { AccessControlClass } from '../../textsecure.d';
|
|
||||||
|
|
||||||
export type SmartGroupV2PermissionsProps = {
|
export type SmartGroupV2PermissionsProps = {
|
||||||
accessEnum: typeof AccessControlClass.AccessRequired;
|
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
setAccessControlAttributesSetting: (value: number) => void;
|
setAccessControlAttributesSetting: (value: number) => void;
|
||||||
setAccessControlMembersSetting: (value: number) => void;
|
setAccessControlMembersSetting: (value: number) => void;
|
||||||
|
|
126
ts/test-both/ContactsParser_test.ts
Normal file
126
ts/test-both/ContactsParser_test.ts
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
// Copyright 2015-2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import { Writer } from 'protobufjs';
|
||||||
|
|
||||||
|
import * as Bytes from '../Bytes';
|
||||||
|
import { typedArrayToArrayBuffer } from '../Crypto';
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
import { ContactBuffer, GroupBuffer } from '../textsecure/ContactsParser';
|
||||||
|
|
||||||
|
describe('ContactsParser', () => {
|
||||||
|
function generateAvatar(): Uint8Array {
|
||||||
|
const result = new Uint8Array(255);
|
||||||
|
for (let i = 0; i < result.length; i += 1) {
|
||||||
|
result[i] = i;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ContactBuffer', () => {
|
||||||
|
function getTestBuffer(): ArrayBuffer {
|
||||||
|
const avatarBuffer = generateAvatar();
|
||||||
|
|
||||||
|
const contactInfoBuffer = Proto.ContactDetails.encode({
|
||||||
|
name: 'Zero Cool',
|
||||||
|
number: '+10000000000',
|
||||||
|
uuid: '7198E1BD-1293-452A-A098-F982FF201902',
|
||||||
|
avatar: { contentType: 'image/jpeg', length: avatarBuffer.length },
|
||||||
|
}).finish();
|
||||||
|
|
||||||
|
const writer = new Writer();
|
||||||
|
writer.bytes(contactInfoBuffer);
|
||||||
|
const prefixedContact = writer.finish();
|
||||||
|
|
||||||
|
const chunks: Array<Uint8Array> = [];
|
||||||
|
for (let i = 0; i < 3; i += 1) {
|
||||||
|
chunks.push(prefixedContact);
|
||||||
|
chunks.push(avatarBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return typedArrayToArrayBuffer(Bytes.concatenate(chunks));
|
||||||
|
}
|
||||||
|
|
||||||
|
it('parses an array buffer of contacts', () => {
|
||||||
|
const arrayBuffer = getTestBuffer();
|
||||||
|
const contactBuffer = new ContactBuffer(arrayBuffer);
|
||||||
|
let contact = contactBuffer.next();
|
||||||
|
let count = 0;
|
||||||
|
while (contact !== undefined) {
|
||||||
|
count += 1;
|
||||||
|
assert.strictEqual(contact.name, 'Zero Cool');
|
||||||
|
assert.strictEqual(contact.number, '+10000000000');
|
||||||
|
assert.strictEqual(
|
||||||
|
contact.uuid,
|
||||||
|
'7198e1bd-1293-452a-a098-f982ff201902'
|
||||||
|
);
|
||||||
|
assert.strictEqual(contact.avatar?.contentType, 'image/jpeg');
|
||||||
|
assert.strictEqual(contact.avatar?.length, 255);
|
||||||
|
assert.strictEqual(contact.avatar?.data.byteLength, 255);
|
||||||
|
const avatarBytes = new Uint8Array(
|
||||||
|
contact.avatar?.data || new ArrayBuffer(0)
|
||||||
|
);
|
||||||
|
for (let j = 0; j < 255; j += 1) {
|
||||||
|
assert.strictEqual(avatarBytes[j], j);
|
||||||
|
}
|
||||||
|
contact = contactBuffer.next();
|
||||||
|
}
|
||||||
|
assert.strictEqual(count, 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GroupBuffer', () => {
|
||||||
|
function getTestBuffer(): ArrayBuffer {
|
||||||
|
const avatarBuffer = generateAvatar();
|
||||||
|
|
||||||
|
const groupInfoBuffer = Proto.GroupDetails.encode({
|
||||||
|
id: new Uint8Array([1, 3, 3, 7]),
|
||||||
|
name: 'Hackers',
|
||||||
|
membersE164: ['cereal', 'burn', 'phreak', 'joey'],
|
||||||
|
avatar: { contentType: 'image/jpeg', length: avatarBuffer.length },
|
||||||
|
}).finish();
|
||||||
|
|
||||||
|
const writer = new Writer();
|
||||||
|
writer.bytes(groupInfoBuffer);
|
||||||
|
const prefixedGroup = writer.finish();
|
||||||
|
|
||||||
|
const chunks: Array<Uint8Array> = [];
|
||||||
|
for (let i = 0; i < 3; i += 1) {
|
||||||
|
chunks.push(prefixedGroup);
|
||||||
|
chunks.push(avatarBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return typedArrayToArrayBuffer(Bytes.concatenate(chunks));
|
||||||
|
}
|
||||||
|
|
||||||
|
it('parses an array buffer of groups', () => {
|
||||||
|
const arrayBuffer = getTestBuffer();
|
||||||
|
const groupBuffer = new GroupBuffer(arrayBuffer);
|
||||||
|
let group = groupBuffer.next();
|
||||||
|
let count = 0;
|
||||||
|
while (group !== undefined) {
|
||||||
|
count += 1;
|
||||||
|
assert.strictEqual(group.name, 'Hackers');
|
||||||
|
assert.deepEqual(group.id, new Uint8Array([1, 3, 3, 7]));
|
||||||
|
assert.sameMembers(group.membersE164, [
|
||||||
|
'cereal',
|
||||||
|
'burn',
|
||||||
|
'phreak',
|
||||||
|
'joey',
|
||||||
|
]);
|
||||||
|
assert.strictEqual(group.avatar?.contentType, 'image/jpeg');
|
||||||
|
assert.strictEqual(group.avatar?.length, 255);
|
||||||
|
assert.strictEqual(group.avatar?.data.byteLength, 255);
|
||||||
|
const avatarBytes = new Uint8Array(
|
||||||
|
group.avatar?.data || new ArrayBuffer(0)
|
||||||
|
);
|
||||||
|
for (let j = 0; j < 255; j += 1) {
|
||||||
|
assert.strictEqual(avatarBytes[j], j);
|
||||||
|
}
|
||||||
|
group = groupBuffer.next();
|
||||||
|
}
|
||||||
|
assert.strictEqual(count, 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
333
ts/test-both/processDataMessage_test.ts
Normal file
333
ts/test-both/processDataMessage_test.ts
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
import {
|
||||||
|
processDataMessage,
|
||||||
|
ATTACHMENT_MAX,
|
||||||
|
} from '../textsecure/processDataMessage';
|
||||||
|
import { ProcessedAttachment } from '../textsecure/Types.d';
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
|
const FLAGS = Proto.DataMessage.Flags;
|
||||||
|
|
||||||
|
const TIMESTAMP = Date.now();
|
||||||
|
|
||||||
|
const UNPROCESSED_ATTACHMENT: Proto.IAttachmentPointer = {
|
||||||
|
cdnId: 123,
|
||||||
|
key: new Uint8Array([1, 2, 3]),
|
||||||
|
digest: new Uint8Array([4, 5, 6]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROCESSED_ATTACHMENT: ProcessedAttachment = {
|
||||||
|
cdnId: '123',
|
||||||
|
key: 'AQID',
|
||||||
|
digest: 'BAUG',
|
||||||
|
};
|
||||||
|
|
||||||
|
const GROUP_ID = new Uint8Array([0x68, 0x65, 0x79]);
|
||||||
|
|
||||||
|
const DERIVED_GROUPV2_ID = '7qQUi8Wa6Jm3Rl+l63saATGeciEqokbHpP+lV3F5t9o=';
|
||||||
|
|
||||||
|
describe('processDataMessage', () => {
|
||||||
|
const check = (message: Proto.IDataMessage) =>
|
||||||
|
processDataMessage(
|
||||||
|
{
|
||||||
|
timestamp: TIMESTAMP,
|
||||||
|
...message,
|
||||||
|
},
|
||||||
|
TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should process attachments', async () => {
|
||||||
|
const out = await check({
|
||||||
|
attachments: [UNPROCESSED_ATTACHMENT],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(out.attachments, [PROCESSED_ATTACHMENT]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw on too many attachments', async () => {
|
||||||
|
const attachments: Array<Proto.IAttachmentPointer> = [];
|
||||||
|
for (let i = 0; i < ATTACHMENT_MAX + 1; i += 1) {
|
||||||
|
attachments.push(UNPROCESSED_ATTACHMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
await assert.isRejected(
|
||||||
|
check({ attachments }),
|
||||||
|
`Too many attachments: ${ATTACHMENT_MAX + 1} included in one message` +
|
||||||
|
`, max is ${ATTACHMENT_MAX}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process group context UPDATE/QUIT message', async () => {
|
||||||
|
const { UPDATE, QUIT } = Proto.GroupContext.Type;
|
||||||
|
|
||||||
|
for (const type of [UPDATE, QUIT]) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const out = await check({
|
||||||
|
body: 'should be deleted',
|
||||||
|
attachments: [UNPROCESSED_ATTACHMENT],
|
||||||
|
group: {
|
||||||
|
id: GROUP_ID,
|
||||||
|
name: 'Group',
|
||||||
|
avatar: UNPROCESSED_ATTACHMENT,
|
||||||
|
type,
|
||||||
|
membersE164: ['+1'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.isUndefined(out.body);
|
||||||
|
assert.strictEqual(out.attachments.length, 0);
|
||||||
|
assert.deepStrictEqual(out.group, {
|
||||||
|
id: 'hey',
|
||||||
|
name: 'Group',
|
||||||
|
avatar: PROCESSED_ATTACHMENT,
|
||||||
|
type,
|
||||||
|
membersE164: ['+1'],
|
||||||
|
derivedGroupV2Id: DERIVED_GROUPV2_ID,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process group context DELIVER message', async () => {
|
||||||
|
const out = await check({
|
||||||
|
body: 'should not be deleted',
|
||||||
|
attachments: [UNPROCESSED_ATTACHMENT],
|
||||||
|
group: {
|
||||||
|
id: GROUP_ID,
|
||||||
|
name: 'should be deleted',
|
||||||
|
membersE164: ['should be deleted'],
|
||||||
|
avatar: {},
|
||||||
|
type: Proto.GroupContext.Type.DELIVER,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(out.body, 'should not be deleted');
|
||||||
|
assert.strictEqual(out.attachments.length, 1);
|
||||||
|
assert.deepStrictEqual(out.group, {
|
||||||
|
id: 'hey',
|
||||||
|
type: Proto.GroupContext.Type.DELIVER,
|
||||||
|
membersE164: [],
|
||||||
|
derivedGroupV2Id: DERIVED_GROUPV2_ID,
|
||||||
|
avatar: undefined,
|
||||||
|
name: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process groupv2 context', async () => {
|
||||||
|
const out = await check({
|
||||||
|
groupV2: {
|
||||||
|
masterKey: new Uint8Array(32),
|
||||||
|
revision: 1,
|
||||||
|
groupChange: new Uint8Array([4, 5, 6]),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(out.groupV2, {
|
||||||
|
masterKey: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
|
||||||
|
revision: 1,
|
||||||
|
groupChange: 'BAUG',
|
||||||
|
id: 'd/rq8//fR4RzhvN3G9KcKlQoj7cguQFjTOqLV6JUSbo=',
|
||||||
|
secretParams:
|
||||||
|
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd/rq8//fR' +
|
||||||
|
'4RzhvN3G9KcKlQoj7cguQFjTOqLV6JUSbrURzeILsUmsymGJmHt3kpBJ2zosqp4ex' +
|
||||||
|
'sg+qwF1z6YdB/rxKnxKRLZZP/V0F7bERslYILy2lUh3Sh3iA98yO4CGfzjjFVo1SI' +
|
||||||
|
'7U8XApLeVNQHJo7nkflf/JyBrqPft5gEucbKW/h+S3OYjfQ5zl2Cpw3XrV7N6OKEu' +
|
||||||
|
'tLUWPHQuJx11A4xDPrmtAOnGy2NBxoOybDNlWipeNbn1WQJqOjMF7YA80oEm+5qnM' +
|
||||||
|
'kEYcFVqbYaSzPcMhg3mQ0SYfQpxYgSOJpwp9f/8EDnwJV4ISPBOo2CiaSqVfnd8Dw' +
|
||||||
|
'ZOc58gQA==',
|
||||||
|
publicParams:
|
||||||
|
'AHf66vP/30eEc4bzdxvSnCpUKI+3ILkBY0zqi1eiVEm6LnGylv4fk' +
|
||||||
|
'tzmI30Oc5dgqcN161ezejihLrS1Fjx0LieOJpwp9f/8EDnwJV4ISPBOo2CiaSqVfn' +
|
||||||
|
'd8DwZOc58gQA==',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should base64 profileKey', async () => {
|
||||||
|
const out = await check({
|
||||||
|
profileKey: new Uint8Array([42, 23, 55]),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(out.profileKey, 'Khc3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process quote', async () => {
|
||||||
|
const out = await check({
|
||||||
|
quote: {
|
||||||
|
id: 1,
|
||||||
|
authorUuid: 'author',
|
||||||
|
text: 'text',
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
contentType: 'image/jpeg',
|
||||||
|
fileName: 'image.jpg',
|
||||||
|
thumbnail: UNPROCESSED_ATTACHMENT,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(out.quote, {
|
||||||
|
id: 1,
|
||||||
|
authorUuid: 'author',
|
||||||
|
text: 'text',
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
contentType: 'image/jpeg',
|
||||||
|
fileName: 'image.jpg',
|
||||||
|
thumbnail: PROCESSED_ATTACHMENT,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bodyRanges: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process contact', async () => {
|
||||||
|
const out = await check({
|
||||||
|
contact: [
|
||||||
|
{
|
||||||
|
avatar: {
|
||||||
|
avatar: UNPROCESSED_ATTACHMENT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
avatar: {
|
||||||
|
avatar: UNPROCESSED_ATTACHMENT,
|
||||||
|
isProfile: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(out.contact, [
|
||||||
|
{
|
||||||
|
avatar: { avatar: PROCESSED_ATTACHMENT, isProfile: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
avatar: { avatar: PROCESSED_ATTACHMENT, isProfile: true },
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process reaction', async () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
(
|
||||||
|
await check({
|
||||||
|
reaction: {
|
||||||
|
emoji: '😎',
|
||||||
|
targetTimestamp: TIMESTAMP,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).reaction,
|
||||||
|
{
|
||||||
|
emoji: '😎',
|
||||||
|
remove: false,
|
||||||
|
targetAuthorUuid: undefined,
|
||||||
|
targetTimestamp: TIMESTAMP,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
(
|
||||||
|
await check({
|
||||||
|
reaction: {
|
||||||
|
emoji: '😎',
|
||||||
|
remove: true,
|
||||||
|
targetTimestamp: TIMESTAMP,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).reaction,
|
||||||
|
{
|
||||||
|
emoji: '😎',
|
||||||
|
remove: true,
|
||||||
|
targetAuthorUuid: undefined,
|
||||||
|
targetTimestamp: TIMESTAMP,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process preview', async () => {
|
||||||
|
const out = await check({
|
||||||
|
preview: [
|
||||||
|
{
|
||||||
|
date: TIMESTAMP,
|
||||||
|
image: UNPROCESSED_ATTACHMENT,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(out.preview, [
|
||||||
|
{
|
||||||
|
date: TIMESTAMP,
|
||||||
|
description: undefined,
|
||||||
|
title: undefined,
|
||||||
|
url: undefined,
|
||||||
|
image: PROCESSED_ATTACHMENT,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process sticker', async () => {
|
||||||
|
const out = await check({
|
||||||
|
sticker: {
|
||||||
|
packId: new Uint8Array([1, 2, 3]),
|
||||||
|
packKey: new Uint8Array([4, 5, 6]),
|
||||||
|
stickerId: 1,
|
||||||
|
data: UNPROCESSED_ATTACHMENT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(out.sticker, {
|
||||||
|
packId: '010203',
|
||||||
|
packKey: 'BAUG',
|
||||||
|
stickerId: 1,
|
||||||
|
data: PROCESSED_ATTACHMENT,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process FLAGS=END_SESSION', async () => {
|
||||||
|
const out = await check({
|
||||||
|
flags: FLAGS.END_SESSION,
|
||||||
|
body: 'should be deleted',
|
||||||
|
group: {
|
||||||
|
id: GROUP_ID,
|
||||||
|
type: Proto.GroupContext.Type.DELIVER,
|
||||||
|
},
|
||||||
|
attachments: [UNPROCESSED_ATTACHMENT],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.isUndefined(out.body);
|
||||||
|
assert.isUndefined(out.group);
|
||||||
|
assert.deepStrictEqual(out.attachments, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process FLAGS=EXPIRATION_TIMER_UPDATE,PROFILE_KEY_UPDATE', async () => {
|
||||||
|
const values = [FLAGS.EXPIRATION_TIMER_UPDATE, FLAGS.PROFILE_KEY_UPDATE];
|
||||||
|
for (const flags of values) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const out = await check({
|
||||||
|
flags,
|
||||||
|
body: 'should be deleted',
|
||||||
|
attachments: [UNPROCESSED_ATTACHMENT],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.isUndefined(out.body);
|
||||||
|
assert.deepStrictEqual(out.attachments, []);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('processes trivial fields', async () => {
|
||||||
|
assert.strictEqual((await check({ flags: null })).flags, 0);
|
||||||
|
assert.strictEqual((await check({ flags: 1 })).flags, 1);
|
||||||
|
|
||||||
|
assert.strictEqual((await check({ expireTimer: null })).expireTimer, 0);
|
||||||
|
assert.strictEqual((await check({ expireTimer: 123 })).expireTimer, 123);
|
||||||
|
|
||||||
|
assert.isFalse((await check({ isViewOnce: null })).isViewOnce);
|
||||||
|
assert.isFalse((await check({ isViewOnce: false })).isViewOnce);
|
||||||
|
assert.isTrue((await check({ isViewOnce: true })).isViewOnce);
|
||||||
|
});
|
||||||
|
});
|
37
ts/test-both/processSyncMessage_test.ts
Normal file
37
ts/test-both/processSyncMessage_test.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import getGuid from 'uuid/v4';
|
||||||
|
|
||||||
|
import { processSyncMessage } from '../textsecure/processSyncMessage';
|
||||||
|
|
||||||
|
describe('processSyncMessage', () => {
|
||||||
|
it('should normalize UUIDs in sent', () => {
|
||||||
|
const destinationUuid = getGuid();
|
||||||
|
|
||||||
|
const out = processSyncMessage({
|
||||||
|
sent: {
|
||||||
|
destinationUuid: destinationUuid.toUpperCase(),
|
||||||
|
|
||||||
|
unidentifiedStatus: [
|
||||||
|
{
|
||||||
|
destinationUuid: destinationUuid.toUpperCase(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(out, {
|
||||||
|
sent: {
|
||||||
|
destinationUuid,
|
||||||
|
|
||||||
|
unidentifiedStatus: [
|
||||||
|
{
|
||||||
|
destinationUuid,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -13,7 +13,7 @@ describe('both/state/ducks/composer', () => {
|
||||||
conversationId: '123',
|
conversationId: '123',
|
||||||
quote: {
|
quote: {
|
||||||
attachments: [],
|
attachments: [],
|
||||||
id: '456',
|
id: 456,
|
||||||
isViewOnce: false,
|
isViewOnce: false,
|
||||||
messageId: '789',
|
messageId: '789',
|
||||||
referencedMessageNotFound: false,
|
referencedMessageNotFound: false,
|
||||||
|
@ -114,7 +114,7 @@ describe('both/state/ducks/composer', () => {
|
||||||
const nextState = reducer(state, setQuotedMessage(QUOTED_MESSAGE));
|
const nextState = reducer(state, setQuotedMessage(QUOTED_MESSAGE));
|
||||||
|
|
||||||
assert.equal(nextState.quotedMessage?.conversationId, '123');
|
assert.equal(nextState.quotedMessage?.conversationId, '123');
|
||||||
assert.equal(nextState.quotedMessage?.quote?.id, '456');
|
assert.equal(nextState.quotedMessage?.quote?.id, 456);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,12 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
|
||||||
import { dropNull } from '../../util/dropNull';
|
import { dropNull, shallowDropNull } from '../../util/dropNull';
|
||||||
|
|
||||||
|
type Test = {
|
||||||
|
a: number | null;
|
||||||
|
b: number | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
describe('dropNull', () => {
|
describe('dropNull', () => {
|
||||||
it('swaps null with undefined', () => {
|
it('swaps null with undefined', () => {
|
||||||
|
@ -16,4 +21,42 @@ describe('dropNull', () => {
|
||||||
it('non-null values undefined be', () => {
|
it('non-null values undefined be', () => {
|
||||||
assert.strictEqual(dropNull('test'), 'test');
|
assert.strictEqual(dropNull('test'), 'test');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('shallowDropNull', () => {
|
||||||
|
it('return undefined with given null', () => {
|
||||||
|
assert.strictEqual(shallowDropNull<Test>(null), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return undefined with given undefined', () => {
|
||||||
|
assert.strictEqual(shallowDropNull<Test>(undefined), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('swaps null with undefined', () => {
|
||||||
|
const result:
|
||||||
|
| {
|
||||||
|
a: number | undefined;
|
||||||
|
b: number | undefined;
|
||||||
|
}
|
||||||
|
| undefined = shallowDropNull<Test>({
|
||||||
|
a: null,
|
||||||
|
b: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result, { a: undefined, b: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves undefined be', () => {
|
||||||
|
const result:
|
||||||
|
| {
|
||||||
|
a: number | undefined;
|
||||||
|
b: number | undefined;
|
||||||
|
}
|
||||||
|
| undefined = shallowDropNull<Test>({
|
||||||
|
a: 1,
|
||||||
|
b: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result, { a: 1, b: undefined });
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
76
ts/test-electron/MessageReceiver_test.ts
Normal file
76
ts/test-electron/MessageReceiver_test.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright 2015-2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable
|
||||||
|
class-methods-use-this,
|
||||||
|
@typescript-eslint/no-empty-function
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
import { connection as WebSocket } from 'websocket';
|
||||||
|
|
||||||
|
import MessageReceiver from '../textsecure/MessageReceiver';
|
||||||
|
import { DecryptionErrorEvent } from '../textsecure/messageReceiverEvents';
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
import * as Crypto from '../Crypto';
|
||||||
|
|
||||||
|
// TODO: remove once we move away from ArrayBuffers
|
||||||
|
const FIXMEU8 = Uint8Array;
|
||||||
|
|
||||||
|
describe('MessageReceiver', () => {
|
||||||
|
class FakeSocket extends EventEmitter {
|
||||||
|
public sendBytes(_: Uint8Array) {}
|
||||||
|
|
||||||
|
public close() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const number = '+19999999999';
|
||||||
|
const uuid = 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee';
|
||||||
|
const deviceId = 1;
|
||||||
|
const signalingKey = Crypto.getRandomBytes(32 + 20);
|
||||||
|
|
||||||
|
describe('connecting', () => {
|
||||||
|
it('generates decryption-error event when it cannot decrypt', done => {
|
||||||
|
const socket = new FakeSocket();
|
||||||
|
|
||||||
|
const messageReceiver = new MessageReceiver(
|
||||||
|
'oldUsername.2',
|
||||||
|
'username.2',
|
||||||
|
'password',
|
||||||
|
signalingKey,
|
||||||
|
{
|
||||||
|
serverTrustRoot: 'AAAAAAAA',
|
||||||
|
socket: socket as WebSocket,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const body = Proto.Envelope.encode({
|
||||||
|
type: Proto.Envelope.Type.CIPHERTEXT,
|
||||||
|
source: number,
|
||||||
|
sourceUuid: uuid,
|
||||||
|
sourceDevice: deviceId,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
content: new FIXMEU8(Crypto.getRandomBytes(200)),
|
||||||
|
}).finish();
|
||||||
|
|
||||||
|
const message = Proto.WebSocketMessage.encode({
|
||||||
|
type: Proto.WebSocketMessage.Type.REQUEST,
|
||||||
|
request: { id: 1, verb: 'PUT', path: '/api/v1/message', body },
|
||||||
|
}).finish();
|
||||||
|
|
||||||
|
socket.emit('message', {
|
||||||
|
type: 'binary',
|
||||||
|
binaryData: message,
|
||||||
|
});
|
||||||
|
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'decryption-error',
|
||||||
|
(error: DecryptionErrorEvent) => {
|
||||||
|
assert.strictEqual(error.decryptionError.senderUuid, uuid);
|
||||||
|
assert.strictEqual(error.decryptionError.senderDevice, deviceId);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -5,6 +5,7 @@ import { assert } from 'chai';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import { setup as setupI18n } from '../../../js/modules/i18n';
|
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||||
import enMessages from '../../../_locales/en/messages.json';
|
import enMessages from '../../../_locales/en/messages.json';
|
||||||
|
import { SignalService as Proto } from '../../protobuf';
|
||||||
|
|
||||||
describe('Message', () => {
|
describe('Message', () => {
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
@ -384,8 +385,7 @@ describe('Message', () => {
|
||||||
title: 'voice message',
|
title: 'voice message',
|
||||||
attachment: {
|
attachment: {
|
||||||
contentType: 'audio/ogg',
|
contentType: 'audio/ogg',
|
||||||
flags:
|
flags: Proto.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||||
window.textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE,
|
|
||||||
},
|
},
|
||||||
expectedText: 'Voice Message',
|
expectedText: 'Voice Message',
|
||||||
expectedEmoji: '🎤',
|
expectedEmoji: '🎤',
|
||||||
|
|
212
ts/textsecure.d.ts
vendored
212
ts/textsecure.d.ts
vendored
|
@ -18,6 +18,7 @@ import { Storage } from './textsecure/Storage';
|
||||||
import {
|
import {
|
||||||
StorageServiceCallOptionsType,
|
StorageServiceCallOptionsType,
|
||||||
StorageServiceCredentials,
|
StorageServiceCredentials,
|
||||||
|
ProcessedAttachment,
|
||||||
} from './textsecure/Types.d';
|
} from './textsecure/Types.d';
|
||||||
|
|
||||||
export type UnprocessedType = {
|
export type UnprocessedType = {
|
||||||
|
@ -31,6 +32,7 @@ export type UnprocessedType = {
|
||||||
source?: string;
|
source?: string;
|
||||||
sourceDevice?: number;
|
sourceDevice?: number;
|
||||||
sourceUuid?: string;
|
sourceUuid?: string;
|
||||||
|
messageAgeSec?: number;
|
||||||
version: number;
|
version: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -71,7 +73,6 @@ type DeviceNameProtobufTypes = {
|
||||||
|
|
||||||
type GroupsProtobufTypes = {
|
type GroupsProtobufTypes = {
|
||||||
AvatarUploadAttributes: typeof AvatarUploadAttributesClass;
|
AvatarUploadAttributes: typeof AvatarUploadAttributesClass;
|
||||||
Member: typeof MemberClass;
|
|
||||||
MemberPendingProfileKey: typeof MemberPendingProfileKeyClass;
|
MemberPendingProfileKey: typeof MemberPendingProfileKeyClass;
|
||||||
MemberPendingAdminApproval: typeof MemberPendingAdminApprovalClass;
|
MemberPendingAdminApproval: typeof MemberPendingAdminApprovalClass;
|
||||||
AccessControl: typeof AccessControlClass;
|
AccessControl: typeof AccessControlClass;
|
||||||
|
@ -86,9 +87,6 @@ type GroupsProtobufTypes = {
|
||||||
|
|
||||||
type SignalServiceProtobufTypes = {
|
type SignalServiceProtobufTypes = {
|
||||||
AttachmentPointer: typeof AttachmentPointerClass;
|
AttachmentPointer: typeof AttachmentPointerClass;
|
||||||
ContactDetails: typeof ContactDetailsClass;
|
|
||||||
Content: typeof ContentClass;
|
|
||||||
DataMessage: typeof DataMessageClass;
|
|
||||||
Envelope: typeof EnvelopeClass;
|
Envelope: typeof EnvelopeClass;
|
||||||
GroupContext: typeof GroupContextClass;
|
GroupContext: typeof GroupContextClass;
|
||||||
GroupContextV2: typeof GroupContextV2Class;
|
GroupContextV2: typeof GroupContextV2Class;
|
||||||
|
@ -159,39 +157,14 @@ export declare class AvatarUploadAttributesClass {
|
||||||
signature?: string;
|
signature?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class MemberClass {
|
|
||||||
static decode: (
|
|
||||||
data: ArrayBuffer | ByteBufferClass,
|
|
||||||
encoding?: string
|
|
||||||
) => MemberClass;
|
|
||||||
|
|
||||||
userId?: ProtoBinaryType;
|
|
||||||
role?: MemberRoleEnum;
|
|
||||||
profileKey?: ProtoBinaryType;
|
|
||||||
presentation?: ProtoBinaryType;
|
|
||||||
joinedAtVersion?: number;
|
|
||||||
|
|
||||||
// Note: only role and presentation are required when creating a group
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MemberRoleEnum = number;
|
export type MemberRoleEnum = number;
|
||||||
|
|
||||||
// Note: we need to use namespaces to express nested classes in Typescript
|
|
||||||
export declare namespace MemberClass {
|
|
||||||
class Role {
|
|
||||||
static UNKNOWN: number;
|
|
||||||
static DEFAULT: number;
|
|
||||||
static ADMINISTRATOR: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare class MemberPendingProfileKeyClass {
|
export declare class MemberPendingProfileKeyClass {
|
||||||
static decode: (
|
static decode: (
|
||||||
data: ArrayBuffer | ByteBufferClass,
|
data: ArrayBuffer | ByteBufferClass,
|
||||||
encoding?: string
|
encoding?: string
|
||||||
) => MemberPendingProfileKeyClass;
|
) => MemberPendingProfileKeyClass;
|
||||||
|
|
||||||
member?: MemberClass;
|
|
||||||
addedByUserId?: ProtoBinaryType;
|
addedByUserId?: ProtoBinaryType;
|
||||||
timestamp?: ProtoBigNumberType;
|
timestamp?: ProtoBigNumberType;
|
||||||
}
|
}
|
||||||
|
@ -245,7 +218,6 @@ export declare class GroupClass {
|
||||||
disappearingMessagesTimer?: ProtoBinaryType;
|
disappearingMessagesTimer?: ProtoBinaryType;
|
||||||
accessControl?: AccessControlClass;
|
accessControl?: AccessControlClass;
|
||||||
version?: number;
|
version?: number;
|
||||||
members?: Array<MemberClass>;
|
|
||||||
membersPendingProfileKey?: Array<MemberPendingProfileKeyClass>;
|
membersPendingProfileKey?: Array<MemberPendingProfileKeyClass>;
|
||||||
membersPendingAdminApproval?: Array<MemberPendingAdminApprovalClass>;
|
membersPendingAdminApproval?: Array<MemberPendingAdminApprovalClass>;
|
||||||
inviteLinkPassword?: ProtoBinaryType;
|
inviteLinkPassword?: ProtoBinaryType;
|
||||||
|
@ -299,7 +271,6 @@ export declare namespace GroupChangeClass {
|
||||||
// Note: we need to use namespaces to express nested classes in Typescript
|
// Note: we need to use namespaces to express nested classes in Typescript
|
||||||
export declare namespace GroupChangeClass.Actions {
|
export declare namespace GroupChangeClass.Actions {
|
||||||
class AddMemberAction {
|
class AddMemberAction {
|
||||||
added?: MemberClass;
|
|
||||||
joinFromInviteLink?: boolean;
|
joinFromInviteLink?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,7 +451,7 @@ export declare class AttachmentPointerClass {
|
||||||
GIF: number;
|
GIF: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
cdnId?: ProtoBigNumberType;
|
cdnId?: string;
|
||||||
cdnKey?: string;
|
cdnKey?: string;
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
key?: ProtoBinaryType;
|
key?: ProtoBinaryType;
|
||||||
|
@ -493,184 +464,16 @@ export declare class AttachmentPointerClass {
|
||||||
height?: number;
|
height?: number;
|
||||||
caption?: string;
|
caption?: string;
|
||||||
blurHash?: string;
|
blurHash?: string;
|
||||||
uploadTimestamp?: ProtoBigNumberType;
|
|
||||||
cdnNumber?: number;
|
cdnNumber?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DownloadAttachmentType = {
|
export type DownloadAttachmentType = Omit<
|
||||||
|
ProcessedAttachment,
|
||||||
|
'digest' | 'key'
|
||||||
|
> & {
|
||||||
data: ArrayBuffer;
|
data: ArrayBuffer;
|
||||||
cdnId?: ProtoBigNumberType;
|
|
||||||
cdnKey?: string;
|
|
||||||
contentType?: string;
|
|
||||||
size?: number;
|
|
||||||
thumbnail?: ProtoBinaryType;
|
|
||||||
fileName?: string;
|
|
||||||
flags?: number;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
caption?: string;
|
|
||||||
blurHash?: string;
|
|
||||||
uploadTimestamp?: ProtoBigNumberType;
|
|
||||||
cdnNumber?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export declare class ContactDetailsClass {
|
|
||||||
static decode: (
|
|
||||||
data: ArrayBuffer | ByteBufferClass,
|
|
||||||
encoding?: string
|
|
||||||
) => ContactDetailsClass;
|
|
||||||
|
|
||||||
number?: string;
|
|
||||||
uuid?: string;
|
|
||||||
name?: string;
|
|
||||||
avatar?: ContactDetailsClass.Avatar;
|
|
||||||
color?: string;
|
|
||||||
verified?: VerifiedClass;
|
|
||||||
profileKey?: ProtoBinaryType;
|
|
||||||
blocked?: boolean;
|
|
||||||
expireTimer?: number;
|
|
||||||
inboxPosition?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: we need to use namespaces to express nested classes in Typescript
|
|
||||||
export declare namespace ContactDetailsClass {
|
|
||||||
class Avatar {
|
|
||||||
contentType?: string;
|
|
||||||
length?: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare class ContentClass {
|
|
||||||
static decode: (
|
|
||||||
data: ArrayBuffer | ByteBufferClass,
|
|
||||||
encoding?: string
|
|
||||||
) => ContentClass;
|
|
||||||
toArrayBuffer: () => ArrayBuffer;
|
|
||||||
|
|
||||||
dataMessage?: DataMessageClass;
|
|
||||||
syncMessage?: SyncMessageClass;
|
|
||||||
callingMessage?: CallingMessageClass;
|
|
||||||
nullMessage?: NullMessageClass;
|
|
||||||
receiptMessage?: ReceiptMessageClass;
|
|
||||||
typingMessage?: TypingMessageClass;
|
|
||||||
senderKeyDistributionMessage?: ByteBufferClass;
|
|
||||||
decryptionErrorMessage?: ByteBufferClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare class DataMessageClass {
|
|
||||||
static decode: (
|
|
||||||
data: ArrayBuffer | ByteBufferClass,
|
|
||||||
encoding?: string
|
|
||||||
) => DataMessageClass;
|
|
||||||
toArrayBuffer(): ArrayBuffer;
|
|
||||||
|
|
||||||
body?: string | null;
|
|
||||||
attachments?: Array<AttachmentPointerClass>;
|
|
||||||
group?: GroupContextClass | null;
|
|
||||||
groupV2?: GroupContextV2Class | null;
|
|
||||||
flags?: number;
|
|
||||||
expireTimer?: number;
|
|
||||||
profileKey?: ProtoBinaryType;
|
|
||||||
timestamp?: ProtoBigNumberType;
|
|
||||||
quote?: DataMessageClass.Quote;
|
|
||||||
contact?: Array<DataMessageClass.Contact>;
|
|
||||||
preview?: Array<DataMessageClass.Preview>;
|
|
||||||
sticker?: DataMessageClass.Sticker;
|
|
||||||
requiredProtocolVersion?: number;
|
|
||||||
isViewOnce?: boolean;
|
|
||||||
reaction?: DataMessageClass.Reaction;
|
|
||||||
delete?: DataMessageClass.Delete;
|
|
||||||
bodyRanges?: Array<DataMessageClass.BodyRange>;
|
|
||||||
groupCallUpdate?: DataMessageClass.GroupCallUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: we need to use namespaces to express nested classes in Typescript
|
|
||||||
export declare namespace DataMessageClass {
|
|
||||||
// Note: deep nesting
|
|
||||||
class Contact {
|
|
||||||
name: any;
|
|
||||||
number: any;
|
|
||||||
email: any;
|
|
||||||
address: any;
|
|
||||||
avatar: any;
|
|
||||||
organization?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Flags {
|
|
||||||
static END_SESSION: number;
|
|
||||||
static EXPIRATION_TIMER_UPDATE: number;
|
|
||||||
static PROFILE_KEY_UPDATE: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Preview {
|
|
||||||
url?: string;
|
|
||||||
title?: string;
|
|
||||||
image?: AttachmentPointerClass;
|
|
||||||
description?: string;
|
|
||||||
date?: ProtoBigNumberType;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProtocolVersion {
|
|
||||||
static INITIAL: number;
|
|
||||||
static MESSAGE_TIMERS: number;
|
|
||||||
static VIEW_ONCE: number;
|
|
||||||
static VIEW_ONCE_VIDEO: number;
|
|
||||||
static REACTIONS: number;
|
|
||||||
static MENTIONS: number;
|
|
||||||
static CURRENT: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: deep nesting
|
|
||||||
class Quote {
|
|
||||||
id?: ProtoBigNumberType | null;
|
|
||||||
authorUuid?: string | null;
|
|
||||||
text?: string | null;
|
|
||||||
attachments?: Array<DataMessageClass.Quote.QuotedAttachment>;
|
|
||||||
bodyRanges?: Array<DataMessageClass.BodyRange>;
|
|
||||||
|
|
||||||
// Added later during processing
|
|
||||||
referencedMessageNotFound?: boolean;
|
|
||||||
isViewOnce?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BodyRange {
|
|
||||||
start?: number;
|
|
||||||
length?: number;
|
|
||||||
mentionUuid?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Reaction {
|
|
||||||
emoji: string | null;
|
|
||||||
remove: boolean;
|
|
||||||
targetAuthorUuid: string | null;
|
|
||||||
targetTimestamp: ProtoBigNumberType | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Delete {
|
|
||||||
targetSentTimestamp?: ProtoBigNumberType;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Sticker {
|
|
||||||
packId?: ProtoBinaryType;
|
|
||||||
packKey?: ProtoBinaryType;
|
|
||||||
stickerId?: number;
|
|
||||||
data?: AttachmentPointerClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GroupCallUpdate {
|
|
||||||
eraId?: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: we need to use namespaces to express nested classes in Typescript
|
|
||||||
export declare namespace DataMessageClass.Quote {
|
|
||||||
class QuotedAttachment {
|
|
||||||
contentType?: string;
|
|
||||||
fileName?: string;
|
|
||||||
thumbnail?: AttachmentPointerClass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare class DeviceNameClass {
|
declare class DeviceNameClass {
|
||||||
static decode: (
|
static decode: (
|
||||||
data: ArrayBuffer | ByteBufferClass,
|
data: ArrayBuffer | ByteBufferClass,
|
||||||
|
@ -1140,7 +943,6 @@ export declare namespace SyncMessageClass {
|
||||||
destination?: string;
|
destination?: string;
|
||||||
destinationUuid?: string;
|
destinationUuid?: string;
|
||||||
timestamp?: ProtoBigNumberType;
|
timestamp?: ProtoBigNumberType;
|
||||||
message?: DataMessageClass;
|
|
||||||
expirationStartTimestamp?: ProtoBigNumberType;
|
expirationStartTimestamp?: ProtoBigNumberType;
|
||||||
unidentifiedStatus?: Array<SyncMessageClass.Sent.UnidentifiedDeliveryStatus>;
|
unidentifiedStatus?: Array<SyncMessageClass.Sent.UnidentifiedDeliveryStatus>;
|
||||||
isRecipientUpdate?: boolean;
|
isRecipientUpdate?: boolean;
|
||||||
|
|
|
@ -1,101 +1,160 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
import { ByteBufferClass } from '../window.d';
|
import { Reader } from 'protobufjs';
|
||||||
import { AttachmentType } from './SendMessage';
|
|
||||||
|
|
||||||
type ProtobufConstructorType = {
|
import { SignalService as Proto } from '../protobuf';
|
||||||
decode: (data: ArrayBuffer) => ProtobufType;
|
import { normalizeUuid } from '../util/normalizeUuid';
|
||||||
|
import { typedArrayToArrayBuffer } from '../Crypto';
|
||||||
|
|
||||||
|
import Avatar = Proto.ContactDetails.IAvatar;
|
||||||
|
|
||||||
|
type OptionalAvatar = { avatar?: Avatar | null };
|
||||||
|
|
||||||
|
type DecoderBase<Message extends OptionalAvatar> = {
|
||||||
|
decodeDelimited(reader: Reader): Message | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProtobufType = {
|
export type MessageWithAvatar<Message extends OptionalAvatar> = Omit<
|
||||||
avatar?: PackedAttachmentType;
|
Message,
|
||||||
profileKey?: any;
|
'avatar'
|
||||||
uuid?: string;
|
> & {
|
||||||
members: Array<string>;
|
avatar?: (Avatar & { data: ArrayBuffer }) | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PackedAttachmentType = AttachmentType & {
|
export type ModifiedGroupDetails = MessageWithAvatar<Proto.GroupDetails>;
|
||||||
length: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ProtoParser {
|
export type ModifiedContactDetails = MessageWithAvatar<Proto.ContactDetails>;
|
||||||
buffer: ByteBufferClass;
|
|
||||||
|
|
||||||
protobuf: ProtobufConstructorType;
|
// TODO: remove once we move away from ArrayBuffers
|
||||||
|
const FIXMEU8 = Uint8Array;
|
||||||
|
|
||||||
constructor(arrayBuffer: ArrayBuffer, protobuf: ProtobufConstructorType) {
|
class ParserBase<
|
||||||
this.protobuf = protobuf;
|
Message extends OptionalAvatar,
|
||||||
this.buffer = new window.dcodeIO.ByteBuffer();
|
Decoder extends DecoderBase<Message>
|
||||||
this.buffer.append(arrayBuffer);
|
> {
|
||||||
this.buffer.offset = 0;
|
protected readonly reader: Reader;
|
||||||
this.buffer.limit = arrayBuffer.byteLength;
|
|
||||||
|
constructor(arrayBuffer: ArrayBuffer, private readonly decoder: Decoder) {
|
||||||
|
this.reader = new Reader(new FIXMEU8(arrayBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
next(): ProtobufType | undefined | null {
|
protected decodeDelimited(): MessageWithAvatar<Message> | undefined {
|
||||||
|
if (this.reader.pos === this.reader.len) {
|
||||||
|
return undefined; // eof
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.buffer.limit === this.buffer.offset) {
|
const proto = this.decoder.decodeDelimited(this.reader);
|
||||||
return undefined; // eof
|
|
||||||
}
|
|
||||||
const len = this.buffer.readVarint32();
|
|
||||||
const nextBuffer = this.buffer
|
|
||||||
.slice(this.buffer.offset, this.buffer.offset + len)
|
|
||||||
.toArrayBuffer();
|
|
||||||
|
|
||||||
const proto = this.protobuf.decode(nextBuffer);
|
if (!proto) {
|
||||||
this.buffer.skip(len);
|
return undefined;
|
||||||
|
|
||||||
if (proto.avatar) {
|
|
||||||
const attachmentLen = proto.avatar.length;
|
|
||||||
proto.avatar.data = this.buffer
|
|
||||||
.slice(this.buffer.offset, this.buffer.offset + attachmentLen)
|
|
||||||
.toArrayBuffer();
|
|
||||||
this.buffer.skip(attachmentLen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proto.profileKey) {
|
if (!proto.avatar) {
|
||||||
proto.profileKey = proto.profileKey.toArrayBuffer();
|
return {
|
||||||
|
...proto,
|
||||||
|
avatar: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proto.uuid) {
|
const attachmentLen = proto.avatar.length ?? 0;
|
||||||
window.normalizeUuids(
|
const avatarData = this.reader.buf.slice(
|
||||||
proto,
|
this.reader.pos,
|
||||||
['uuid'],
|
this.reader.pos + attachmentLen
|
||||||
'ProtoParser::next (proto.uuid)'
|
);
|
||||||
);
|
this.reader.skip(attachmentLen);
|
||||||
}
|
|
||||||
|
|
||||||
if (proto.members) {
|
return {
|
||||||
window.normalizeUuids(
|
...proto,
|
||||||
proto,
|
|
||||||
proto.members.map((_member, i) => `members.${i}.uuid`),
|
|
||||||
'ProtoParser::next (proto.members)'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return proto;
|
avatar: {
|
||||||
|
...proto.avatar,
|
||||||
|
|
||||||
|
data: typedArrayToArrayBuffer(avatarData),
|
||||||
|
},
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'ProtoParser.next error:',
|
'ProtoParser.next error:',
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupBuffer extends ParserBase<
|
||||||
|
Proto.GroupDetails,
|
||||||
|
typeof Proto.GroupDetails
|
||||||
|
> {
|
||||||
|
constructor(arrayBuffer: ArrayBuffer) {
|
||||||
|
super(arrayBuffer, Proto.GroupDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
public next(): ModifiedGroupDetails | undefined {
|
||||||
|
const proto = this.decodeDelimited();
|
||||||
|
if (!proto) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
if (!proto.members) {
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...proto,
|
||||||
|
members: proto.members.map((member, i) => {
|
||||||
|
if (!member.uuid) {
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...member,
|
||||||
|
uuid: normalizeUuid(member.uuid, `GroupBuffer.member[${i}].uuid`),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GroupBuffer extends ProtoParser {
|
export class ContactBuffer extends ParserBase<
|
||||||
|
Proto.ContactDetails,
|
||||||
|
typeof Proto.ContactDetails
|
||||||
|
> {
|
||||||
constructor(arrayBuffer: ArrayBuffer) {
|
constructor(arrayBuffer: ArrayBuffer) {
|
||||||
super(arrayBuffer, window.textsecure.protobuf.GroupDetails as any);
|
super(arrayBuffer, Proto.ContactDetails);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export class ContactBuffer extends ProtoParser {
|
public next(): ModifiedContactDetails | undefined {
|
||||||
constructor(arrayBuffer: ArrayBuffer) {
|
const proto = this.decodeDelimited();
|
||||||
super(arrayBuffer, window.textsecure.protobuf.ContactDetails as any);
|
if (!proto) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!proto.uuid) {
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { verified } = proto;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...proto,
|
||||||
|
|
||||||
|
verified:
|
||||||
|
verified && verified.destinationUuid
|
||||||
|
? {
|
||||||
|
...verified,
|
||||||
|
|
||||||
|
destinationUuid: normalizeUuid(
|
||||||
|
verified.destinationUuid,
|
||||||
|
'ContactBuffer.verified.destinationUuid'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: verified,
|
||||||
|
|
||||||
|
uuid: normalizeUuid(proto.uuid, 'ContactBuffer.uuid'),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,7 @@ const Crypto = {
|
||||||
async decryptAttachment(
|
async decryptAttachment(
|
||||||
encryptedBin: ArrayBuffer,
|
encryptedBin: ArrayBuffer,
|
||||||
keys: ArrayBuffer,
|
keys: ArrayBuffer,
|
||||||
theirDigest: ArrayBuffer
|
theirDigest?: ArrayBuffer
|
||||||
): Promise<ArrayBuffer> {
|
): Promise<ArrayBuffer> {
|
||||||
if (keys.byteLength !== 64) {
|
if (keys.byteLength !== 64) {
|
||||||
throw new Error('Got invalid length attachment keys');
|
throw new Error('Got invalid length attachment keys');
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
|
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type EventHandler = (event: any) => unknown;
|
||||||
|
|
||||||
export default class EventTarget {
|
export default class EventTarget {
|
||||||
listeners?: { [type: string]: Array<Function> };
|
listeners?: { [type: string]: Array<EventHandler> };
|
||||||
|
|
||||||
dispatchEvent(ev: Event): Array<unknown> {
|
dispatchEvent(ev: Event): Array<unknown> {
|
||||||
if (!(ev instanceof Event)) {
|
if (!(ev instanceof Event)) {
|
||||||
|
@ -36,7 +38,7 @@ export default class EventTarget {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListener(eventName: string, callback: Function): void {
|
addEventListener(eventName: string, callback: EventHandler): void {
|
||||||
if (typeof eventName !== 'string') {
|
if (typeof eventName !== 'string') {
|
||||||
throw new Error('First argument expects a string');
|
throw new Error('First argument expects a string');
|
||||||
}
|
}
|
||||||
|
@ -54,7 +56,7 @@ export default class EventTarget {
|
||||||
this.listeners[eventName] = listeners;
|
this.listeners[eventName] = listeners;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeEventListener(eventName: string, callback: Function): void {
|
removeEventListener(eventName: string, callback: EventHandler): void {
|
||||||
if (typeof eventName !== 'string') {
|
if (typeof eventName !== 'string') {
|
||||||
throw new Error('First argument expects a string');
|
throw new Error('First argument expects a string');
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -23,7 +23,6 @@ import {
|
||||||
} from '@signalapp/signal-client';
|
} from '@signalapp/signal-client';
|
||||||
|
|
||||||
import { WebAPIType } from './WebAPI';
|
import { WebAPIType } from './WebAPI';
|
||||||
import { ContentClass, DataMessageClass } from '../textsecure.d';
|
|
||||||
import {
|
import {
|
||||||
CallbackResultType,
|
CallbackResultType,
|
||||||
SendMetadataType,
|
SendMetadataType,
|
||||||
|
@ -42,6 +41,7 @@ import { Sessions, IdentityKeys } from '../LibSignalStores';
|
||||||
import { typedArrayToArrayBuffer as toArrayBuffer } from '../Crypto';
|
import { typedArrayToArrayBuffer as toArrayBuffer } from '../Crypto';
|
||||||
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
||||||
import { getKeysForIdentifier } from './getKeysForIdentifier';
|
import { getKeysForIdentifier } from './getKeysForIdentifier';
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
export const enum SenderCertificateMode {
|
export const enum SenderCertificateMode {
|
||||||
WithE164,
|
WithE164,
|
||||||
|
@ -72,13 +72,13 @@ type OutgoingMessageOptionsType = SendOptionsType & {
|
||||||
|
|
||||||
function ciphertextMessageTypeToEnvelopeType(type: number) {
|
function ciphertextMessageTypeToEnvelopeType(type: number) {
|
||||||
if (type === CiphertextMessageType.PreKey) {
|
if (type === CiphertextMessageType.PreKey) {
|
||||||
return window.textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE;
|
return Proto.Envelope.Type.PREKEY_BUNDLE;
|
||||||
}
|
}
|
||||||
if (type === CiphertextMessageType.Whisper) {
|
if (type === CiphertextMessageType.Whisper) {
|
||||||
return window.textsecure.protobuf.Envelope.Type.CIPHERTEXT;
|
return Proto.Envelope.Type.CIPHERTEXT;
|
||||||
}
|
}
|
||||||
if (type === CiphertextMessageType.Plaintext) {
|
if (type === CiphertextMessageType.Plaintext) {
|
||||||
return window.textsecure.protobuf.Envelope.Type.PLAINTEXT_CONTENT;
|
return Proto.Envelope.Type.PLAINTEXT_CONTENT;
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`ciphertextMessageTypeToEnvelopeType: Unrecognized type ${type}`
|
`ciphertextMessageTypeToEnvelopeType: Unrecognized type ${type}`
|
||||||
|
@ -96,11 +96,11 @@ function getPaddedMessageLength(messageLength: number): number {
|
||||||
return messagePartCount * 160;
|
return messagePartCount * 160;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function padMessage(messageBuffer: ArrayBuffer): Uint8Array {
|
export function padMessage(messageBuffer: Uint8Array): Uint8Array {
|
||||||
const plaintext = new Uint8Array(
|
const plaintext = new Uint8Array(
|
||||||
getPaddedMessageLength(messageBuffer.byteLength + 1) - 1
|
getPaddedMessageLength(messageBuffer.byteLength + 1) - 1
|
||||||
);
|
);
|
||||||
plaintext.set(new Uint8Array(messageBuffer));
|
plaintext.set(messageBuffer);
|
||||||
plaintext[messageBuffer.byteLength] = 0x80;
|
plaintext[messageBuffer.byteLength] = 0x80;
|
||||||
|
|
||||||
return plaintext;
|
return plaintext;
|
||||||
|
@ -113,7 +113,7 @@ export default class OutgoingMessage {
|
||||||
|
|
||||||
identifiers: Array<string>;
|
identifiers: Array<string>;
|
||||||
|
|
||||||
message: ContentClass | PlaintextContent;
|
message: Proto.Content | PlaintextContent;
|
||||||
|
|
||||||
callback: (result: CallbackResultType) => void;
|
callback: (result: CallbackResultType) => void;
|
||||||
|
|
||||||
|
@ -141,14 +141,14 @@ export default class OutgoingMessage {
|
||||||
server: WebAPIType,
|
server: WebAPIType,
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
identifiers: Array<string>,
|
identifiers: Array<string>,
|
||||||
message: ContentClass | DataMessageClass | PlaintextContent,
|
message: Proto.Content | Proto.DataMessage | PlaintextContent,
|
||||||
contentHint: number,
|
contentHint: number,
|
||||||
groupId: string | undefined,
|
groupId: string | undefined,
|
||||||
callback: (result: CallbackResultType) => void,
|
callback: (result: CallbackResultType) => void,
|
||||||
options: OutgoingMessageOptionsType = {}
|
options: OutgoingMessageOptionsType = {}
|
||||||
) {
|
) {
|
||||||
if (message instanceof window.textsecure.protobuf.DataMessage) {
|
if (message instanceof Proto.DataMessage) {
|
||||||
const content = new window.textsecure.protobuf.Content();
|
const content = new Proto.Content();
|
||||||
content.dataMessage = message;
|
content.dataMessage = message;
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
this.message = content;
|
this.message = content;
|
||||||
|
@ -304,8 +304,8 @@ export default class OutgoingMessage {
|
||||||
if (!this.plaintext) {
|
if (!this.plaintext) {
|
||||||
const { message } = this;
|
const { message } = this;
|
||||||
|
|
||||||
if (message instanceof window.textsecure.protobuf.Content) {
|
if (message instanceof Proto.Content) {
|
||||||
this.plaintext = padMessage(message.toArrayBuffer());
|
this.plaintext = padMessage(Proto.Content.encode(message).finish());
|
||||||
} else {
|
} else {
|
||||||
this.plaintext = message.serialize();
|
this.plaintext = message.serialize();
|
||||||
}
|
}
|
||||||
|
@ -324,7 +324,7 @@ export default class OutgoingMessage {
|
||||||
}): Promise<CiphertextMessage> {
|
}): Promise<CiphertextMessage> {
|
||||||
const { message } = this;
|
const { message } = this;
|
||||||
|
|
||||||
if (message instanceof window.textsecure.protobuf.Content) {
|
if (message instanceof Proto.Content) {
|
||||||
return signalEncrypt(
|
return signalEncrypt(
|
||||||
Buffer.from(this.getPlaintext()),
|
Buffer.from(this.getPlaintext()),
|
||||||
protocolAddress,
|
protocolAddress,
|
||||||
|
@ -421,8 +421,7 @@ export default class OutgoingMessage {
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type:
|
type: Proto.Envelope.Type.UNIDENTIFIED_SENDER,
|
||||||
window.textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER,
|
|
||||||
destinationDeviceId,
|
destinationDeviceId,
|
||||||
destinationRegistrationId,
|
destinationRegistrationId,
|
||||||
content: buffer.toString('base64'),
|
content: buffer.toString('base64'),
|
||||||
|
|
|
@ -14,7 +14,8 @@ import {
|
||||||
} from '../Crypto';
|
} from '../Crypto';
|
||||||
import { calculateAgreement, createKeyPair, generateKeyPair } from '../Curve';
|
import { calculateAgreement, createKeyPair, generateKeyPair } from '../Curve';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import { assert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
|
import { normalizeUuid } from '../util/normalizeUuid';
|
||||||
|
|
||||||
// TODO: remove once we move away from ArrayBuffers
|
// TODO: remove once we move away from ArrayBuffers
|
||||||
const FIXMEU8 = Uint8Array;
|
const FIXMEU8 = Uint8Array;
|
||||||
|
@ -35,7 +36,7 @@ class ProvisioningCipherInner {
|
||||||
async decrypt(
|
async decrypt(
|
||||||
provisionEnvelope: Proto.ProvisionEnvelope
|
provisionEnvelope: Proto.ProvisionEnvelope
|
||||||
): Promise<ProvisionDecryptResult> {
|
): Promise<ProvisionDecryptResult> {
|
||||||
assert(
|
strictAssert(
|
||||||
provisionEnvelope.publicKey && provisionEnvelope.body,
|
provisionEnvelope.publicKey && provisionEnvelope.body,
|
||||||
'Missing required fields in ProvisionEnvelope'
|
'Missing required fields in ProvisionEnvelope'
|
||||||
);
|
);
|
||||||
|
@ -79,19 +80,17 @@ class ProvisioningCipherInner {
|
||||||
new FIXMEU8(plaintext)
|
new FIXMEU8(plaintext)
|
||||||
);
|
);
|
||||||
const privKey = provisionMessage.identityKeyPrivate;
|
const privKey = provisionMessage.identityKeyPrivate;
|
||||||
assert(privKey, 'Missing identityKeyPrivate in ProvisionMessage');
|
strictAssert(privKey, 'Missing identityKeyPrivate in ProvisionMessage');
|
||||||
|
|
||||||
const keyPair = createKeyPair(typedArrayToArrayBuffer(privKey));
|
const keyPair = createKeyPair(typedArrayToArrayBuffer(privKey));
|
||||||
window.normalizeUuids(
|
|
||||||
provisionMessage,
|
const { uuid } = provisionMessage;
|
||||||
['uuid'],
|
strictAssert(uuid, 'Missing uuid in provisioning message');
|
||||||
'ProvisioningCipher.decrypt'
|
|
||||||
);
|
|
||||||
|
|
||||||
const ret: ProvisionDecryptResult = {
|
const ret: ProvisionDecryptResult = {
|
||||||
identityKeyPair: keyPair,
|
identityKeyPair: keyPair,
|
||||||
number: provisionMessage.number,
|
number: provisionMessage.number,
|
||||||
uuid: provisionMessage.uuid,
|
uuid: normalizeUuid(uuid, 'ProvisionMessage.uuid'),
|
||||||
provisioningCode: provisionMessage.provisioningCode,
|
provisioningCode: provisionMessage.provisioningCode,
|
||||||
userAgent: provisionMessage.userAgent,
|
userAgent: provisionMessage.userAgent,
|
||||||
readReceipts: provisionMessage.readReceipts,
|
readReceipts: provisionMessage.readReceipts,
|
||||||
|
|
|
@ -30,22 +30,16 @@ import {
|
||||||
import createTaskWithTimeout from './TaskWithTimeout';
|
import createTaskWithTimeout from './TaskWithTimeout';
|
||||||
import OutgoingMessage, { SerializedCertificateType } from './OutgoingMessage';
|
import OutgoingMessage, { SerializedCertificateType } from './OutgoingMessage';
|
||||||
import Crypto from './Crypto';
|
import Crypto from './Crypto';
|
||||||
|
import * as Bytes from '../Bytes';
|
||||||
import {
|
import {
|
||||||
base64ToArrayBuffer,
|
|
||||||
concatenateBytes,
|
concatenateBytes,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
getZeroes,
|
getZeroes,
|
||||||
hexToArrayBuffer,
|
|
||||||
typedArrayToArrayBuffer,
|
typedArrayToArrayBuffer,
|
||||||
} from '../Crypto';
|
} from '../Crypto';
|
||||||
import {
|
import {
|
||||||
AttachmentPointerClass,
|
|
||||||
CallingMessageClass,
|
|
||||||
ContentClass,
|
|
||||||
DataMessageClass,
|
|
||||||
StorageServiceCallOptionsType,
|
StorageServiceCallOptionsType,
|
||||||
StorageServiceCredentials,
|
StorageServiceCredentials,
|
||||||
SyncMessageClass,
|
|
||||||
} from '../textsecure.d';
|
} from '../textsecure.d';
|
||||||
import { MessageError, SignedPreKeyRotationError } from './Errors';
|
import { MessageError, SignedPreKeyRotationError } from './Errors';
|
||||||
import { BodyRangesType } from '../types/Util';
|
import { BodyRangesType } from '../types/Util';
|
||||||
|
@ -56,18 +50,6 @@ import {
|
||||||
import { concat } from '../util/iterables';
|
import { concat } from '../util/iterables';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
function stringToArrayBuffer(str: string): ArrayBuffer {
|
|
||||||
if (typeof str !== 'string') {
|
|
||||||
throw new Error('Passed non-string to stringToArrayBuffer');
|
|
||||||
}
|
|
||||||
const res = new ArrayBuffer(str.length);
|
|
||||||
const uint = new Uint8Array(res);
|
|
||||||
for (let i = 0; i < str.length; i += 1) {
|
|
||||||
uint[i] = str.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SendMetadataType = {
|
export type SendMetadataType = {
|
||||||
[identifier: string]: {
|
[identifier: string]: {
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
|
@ -101,7 +83,7 @@ type PreviewType = {
|
||||||
|
|
||||||
type QuoteAttachmentType = {
|
type QuoteAttachmentType = {
|
||||||
thumbnail?: AttachmentType;
|
thumbnail?: AttachmentType;
|
||||||
attachmentPointer?: AttachmentPointerClass;
|
attachmentPointer?: Proto.IAttachmentPointer;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GroupV2InfoType = {
|
export type GroupV2InfoType = {
|
||||||
|
@ -130,7 +112,7 @@ export type AttachmentType = {
|
||||||
height: number;
|
height: number;
|
||||||
caption: string;
|
caption: string;
|
||||||
|
|
||||||
attachmentPointer?: AttachmentPointerClass;
|
attachmentPointer?: Proto.IAttachmentPointer;
|
||||||
|
|
||||||
blurHash?: string;
|
blurHash?: string;
|
||||||
};
|
};
|
||||||
|
@ -174,6 +156,9 @@ export type GroupSendOptionsType = {
|
||||||
groupCallUpdate?: GroupCallUpdateType;
|
groupCallUpdate?: GroupCallUpdateType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: remove once we move away from ArrayBuffers
|
||||||
|
const FIXMEU8 = Uint8Array;
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
attachments: Array<any>;
|
attachments: Array<any>;
|
||||||
|
|
||||||
|
@ -219,7 +204,7 @@ class Message {
|
||||||
|
|
||||||
dataMessage: any;
|
dataMessage: any;
|
||||||
|
|
||||||
attachmentPointers?: Array<any>;
|
attachmentPointers: Array<Proto.IAttachmentPointer> = [];
|
||||||
|
|
||||||
deletedForEveryoneTimestamp?: number;
|
deletedForEveryoneTimestamp?: number;
|
||||||
|
|
||||||
|
@ -301,17 +286,14 @@ class Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
isEndSession() {
|
isEndSession() {
|
||||||
return (
|
return (this.flags || 0) & Proto.DataMessage.Flags.END_SESSION;
|
||||||
(this.flags || 0) &
|
|
||||||
window.textsecure.protobuf.DataMessage.Flags.END_SESSION
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toProto(): DataMessageClass {
|
toProto(): Proto.DataMessage {
|
||||||
if (this.dataMessage instanceof window.textsecure.protobuf.DataMessage) {
|
if (this.dataMessage instanceof Proto.DataMessage) {
|
||||||
return this.dataMessage;
|
return this.dataMessage;
|
||||||
}
|
}
|
||||||
const proto = new window.textsecure.protobuf.DataMessage();
|
const proto = new Proto.DataMessage();
|
||||||
|
|
||||||
proto.timestamp = this.timestamp;
|
proto.timestamp = this.timestamp;
|
||||||
proto.attachments = this.attachmentPointers;
|
proto.attachments = this.attachmentPointers;
|
||||||
|
@ -330,19 +312,19 @@ class Message {
|
||||||
proto.flags = this.flags;
|
proto.flags = this.flags;
|
||||||
}
|
}
|
||||||
if (this.groupV2) {
|
if (this.groupV2) {
|
||||||
proto.groupV2 = new window.textsecure.protobuf.GroupContextV2();
|
proto.groupV2 = new Proto.GroupContextV2();
|
||||||
proto.groupV2.masterKey = this.groupV2.masterKey;
|
proto.groupV2.masterKey = this.groupV2.masterKey;
|
||||||
proto.groupV2.revision = this.groupV2.revision;
|
proto.groupV2.revision = this.groupV2.revision;
|
||||||
proto.groupV2.groupChange = this.groupV2.groupChange || null;
|
proto.groupV2.groupChange = this.groupV2.groupChange || null;
|
||||||
} else if (this.group) {
|
} else if (this.group) {
|
||||||
proto.group = new window.textsecure.protobuf.GroupContext();
|
proto.group = new Proto.GroupContext();
|
||||||
proto.group.id = stringToArrayBuffer(this.group.id);
|
proto.group.id = Bytes.fromString(this.group.id);
|
||||||
proto.group.type = this.group.type;
|
proto.group.type = this.group.type;
|
||||||
}
|
}
|
||||||
if (this.sticker) {
|
if (this.sticker) {
|
||||||
proto.sticker = new window.textsecure.protobuf.DataMessage.Sticker();
|
proto.sticker = new Proto.DataMessage.Sticker();
|
||||||
proto.sticker.packId = hexToArrayBuffer(this.sticker.packId);
|
proto.sticker.packId = Bytes.fromHex(this.sticker.packId);
|
||||||
proto.sticker.packKey = base64ToArrayBuffer(this.sticker.packKey);
|
proto.sticker.packKey = Bytes.fromBase64(this.sticker.packKey);
|
||||||
proto.sticker.stickerId = this.sticker.stickerId;
|
proto.sticker.stickerId = this.sticker.stickerId;
|
||||||
|
|
||||||
if (this.sticker.attachmentPointer) {
|
if (this.sticker.attachmentPointer) {
|
||||||
|
@ -350,7 +332,7 @@ class Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.reaction) {
|
if (this.reaction) {
|
||||||
proto.reaction = new window.textsecure.protobuf.DataMessage.Reaction();
|
proto.reaction = new Proto.DataMessage.Reaction();
|
||||||
proto.reaction.emoji = this.reaction.emoji || null;
|
proto.reaction.emoji = this.reaction.emoji || null;
|
||||||
proto.reaction.remove = this.reaction.remove || false;
|
proto.reaction.remove = this.reaction.remove || false;
|
||||||
proto.reaction.targetAuthorUuid = this.reaction.targetAuthorUuid || null;
|
proto.reaction.targetAuthorUuid = this.reaction.targetAuthorUuid || null;
|
||||||
|
@ -359,7 +341,7 @@ class Message {
|
||||||
|
|
||||||
if (Array.isArray(this.preview)) {
|
if (Array.isArray(this.preview)) {
|
||||||
proto.preview = this.preview.map(preview => {
|
proto.preview = this.preview.map(preview => {
|
||||||
const item = new window.textsecure.protobuf.DataMessage.Preview();
|
const item = new Proto.DataMessage.Preview();
|
||||||
item.title = preview.title;
|
item.title = preview.title;
|
||||||
item.url = preview.url;
|
item.url = preview.url;
|
||||||
item.description = preview.description || null;
|
item.description = preview.description || null;
|
||||||
|
@ -369,8 +351,8 @@ class Message {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.quote) {
|
if (this.quote) {
|
||||||
const { QuotedAttachment } = window.textsecure.protobuf.DataMessage.Quote;
|
const { QuotedAttachment } = Proto.DataMessage.Quote;
|
||||||
const { BodyRange, Quote } = window.textsecure.protobuf.DataMessage;
|
const { BodyRange, Quote } = Proto.DataMessage;
|
||||||
|
|
||||||
proto.quote = new Quote();
|
proto.quote = new Quote();
|
||||||
const { quote } = proto;
|
const { quote } = proto;
|
||||||
|
@ -396,24 +378,26 @@ class Message {
|
||||||
const bodyRange = new BodyRange();
|
const bodyRange = new BodyRange();
|
||||||
bodyRange.start = range.start;
|
bodyRange.start = range.start;
|
||||||
bodyRange.length = range.length;
|
bodyRange.length = range.length;
|
||||||
bodyRange.mentionUuid = range.mentionUuid;
|
if (range.mentionUuid !== undefined) {
|
||||||
|
bodyRange.mentionUuid = range.mentionUuid;
|
||||||
|
}
|
||||||
return bodyRange;
|
return bodyRange;
|
||||||
});
|
});
|
||||||
if (
|
if (
|
||||||
quote.bodyRanges.length &&
|
quote.bodyRanges.length &&
|
||||||
(!proto.requiredProtocolVersion ||
|
(!proto.requiredProtocolVersion ||
|
||||||
proto.requiredProtocolVersion <
|
proto.requiredProtocolVersion <
|
||||||
window.textsecure.protobuf.DataMessage.ProtocolVersion.MENTIONS)
|
Proto.DataMessage.ProtocolVersion.MENTIONS)
|
||||||
) {
|
) {
|
||||||
proto.requiredProtocolVersion =
|
proto.requiredProtocolVersion =
|
||||||
window.textsecure.protobuf.DataMessage.ProtocolVersion.MENTIONS;
|
Proto.DataMessage.ProtocolVersion.MENTIONS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.expireTimer) {
|
if (this.expireTimer) {
|
||||||
proto.expireTimer = this.expireTimer;
|
proto.expireTimer = this.expireTimer;
|
||||||
}
|
}
|
||||||
if (this.profileKey) {
|
if (this.profileKey) {
|
||||||
proto.profileKey = this.profileKey;
|
proto.profileKey = new FIXMEU8(this.profileKey);
|
||||||
}
|
}
|
||||||
if (this.deletedForEveryoneTimestamp) {
|
if (this.deletedForEveryoneTimestamp) {
|
||||||
proto.delete = {
|
proto.delete = {
|
||||||
|
@ -422,7 +406,7 @@ class Message {
|
||||||
}
|
}
|
||||||
if (this.mentions) {
|
if (this.mentions) {
|
||||||
proto.requiredProtocolVersion =
|
proto.requiredProtocolVersion =
|
||||||
window.textsecure.protobuf.DataMessage.ProtocolVersion.MENTIONS;
|
Proto.DataMessage.ProtocolVersion.MENTIONS;
|
||||||
proto.bodyRanges = this.mentions.map(
|
proto.bodyRanges = this.mentions.map(
|
||||||
({ start, length, mentionUuid }) => ({
|
({ start, length, mentionUuid }) => ({
|
||||||
start,
|
start,
|
||||||
|
@ -433,7 +417,7 @@ class Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.groupCallUpdate) {
|
if (this.groupCallUpdate) {
|
||||||
const { GroupCallUpdate } = window.textsecure.protobuf.DataMessage;
|
const { GroupCallUpdate } = Proto.DataMessage;
|
||||||
|
|
||||||
const groupCallUpdate = new GroupCallUpdate();
|
const groupCallUpdate = new GroupCallUpdate();
|
||||||
groupCallUpdate.eraId = this.groupCallUpdate.eraId;
|
groupCallUpdate.eraId = this.groupCallUpdate.eraId;
|
||||||
|
@ -446,7 +430,9 @@ class Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
toArrayBuffer() {
|
toArrayBuffer() {
|
||||||
return this.toProto().toArrayBuffer();
|
return typedArrayToArrayBuffer(
|
||||||
|
Proto.DataMessage.encode(this.toProto()).finish()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,13 +478,13 @@ export default class MessageSender {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRandomPadding(): ArrayBuffer {
|
getRandomPadding(): Uint8Array {
|
||||||
// Generate a random int from 1 and 512
|
// Generate a random int from 1 and 512
|
||||||
const buffer = getRandomBytes(2);
|
const buffer = getRandomBytes(2);
|
||||||
const paddingLength = (new Uint16Array(buffer)[0] & 0x1ff) + 1;
|
const paddingLength = (new Uint16Array(buffer)[0] & 0x1ff) + 1;
|
||||||
|
|
||||||
// Generate a random padding buffer of the chosen size
|
// Generate a random padding buffer of the chosen size
|
||||||
return getRandomBytes(paddingLength);
|
return new FIXMEU8(getRandomBytes(paddingLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
getPaddedAttachment(data: ArrayBuffer): ArrayBuffer {
|
getPaddedAttachment(data: ArrayBuffer): ArrayBuffer {
|
||||||
|
@ -511,10 +497,11 @@ export default class MessageSender {
|
||||||
|
|
||||||
async makeAttachmentPointer(
|
async makeAttachmentPointer(
|
||||||
attachment: AttachmentType
|
attachment: AttachmentType
|
||||||
): Promise<AttachmentPointerClass | undefined> {
|
): Promise<Proto.IAttachmentPointer> {
|
||||||
if (typeof attachment !== 'object' || attachment == null) {
|
assert(
|
||||||
return Promise.resolve(undefined);
|
typeof attachment === 'object' && attachment !== null,
|
||||||
}
|
'Got null attachment in `makeAttachmentPointer`'
|
||||||
|
);
|
||||||
|
|
||||||
const { data, size } = attachment;
|
const { data, size } = attachment;
|
||||||
if (!(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
|
if (!(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
|
||||||
|
@ -535,12 +522,12 @@ export default class MessageSender {
|
||||||
const result = await Crypto.encryptAttachment(padded, key, iv);
|
const result = await Crypto.encryptAttachment(padded, key, iv);
|
||||||
const id = await this.server.putAttachment(result.ciphertext);
|
const id = await this.server.putAttachment(result.ciphertext);
|
||||||
|
|
||||||
const proto = new window.textsecure.protobuf.AttachmentPointer();
|
const proto = new Proto.AttachmentPointer();
|
||||||
proto.cdnId = id;
|
proto.cdnId = id;
|
||||||
proto.contentType = attachment.contentType;
|
proto.contentType = attachment.contentType;
|
||||||
proto.key = key;
|
proto.key = new FIXMEU8(key);
|
||||||
proto.size = attachment.size;
|
proto.size = attachment.size;
|
||||||
proto.digest = result.digest;
|
proto.digest = new FIXMEU8(result.digest);
|
||||||
|
|
||||||
if (attachment.fileName) {
|
if (attachment.fileName) {
|
||||||
proto.fileName = attachment.fileName;
|
proto.fileName = attachment.fileName;
|
||||||
|
@ -657,11 +644,11 @@ export default class MessageSender {
|
||||||
return message.toArrayBuffer();
|
return message.toArrayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getContentMessage(options: MessageOptionsType): Promise<ContentClass> {
|
async getContentMessage(options: MessageOptionsType): Promise<Proto.Content> {
|
||||||
const message = await this.getHydratedMessage(options);
|
const message = await this.getHydratedMessage(options);
|
||||||
const dataMessage = message.toProto();
|
const dataMessage = message.toProto();
|
||||||
|
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.dataMessage = dataMessage;
|
contentMessage.dataMessage = dataMessage;
|
||||||
|
|
||||||
return contentMessage;
|
return contentMessage;
|
||||||
|
@ -685,8 +672,8 @@ export default class MessageSender {
|
||||||
groupMembers: Array<string>;
|
groupMembers: Array<string>;
|
||||||
isTyping: boolean;
|
isTyping: boolean;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
}): ContentClass {
|
}): Proto.Content {
|
||||||
const ACTION_ENUM = window.textsecure.protobuf.TypingMessage.Action;
|
const ACTION_ENUM = Proto.TypingMessage.Action;
|
||||||
const { recipientId, groupId, isTyping, timestamp } = options;
|
const { recipientId, groupId, isTyping, timestamp } = options;
|
||||||
|
|
||||||
if (!recipientId && !groupId) {
|
if (!recipientId && !groupId) {
|
||||||
|
@ -698,12 +685,14 @@ export default class MessageSender {
|
||||||
const finalTimestamp = timestamp || Date.now();
|
const finalTimestamp = timestamp || Date.now();
|
||||||
const action = isTyping ? ACTION_ENUM.STARTED : ACTION_ENUM.STOPPED;
|
const action = isTyping ? ACTION_ENUM.STARTED : ACTION_ENUM.STOPPED;
|
||||||
|
|
||||||
const typingMessage = new window.textsecure.protobuf.TypingMessage();
|
const typingMessage = new Proto.TypingMessage();
|
||||||
typingMessage.groupId = groupId || null;
|
if (groupId) {
|
||||||
|
typingMessage.groupId = new FIXMEU8(groupId);
|
||||||
|
}
|
||||||
typingMessage.action = action;
|
typingMessage.action = action;
|
||||||
typingMessage.timestamp = finalTimestamp;
|
typingMessage.timestamp = finalTimestamp;
|
||||||
|
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.typingMessage = typingMessage;
|
contentMessage.typingMessage = typingMessage;
|
||||||
|
|
||||||
return contentMessage;
|
return contentMessage;
|
||||||
|
@ -767,7 +756,7 @@ export default class MessageSender {
|
||||||
group: groupV1
|
group: groupV1
|
||||||
? {
|
? {
|
||||||
id: groupV1.id,
|
id: groupV1.id,
|
||||||
type: window.textsecure.protobuf.GroupContext.Type.DELIVER,
|
type: Proto.GroupContext.Type.DELIVER,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
mentions,
|
mentions,
|
||||||
|
@ -781,8 +770,8 @@ export default class MessageSender {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
createSyncMessage(): SyncMessageClass {
|
createSyncMessage(): Proto.SyncMessage {
|
||||||
const syncMessage = new window.textsecure.protobuf.SyncMessage();
|
const syncMessage = new Proto.SyncMessage();
|
||||||
|
|
||||||
syncMessage.padding = this.getRandomPadding();
|
syncMessage.padding = this.getRandomPadding();
|
||||||
|
|
||||||
|
@ -843,7 +832,7 @@ export default class MessageSender {
|
||||||
}: {
|
}: {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
recipients: Array<string>;
|
recipients: Array<string>;
|
||||||
proto: ContentClass | DataMessageClass | PlaintextContent;
|
proto: Proto.Content | Proto.DataMessage | PlaintextContent;
|
||||||
contentHint: number;
|
contentHint: number;
|
||||||
groupId: string | undefined;
|
groupId: string | undefined;
|
||||||
callback: (result: CallbackResultType) => void;
|
callback: (result: CallbackResultType) => void;
|
||||||
|
@ -885,7 +874,7 @@ export default class MessageSender {
|
||||||
}: {
|
}: {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
recipients: Array<string>;
|
recipients: Array<string>;
|
||||||
proto: ContentClass | DataMessageClass | PlaintextContent;
|
proto: Proto.Content | Proto.DataMessage | PlaintextContent;
|
||||||
contentHint: number;
|
contentHint: number;
|
||||||
groupId: string | undefined;
|
groupId: string | undefined;
|
||||||
options?: SendOptionsType;
|
options?: SendOptionsType;
|
||||||
|
@ -920,7 +909,7 @@ export default class MessageSender {
|
||||||
options,
|
options,
|
||||||
}: {
|
}: {
|
||||||
identifier: string | undefined;
|
identifier: string | undefined;
|
||||||
proto: DataMessageClass | ContentClass | PlaintextContent;
|
proto: Proto.DataMessage | Proto.Content | PlaintextContent;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
contentHint: number;
|
contentHint: number;
|
||||||
options?: SendOptionsType;
|
options?: SendOptionsType;
|
||||||
|
@ -1030,10 +1019,10 @@ export default class MessageSender {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataMessage = window.textsecure.protobuf.DataMessage.decode(
|
const dataMessage = Proto.DataMessage.decode(
|
||||||
encodedDataMessage
|
new FIXMEU8(encodedDataMessage)
|
||||||
);
|
);
|
||||||
const sentMessage = new window.textsecure.protobuf.SyncMessage.Sent();
|
const sentMessage = new Proto.SyncMessage.Sent();
|
||||||
sentMessage.timestamp = timestamp;
|
sentMessage.timestamp = timestamp;
|
||||||
sentMessage.message = dataMessage;
|
sentMessage.message = dataMessage;
|
||||||
if (destination) {
|
if (destination) {
|
||||||
|
@ -1063,13 +1052,17 @@ export default class MessageSender {
|
||||||
// number we sent to.
|
// number we sent to.
|
||||||
if (sentTo && sentTo.length) {
|
if (sentTo && sentTo.length) {
|
||||||
sentMessage.unidentifiedStatus = sentTo.map(identifier => {
|
sentMessage.unidentifiedStatus = sentTo.map(identifier => {
|
||||||
const status = new window.textsecure.protobuf.SyncMessage.Sent.UnidentifiedDeliveryStatus();
|
const status = new Proto.SyncMessage.Sent.UnidentifiedDeliveryStatus();
|
||||||
const conv = window.ConversationController.get(identifier);
|
const conv = window.ConversationController.get(identifier);
|
||||||
if (conv && conv.get('e164')) {
|
if (conv) {
|
||||||
status.destination = conv.get('e164');
|
const e164 = conv.get('e164');
|
||||||
}
|
if (e164) {
|
||||||
if (conv && conv.get('uuid')) {
|
status.destination = e164;
|
||||||
status.destinationUuid = conv.get('uuid');
|
}
|
||||||
|
const uuid = conv.get('uuid');
|
||||||
|
if (uuid) {
|
||||||
|
status.destinationUuid = uuid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
status.unidentified = Boolean(unidentifiedLookup[identifier]);
|
status.unidentified = Boolean(unidentifiedLookup[identifier]);
|
||||||
return status;
|
return status;
|
||||||
|
@ -1078,12 +1071,10 @@ export default class MessageSender {
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.sent = sentMessage;
|
syncMessage.sent = sentMessage;
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1101,17 +1092,14 @@ export default class MessageSender {
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
const myUuid = window.textsecure.storage.user.getUuid();
|
||||||
const myDevice = window.textsecure.storage.user.getDeviceId();
|
const myDevice = window.textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice !== 1) {
|
if (myDevice !== 1) {
|
||||||
const request = new window.textsecure.protobuf.SyncMessage.Request();
|
const request = new Proto.SyncMessage.Request();
|
||||||
request.type =
|
request.type = Proto.SyncMessage.Request.Type.BLOCKED;
|
||||||
window.textsecure.protobuf.SyncMessage.Request.Type.BLOCKED;
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.request = request;
|
syncMessage.request = request;
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1132,17 +1120,14 @@ export default class MessageSender {
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
const myUuid = window.textsecure.storage.user.getUuid();
|
||||||
const myDevice = window.textsecure.storage.user.getDeviceId();
|
const myDevice = window.textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice !== 1) {
|
if (myDevice !== 1) {
|
||||||
const request = new window.textsecure.protobuf.SyncMessage.Request();
|
const request = new Proto.SyncMessage.Request();
|
||||||
request.type =
|
request.type = Proto.SyncMessage.Request.Type.CONFIGURATION;
|
||||||
window.textsecure.protobuf.SyncMessage.Request.Type.CONFIGURATION;
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.request = request;
|
syncMessage.request = request;
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1163,16 +1148,14 @@ export default class MessageSender {
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
const myUuid = window.textsecure.storage.user.getUuid();
|
||||||
const myDevice = window.textsecure.storage.user.getDeviceId();
|
const myDevice = window.textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice !== 1) {
|
if (myDevice !== 1) {
|
||||||
const request = new window.textsecure.protobuf.SyncMessage.Request();
|
const request = new Proto.SyncMessage.Request();
|
||||||
request.type = window.textsecure.protobuf.SyncMessage.Request.Type.GROUPS;
|
request.type = Proto.SyncMessage.Request.Type.GROUPS;
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.request = request;
|
syncMessage.request = request;
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1194,17 +1177,14 @@ export default class MessageSender {
|
||||||
|
|
||||||
const myDevice = window.textsecure.storage.user.getDeviceId();
|
const myDevice = window.textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice !== 1) {
|
if (myDevice !== 1) {
|
||||||
const request = new window.textsecure.protobuf.SyncMessage.Request();
|
const request = new Proto.SyncMessage.Request();
|
||||||
request.type =
|
request.type = Proto.SyncMessage.Request.Type.CONTACTS;
|
||||||
window.textsecure.protobuf.SyncMessage.Request.Type.CONTACTS;
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.request = request;
|
syncMessage.request = request;
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1229,18 +1209,15 @@ export default class MessageSender {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchLatest = new window.textsecure.protobuf.SyncMessage.FetchLatest();
|
const fetchLatest = new Proto.SyncMessage.FetchLatest();
|
||||||
fetchLatest.type =
|
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST;
|
||||||
window.textsecure.protobuf.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST;
|
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.fetchLatest = fetchLatest;
|
syncMessage.fetchLatest = fetchLatest;
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
await this.sendIndividualProto({
|
await this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1262,17 +1239,15 @@ export default class MessageSender {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = new window.textsecure.protobuf.SyncMessage.Request();
|
const request = new Proto.SyncMessage.Request();
|
||||||
request.type = window.textsecure.protobuf.SyncMessage.Request.Type.KEYS;
|
request.type = Proto.SyncMessage.Request.Type.KEYS;
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.request = request;
|
syncMessage.request = request;
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
await this.sendIndividualProto({
|
await this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1295,25 +1270,19 @@ export default class MessageSender {
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
const myUuid = window.textsecure.storage.user.getUuid();
|
||||||
const myDevice = window.textsecure.storage.user.getDeviceId();
|
const myDevice = window.textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice === 1) {
|
if (myDevice === 1) {
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.read = [];
|
syncMessage.read = [];
|
||||||
for (let i = 0; i < reads.length; i += 1) {
|
for (let i = 0; i < reads.length; i += 1) {
|
||||||
const read = new window.textsecure.protobuf.SyncMessage.Read();
|
const proto = new Proto.SyncMessage.Read(reads[i]);
|
||||||
read.timestamp = reads[i].timestamp;
|
|
||||||
read.sender = reads[i].senderE164 || null;
|
|
||||||
read.senderUuid = reads[i].senderUuid || null;
|
|
||||||
|
|
||||||
syncMessage.read.push(read);
|
syncMessage.read.push(proto);
|
||||||
}
|
}
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1339,18 +1308,18 @@ export default class MessageSender {
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
|
|
||||||
const viewOnceOpen = new window.textsecure.protobuf.SyncMessage.ViewOnceOpen();
|
const viewOnceOpen = new Proto.SyncMessage.ViewOnceOpen();
|
||||||
viewOnceOpen.sender = sender || null;
|
if (sender !== undefined) {
|
||||||
viewOnceOpen.senderUuid = senderUuid || null;
|
viewOnceOpen.sender = sender;
|
||||||
viewOnceOpen.timestamp = timestamp || null;
|
}
|
||||||
|
viewOnceOpen.senderUuid = senderUuid;
|
||||||
|
viewOnceOpen.timestamp = timestamp;
|
||||||
syncMessage.viewOnceOpen = viewOnceOpen;
|
syncMessage.viewOnceOpen = viewOnceOpen;
|
||||||
|
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1379,19 +1348,23 @@ export default class MessageSender {
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
|
|
||||||
const response = new window.textsecure.protobuf.SyncMessage.MessageRequestResponse();
|
const response = new Proto.SyncMessage.MessageRequestResponse();
|
||||||
response.threadE164 = responseArgs.threadE164 || null;
|
if (responseArgs.threadE164 !== undefined) {
|
||||||
response.threadUuid = responseArgs.threadUuid || null;
|
response.threadE164 = responseArgs.threadE164;
|
||||||
response.groupId = responseArgs.groupId || null;
|
}
|
||||||
|
if (responseArgs.threadUuid !== undefined) {
|
||||||
|
response.threadUuid = responseArgs.threadUuid;
|
||||||
|
}
|
||||||
|
if (responseArgs.groupId) {
|
||||||
|
response.groupId = new FIXMEU8(responseArgs.groupId);
|
||||||
|
}
|
||||||
response.type = responseArgs.type;
|
response.type = responseArgs.type;
|
||||||
syncMessage.messageRequestResponse = response;
|
syncMessage.messageRequestResponse = response;
|
||||||
|
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1417,15 +1390,14 @@ export default class MessageSender {
|
||||||
|
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myNumber = window.textsecure.storage.user.getNumber();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
const myUuid = window.textsecure.storage.user.getUuid();
|
||||||
const ENUM =
|
const ENUM = Proto.SyncMessage.StickerPackOperation.Type;
|
||||||
window.textsecure.protobuf.SyncMessage.StickerPackOperation.Type;
|
|
||||||
|
|
||||||
const packOperations = operations.map(item => {
|
const packOperations = operations.map(item => {
|
||||||
const { packId, packKey, installed } = item;
|
const { packId, packKey, installed } = item;
|
||||||
|
|
||||||
const operation = new window.textsecure.protobuf.SyncMessage.StickerPackOperation();
|
const operation = new Proto.SyncMessage.StickerPackOperation();
|
||||||
operation.packId = hexToArrayBuffer(packId);
|
operation.packId = Bytes.fromHex(packId);
|
||||||
operation.packKey = base64ToArrayBuffer(packKey);
|
operation.packKey = Bytes.fromBase64(packKey);
|
||||||
operation.type = installed ? ENUM.INSTALL : ENUM.REMOVE;
|
operation.type = installed ? ENUM.INSTALL : ENUM.REMOVE;
|
||||||
|
|
||||||
return operation;
|
return operation;
|
||||||
|
@ -1434,12 +1406,10 @@ export default class MessageSender {
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.stickerPackOperation = packOperations;
|
syncMessage.stickerPackOperation = packOperations;
|
||||||
|
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1476,7 +1446,7 @@ export default class MessageSender {
|
||||||
);
|
);
|
||||||
|
|
||||||
return promise.then(async () => {
|
return promise.then(async () => {
|
||||||
const verified = new window.textsecure.protobuf.Verified();
|
const verified = new Proto.Verified();
|
||||||
verified.state = state;
|
verified.state = state;
|
||||||
if (destinationE164) {
|
if (destinationE164) {
|
||||||
verified.destination = destinationE164;
|
verified.destination = destinationE164;
|
||||||
|
@ -1484,18 +1454,16 @@ export default class MessageSender {
|
||||||
if (destinationUuid) {
|
if (destinationUuid) {
|
||||||
verified.destinationUuid = destinationUuid;
|
verified.destinationUuid = destinationUuid;
|
||||||
}
|
}
|
||||||
verified.identityKey = identityKey;
|
verified.identityKey = new FIXMEU8(identityKey);
|
||||||
verified.nullMessage = padding;
|
verified.nullMessage = padding;
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.verified = verified;
|
syncMessage.verified = verified;
|
||||||
|
|
||||||
const secondMessage = new window.textsecure.protobuf.Content();
|
const secondMessage = new Proto.Content();
|
||||||
secondMessage.syncMessage = syncMessage;
|
secondMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
await this.sendIndividualProto({
|
await this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid || myNumber,
|
||||||
|
@ -1515,21 +1483,19 @@ export default class MessageSender {
|
||||||
options: SendOptionsType,
|
options: SendOptionsType,
|
||||||
groupId?: string
|
groupId?: string
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendMessage({
|
return this.sendMessage({
|
||||||
messageOptions: {
|
messageOptions: {
|
||||||
recipients,
|
recipients,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
profileKey,
|
profileKey,
|
||||||
flags: window.textsecure.protobuf.DataMessage.Flags.PROFILE_KEY_UPDATE,
|
flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE,
|
||||||
...(groupId
|
...(groupId
|
||||||
? {
|
? {
|
||||||
group: {
|
group: {
|
||||||
id: groupId,
|
id: groupId,
|
||||||
type: window.textsecure.protobuf.GroupContext.Type.DELIVER,
|
type: Proto.GroupContext.Type.DELIVER,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
@ -1542,18 +1508,16 @@ export default class MessageSender {
|
||||||
|
|
||||||
async sendCallingMessage(
|
async sendCallingMessage(
|
||||||
recipientId: string,
|
recipientId: string,
|
||||||
callingMessage: CallingMessageClass,
|
callingMessage: Proto.ICallingMessage,
|
||||||
options?: SendOptionsType
|
options?: SendOptionsType
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const recipients = [recipientId];
|
const recipients = [recipientId];
|
||||||
const finalTimestamp = Date.now();
|
const finalTimestamp = Date.now();
|
||||||
|
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.callingMessage = callingMessage;
|
contentMessage.callingMessage = callingMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
await this.sendMessageProtoAndWait({
|
await this.sendMessageProtoAndWait({
|
||||||
timestamp: finalTimestamp,
|
timestamp: finalTimestamp,
|
||||||
|
@ -1583,17 +1547,14 @@ export default class MessageSender {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
const receiptMessage = new window.textsecure.protobuf.ReceiptMessage();
|
const receiptMessage = new Proto.ReceiptMessage();
|
||||||
receiptMessage.type =
|
receiptMessage.type = Proto.ReceiptMessage.Type.DELIVERY;
|
||||||
window.textsecure.protobuf.ReceiptMessage.Type.DELIVERY;
|
|
||||||
receiptMessage.timestamp = timestamps;
|
receiptMessage.timestamp = timestamps;
|
||||||
|
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.receiptMessage = receiptMessage;
|
contentMessage.receiptMessage = receiptMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: uuid || e164,
|
identifier: uuid || e164,
|
||||||
|
@ -1615,16 +1576,14 @@ export default class MessageSender {
|
||||||
timestamps: Array<number>;
|
timestamps: Array<number>;
|
||||||
options?: SendOptionsType;
|
options?: SendOptionsType;
|
||||||
}): Promise<CallbackResultType> {
|
}): Promise<CallbackResultType> {
|
||||||
const receiptMessage = new window.textsecure.protobuf.ReceiptMessage();
|
const receiptMessage = new Proto.ReceiptMessage();
|
||||||
receiptMessage.type = window.textsecure.protobuf.ReceiptMessage.Type.READ;
|
receiptMessage.type = Proto.ReceiptMessage.Type.READ;
|
||||||
receiptMessage.timestamp = timestamps;
|
receiptMessage.timestamp = timestamps;
|
||||||
|
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.receiptMessage = receiptMessage;
|
contentMessage.receiptMessage = receiptMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: senderUuid || senderE164,
|
identifier: senderUuid || senderE164,
|
||||||
|
@ -1640,10 +1599,10 @@ export default class MessageSender {
|
||||||
uuid,
|
uuid,
|
||||||
e164,
|
e164,
|
||||||
padding,
|
padding,
|
||||||
}: { uuid?: string; e164?: string; padding?: ArrayBuffer },
|
}: { uuid?: string; e164?: string; padding?: Uint8Array },
|
||||||
options?: SendOptionsType
|
options?: SendOptionsType
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const nullMessage = new window.textsecure.protobuf.NullMessage();
|
const nullMessage = new Proto.NullMessage();
|
||||||
|
|
||||||
const identifier = uuid || e164;
|
const identifier = uuid || e164;
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
|
@ -1652,12 +1611,10 @@ export default class MessageSender {
|
||||||
|
|
||||||
nullMessage.padding = padding || this.getRandomPadding();
|
nullMessage.padding = padding || this.getRandomPadding();
|
||||||
|
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
contentMessage.nullMessage = nullMessage;
|
contentMessage.nullMessage = nullMessage;
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
// We want the NullMessage to look like a normal outgoing message
|
// We want the NullMessage to look like a normal outgoing message
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
|
@ -1679,9 +1636,9 @@ export default class MessageSender {
|
||||||
CallbackResultType | void | Array<CallbackResultType | void | Array<void>>
|
CallbackResultType | void | Array<CallbackResultType | void | Array<void>>
|
||||||
> {
|
> {
|
||||||
window.log.info('resetSession: start');
|
window.log.info('resetSession: start');
|
||||||
const proto = new window.textsecure.protobuf.DataMessage();
|
const proto = new Proto.DataMessage();
|
||||||
proto.body = 'TERMINATE';
|
proto.body = 'TERMINATE';
|
||||||
proto.flags = window.textsecure.protobuf.DataMessage.Flags.END_SESSION;
|
proto.flags = Proto.DataMessage.Flags.END_SESSION;
|
||||||
proto.timestamp = timestamp;
|
proto.timestamp = timestamp;
|
||||||
|
|
||||||
const identifier = uuid || e164;
|
const identifier = uuid || e164;
|
||||||
|
@ -1691,9 +1648,7 @@ export default class MessageSender {
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
const sendToContactPromise = window.textsecure.storage.protocol
|
const sendToContactPromise = window.textsecure.storage.protocol
|
||||||
.archiveAllSessions(identifier)
|
.archiveAllSessions(identifier)
|
||||||
|
@ -1723,7 +1678,9 @@ export default class MessageSender {
|
||||||
return sendToContactPromise;
|
return sendToContactPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = proto.toArrayBuffer();
|
const buffer = typedArrayToArrayBuffer(
|
||||||
|
Proto.DataMessage.encode(proto).finish()
|
||||||
|
);
|
||||||
const sendSyncPromise = this.sendSyncMessage({
|
const sendSyncPromise = this.sendSyncMessage({
|
||||||
encodedDataMessage: buffer,
|
encodedDataMessage: buffer,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
@ -1745,9 +1702,7 @@ export default class MessageSender {
|
||||||
profileKey?: ArrayBuffer,
|
profileKey?: ArrayBuffer,
|
||||||
options?: SendOptionsType
|
options?: SendOptionsType
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendMessage({
|
return this.sendMessage({
|
||||||
messageOptions: {
|
messageOptions: {
|
||||||
|
@ -1755,8 +1710,7 @@ export default class MessageSender {
|
||||||
timestamp,
|
timestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
profileKey,
|
profileKey,
|
||||||
flags:
|
flags: Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||||
window.textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
|
||||||
},
|
},
|
||||||
contentHint: ContentHint.DEFAULT,
|
contentHint: ContentHint.DEFAULT,
|
||||||
groupId: undefined,
|
groupId: undefined,
|
||||||
|
@ -1773,9 +1727,7 @@ export default class MessageSender {
|
||||||
plaintext: PlaintextContent;
|
plaintext: PlaintextContent;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
}): Promise<CallbackResultType> {
|
}): Promise<CallbackResultType> {
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
return this.sendMessageProtoAndWait({
|
return this.sendMessageProtoAndWait({
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
@ -1799,13 +1751,17 @@ export default class MessageSender {
|
||||||
options,
|
options,
|
||||||
}: {
|
}: {
|
||||||
recipients: Array<string>;
|
recipients: Array<string>;
|
||||||
proto: ContentClass;
|
proto: Proto.Content;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
contentHint: number;
|
contentHint: number;
|
||||||
groupId: string | undefined;
|
groupId: string | undefined;
|
||||||
options?: SendOptionsType;
|
options?: SendOptionsType;
|
||||||
}): Promise<CallbackResultType> {
|
}): Promise<CallbackResultType> {
|
||||||
const dataMessage = proto.dataMessage?.toArrayBuffer();
|
const dataMessage = proto.dataMessage
|
||||||
|
? typedArrayToArrayBuffer(
|
||||||
|
Proto.DataMessage.encode(proto.dataMessage).finish()
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const myE164 = window.textsecure.storage.user.getNumber();
|
const myE164 = window.textsecure.storage.user.getNumber();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
const myUuid = window.textsecure.storage.user.getUuid();
|
||||||
|
@ -1887,14 +1843,12 @@ export default class MessageSender {
|
||||||
},
|
},
|
||||||
options?: SendOptionsType
|
options?: SendOptionsType
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const contentMessage = new window.textsecure.protobuf.Content();
|
const contentMessage = new Proto.Content();
|
||||||
|
|
||||||
const senderKeyDistributionMessage = await this.getSenderKeyDistributionMessage(
|
const senderKeyDistributionMessage = await this.getSenderKeyDistributionMessage(
|
||||||
distributionId
|
distributionId
|
||||||
);
|
);
|
||||||
contentMessage.senderKeyDistributionMessage = window.dcodeIO.ByteBuffer.wrap(
|
contentMessage.senderKeyDistributionMessage = senderKeyDistributionMessage.serialize();
|
||||||
typedArrayToArrayBuffer(senderKeyDistributionMessage.serialize())
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.sendGroupProto({
|
return this.sendGroupProto({
|
||||||
recipients: identifiers,
|
recipients: identifiers,
|
||||||
|
@ -1913,14 +1867,16 @@ export default class MessageSender {
|
||||||
groupIdentifiers: Array<string>,
|
groupIdentifiers: Array<string>,
|
||||||
options?: SendOptionsType
|
options?: SendOptionsType
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const proto = new window.textsecure.protobuf.DataMessage();
|
const proto = new Proto.Content({
|
||||||
proto.group = new window.textsecure.protobuf.GroupContext();
|
dataMessage: {
|
||||||
proto.group.id = stringToArrayBuffer(groupId);
|
group: {
|
||||||
proto.group.type = window.textsecure.protobuf.GroupContext.Type.QUIT;
|
id: Bytes.fromString(groupId),
|
||||||
|
type: Proto.GroupContext.Type.QUIT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
return this.sendGroupProto({
|
return this.sendGroupProto({
|
||||||
recipients: groupIdentifiers,
|
recipients: groupIdentifiers,
|
||||||
proto,
|
proto,
|
||||||
|
@ -1949,11 +1905,10 @@ export default class MessageSender {
|
||||||
timestamp,
|
timestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
profileKey,
|
profileKey,
|
||||||
flags:
|
flags: Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||||
window.textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
|
||||||
group: {
|
group: {
|
||||||
id: groupId,
|
id: groupId,
|
||||||
type: window.textsecure.protobuf.GroupContext.Type.DELIVER,
|
type: Proto.GroupContext.Type.DELIVER,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1967,9 +1922,7 @@ export default class MessageSender {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
return this.sendMessage({
|
return this.sendMessage({
|
||||||
messageOptions,
|
messageOptions,
|
||||||
contentHint: ContentHint.DEFAULT,
|
contentHint: ContentHint.DEFAULT,
|
||||||
|
|
|
@ -6,8 +6,9 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
import EventTarget from './EventTarget';
|
import EventTarget, { EventHandler } from './EventTarget';
|
||||||
import MessageReceiver from './MessageReceiver';
|
import MessageReceiver from './MessageReceiver';
|
||||||
|
import { ContactSyncEvent, GroupSyncEvent } from './messageReceiverEvents';
|
||||||
import MessageSender from './SendMessage';
|
import MessageSender from './SendMessage';
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
|
|
||||||
|
@ -20,9 +21,9 @@ class SyncRequestInner extends EventTarget {
|
||||||
|
|
||||||
timeout: any;
|
timeout: any;
|
||||||
|
|
||||||
oncontact: Function;
|
oncontact: (event: ContactSyncEvent) => void;
|
||||||
|
|
||||||
ongroup: Function;
|
ongroup: (event: GroupSyncEvent) => void;
|
||||||
|
|
||||||
timeoutMillis: number;
|
timeoutMillis: number;
|
||||||
|
|
||||||
|
@ -43,10 +44,10 @@ class SyncRequestInner extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.oncontact = this.onContactSyncComplete.bind(this);
|
this.oncontact = this.onContactSyncComplete.bind(this);
|
||||||
receiver.addEventListener('contactsync', this.oncontact);
|
receiver.addEventListener('contactSync', this.oncontact);
|
||||||
|
|
||||||
this.ongroup = this.onGroupSyncComplete.bind(this);
|
this.ongroup = this.onGroupSyncComplete.bind(this);
|
||||||
receiver.addEventListener('groupsync', this.ongroup);
|
receiver.addEventListener('groupSync', this.ongroup);
|
||||||
|
|
||||||
this.timeoutMillis = timeoutMillis || 60000;
|
this.timeoutMillis = timeoutMillis || 60000;
|
||||||
}
|
}
|
||||||
|
@ -126,9 +127,15 @@ class SyncRequestInner extends EventTarget {
|
||||||
export default class SyncRequest {
|
export default class SyncRequest {
|
||||||
private inner: SyncRequestInner;
|
private inner: SyncRequestInner;
|
||||||
|
|
||||||
addEventListener: (name: string, handler: Function) => void;
|
addEventListener: (
|
||||||
|
name: 'success' | 'timeout',
|
||||||
|
handler: EventHandler
|
||||||
|
) => void;
|
||||||
|
|
||||||
removeEventListener: (name: string, handler: Function) => void;
|
removeEventListener: (
|
||||||
|
name: 'success' | 'timeout',
|
||||||
|
handler: EventHandler
|
||||||
|
) => void;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
sender: MessageSender,
|
sender: MessageSender,
|
||||||
|
|
151
ts/textsecure/Types.d.ts
vendored
151
ts/textsecure/Types.d.ts
vendored
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
IdentityKeyType,
|
IdentityKeyType,
|
||||||
PreKeyType,
|
PreKeyType,
|
||||||
|
@ -56,3 +58,152 @@ export type OuterSignedPrekeyType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SessionResetsType = Record<string, number>;
|
export type SessionResetsType = Record<string, number>;
|
||||||
|
|
||||||
|
export type ProcessedEnvelope = Readonly<{
|
||||||
|
id: string;
|
||||||
|
receivedAtCounter: number;
|
||||||
|
receivedAtDate: number;
|
||||||
|
messageAgeSec: number;
|
||||||
|
|
||||||
|
// Mostly from Proto.Envelope except for null/undefined
|
||||||
|
type: Proto.Envelope.Type;
|
||||||
|
source?: string;
|
||||||
|
sourceUuid?: string;
|
||||||
|
sourceDevice?: number;
|
||||||
|
timestamp: number;
|
||||||
|
legacyMessage?: Uint8Array;
|
||||||
|
content?: Uint8Array;
|
||||||
|
serverGuid: string;
|
||||||
|
serverTimestamp: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type ProcessedAttachment = {
|
||||||
|
cdnId?: string;
|
||||||
|
cdnKey?: string;
|
||||||
|
digest?: string;
|
||||||
|
contentType?: string;
|
||||||
|
key?: string;
|
||||||
|
size?: number;
|
||||||
|
fileName?: string;
|
||||||
|
flags?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
caption?: string;
|
||||||
|
blurHash?: string;
|
||||||
|
cdnNumber?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedGroupContext = {
|
||||||
|
id: string;
|
||||||
|
type: Proto.GroupContext.Type;
|
||||||
|
name?: string;
|
||||||
|
membersE164: ReadonlyArray<string>;
|
||||||
|
avatar?: ProcessedAttachment;
|
||||||
|
|
||||||
|
// Computed fields
|
||||||
|
derivedGroupV2Id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedGroupV2Context = {
|
||||||
|
masterKey: string;
|
||||||
|
revision?: number;
|
||||||
|
groupChange?: string;
|
||||||
|
|
||||||
|
// Computed fields
|
||||||
|
id: string;
|
||||||
|
secretParams: string;
|
||||||
|
publicParams: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedQuoteAttachment = {
|
||||||
|
contentType?: string;
|
||||||
|
fileName?: string;
|
||||||
|
thumbnail?: ProcessedAttachment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedQuote = {
|
||||||
|
id?: number;
|
||||||
|
authorUuid?: string;
|
||||||
|
text?: string;
|
||||||
|
attachments: ReadonlyArray<ProcessedQuoteAttachment>;
|
||||||
|
bodyRanges: ReadonlyArray<Proto.DataMessage.IBodyRange>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedAvatar = {
|
||||||
|
avatar?: ProcessedAttachment;
|
||||||
|
isProfile: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedContact = Omit<Proto.DataMessage.IContact, 'avatar'> & {
|
||||||
|
avatar?: ProcessedAvatar;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedPreview = {
|
||||||
|
url?: string;
|
||||||
|
title?: string;
|
||||||
|
image?: ProcessedAttachment;
|
||||||
|
description?: string;
|
||||||
|
date?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedSticker = {
|
||||||
|
packId?: string;
|
||||||
|
packKey?: string;
|
||||||
|
stickerId?: number;
|
||||||
|
data?: ProcessedAttachment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedReaction = {
|
||||||
|
emoji?: string;
|
||||||
|
remove: boolean;
|
||||||
|
targetAuthorUuid?: string;
|
||||||
|
targetTimestamp?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedDelete = {
|
||||||
|
targetSentTimestamp?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedBodyRange = Proto.DataMessage.IBodyRange;
|
||||||
|
|
||||||
|
export type ProcessedGroupCallUpdate = Proto.DataMessage.IGroupCallUpdate;
|
||||||
|
|
||||||
|
export type ProcessedDataMessage = {
|
||||||
|
body?: string;
|
||||||
|
attachments: ReadonlyArray<ProcessedAttachment>;
|
||||||
|
group?: ProcessedGroupContext;
|
||||||
|
groupV2?: ProcessedGroupV2Context;
|
||||||
|
flags: number;
|
||||||
|
expireTimer: number;
|
||||||
|
profileKey?: string;
|
||||||
|
timestamp: number;
|
||||||
|
quote?: ProcessedQuote;
|
||||||
|
contact?: ReadonlyArray<ProcessedContact>;
|
||||||
|
preview?: ReadonlyArray<ProcessedPreview>;
|
||||||
|
sticker?: ProcessedSticker;
|
||||||
|
requiredProtocolVersion?: number;
|
||||||
|
isViewOnce: boolean;
|
||||||
|
reaction?: ProcessedReaction;
|
||||||
|
delete?: ProcessedDelete;
|
||||||
|
bodyRanges?: ReadonlyArray<ProcessedBodyRange>;
|
||||||
|
groupCallUpdate?: ProcessedGroupCallUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedUnidentifiedDeliveryStatus = Omit<
|
||||||
|
Proto.SyncMessage.Sent.IUnidentifiedDeliveryStatus,
|
||||||
|
'destinationUuid'
|
||||||
|
> & {
|
||||||
|
destinationUuid?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedSent = Omit<
|
||||||
|
Proto.SyncMessage.ISent,
|
||||||
|
'destinationId' | 'unidentifiedStatus'
|
||||||
|
> & {
|
||||||
|
destinationId?: string;
|
||||||
|
unidentifiedStatus?: Array<ProcessedUnidentifiedDeliveryStatus>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessedSyncMessage = Omit<Proto.ISyncMessage, 'sent'> & {
|
||||||
|
sent?: ProcessedSent;
|
||||||
|
};
|
||||||
|
|
|
@ -33,7 +33,7 @@ import { Long } from '../window.d';
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
import { getUserAgent } from '../util/getUserAgent';
|
import { getUserAgent } from '../util/getUserAgent';
|
||||||
import { toWebSafeBase64 } from '../util/webSafeBase64';
|
import { toWebSafeBase64 } from '../util/webSafeBase64';
|
||||||
import { isPackIdValid, redactPackId } from '../../js/modules/stickers';
|
import { isPackIdValid, redactPackId } from '../types/Stickers';
|
||||||
import {
|
import {
|
||||||
arrayBufferToBase64,
|
arrayBufferToBase64,
|
||||||
base64ToArrayBuffer,
|
base64ToArrayBuffer,
|
||||||
|
@ -53,7 +53,6 @@ import { calculateAgreement, generateKeyPair } from '../Curve';
|
||||||
import * as linkPreviewFetch from '../linkPreviews/linkPreviewFetch';
|
import * as linkPreviewFetch from '../linkPreviews/linkPreviewFetch';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AvatarUploadAttributesClass,
|
|
||||||
StorageServiceCallOptionsType,
|
StorageServiceCallOptionsType,
|
||||||
StorageServiceCredentials,
|
StorageServiceCredentials,
|
||||||
} from '../textsecure.d';
|
} from '../textsecure.d';
|
||||||
|
@ -2161,7 +2160,7 @@ export function initialize({
|
||||||
return Proto.GroupExternalCredential.decode(new FIXMEU8(response));
|
return Proto.GroupExternalCredential.decode(new FIXMEU8(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyAttributes(attributes: AvatarUploadAttributesClass) {
|
function verifyAttributes(attributes: Proto.IAvatarUploadAttributes) {
|
||||||
const {
|
const {
|
||||||
key,
|
key,
|
||||||
credential,
|
credential,
|
||||||
|
@ -2213,8 +2212,8 @@ export function initialize({
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
host: storageUrl,
|
host: storageUrl,
|
||||||
});
|
});
|
||||||
const attributes = window.textsecure.protobuf.AvatarUploadAttributes.decode(
|
const attributes = Proto.AvatarUploadAttributes.decode(
|
||||||
response
|
new FIXMEU8(response)
|
||||||
);
|
);
|
||||||
|
|
||||||
const verified = verifyAttributes(attributes);
|
const verified = verifyAttributes(attributes);
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
import { connection as WebSocket, IMessage } from 'websocket';
|
import { connection as WebSocket, IMessage } from 'websocket';
|
||||||
|
|
||||||
import EventTarget from './EventTarget';
|
import EventTarget, { EventHandler } from './EventTarget';
|
||||||
|
|
||||||
import { dropNull } from '../util/dropNull';
|
import { dropNull } from '../util/dropNull';
|
||||||
import { isOlderThan } from '../util/timestamp';
|
import { isOlderThan } from '../util/timestamp';
|
||||||
|
@ -120,6 +120,12 @@ export type WebSocketResourceOptions = {
|
||||||
keepalive?: KeepAliveOptionsType | true;
|
keepalive?: KeepAliveOptionsType | true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class CloseEvent extends Event {
|
||||||
|
constructor(public readonly code: number, public readonly reason: string) {
|
||||||
|
super('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default class WebSocketResource extends EventTarget {
|
export default class WebSocketResource extends EventTarget {
|
||||||
private outgoingId = 1;
|
private outgoingId = 1;
|
||||||
|
|
||||||
|
@ -159,6 +165,15 @@ export default class WebSocketResource extends EventTarget {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addEventListener(
|
||||||
|
name: 'close',
|
||||||
|
handler: (ev: CloseEvent) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
public addEventListener(name: string, handler: EventHandler): void {
|
||||||
|
return super.addEventListener(name, handler);
|
||||||
|
}
|
||||||
|
|
||||||
public sendRequest(
|
public sendRequest(
|
||||||
options: OutgoingWebSocketRequestOptions
|
options: OutgoingWebSocketRequestOptions
|
||||||
): OutgoingWebSocketRequest {
|
): OutgoingWebSocketRequest {
|
||||||
|
@ -204,10 +219,7 @@ export default class WebSocketResource extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.log.warn('Dispatching our own socket close event');
|
window.log.warn('Dispatching our own socket close event');
|
||||||
const ev = new Event('close');
|
this.dispatchEvent(new CloseEvent(code, reason || 'normal'));
|
||||||
ev.code = code;
|
|
||||||
ev.reason = reason;
|
|
||||||
this.dispatchEvent(ev);
|
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
370
ts/textsecure/messageReceiverEvents.ts
Normal file
370
ts/textsecure/messageReceiverEvents.ts
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
import { ProcessedDataMessage, ProcessedSent } from './Types.d';
|
||||||
|
import type {
|
||||||
|
ModifiedContactDetails,
|
||||||
|
ModifiedGroupDetails,
|
||||||
|
} from './ContactsParser';
|
||||||
|
|
||||||
|
export class ReconnectEvent extends Event {
|
||||||
|
constructor() {
|
||||||
|
super('reconnect');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EmptyEvent extends Event {
|
||||||
|
constructor() {
|
||||||
|
super('empty');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProgressEvent extends Event {
|
||||||
|
public readonly count: number;
|
||||||
|
|
||||||
|
constructor({ count }: { count: number }) {
|
||||||
|
super('progress');
|
||||||
|
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TypingEventData = Readonly<{
|
||||||
|
typingMessage: Proto.ITypingMessage;
|
||||||
|
timestamp: number;
|
||||||
|
started: boolean;
|
||||||
|
stopped: boolean;
|
||||||
|
groupId?: string;
|
||||||
|
groupV2Id?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type TypingEventConfig = {
|
||||||
|
sender?: string;
|
||||||
|
senderUuid?: string;
|
||||||
|
senderDevice: number;
|
||||||
|
typing: TypingEventData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class TypingEvent extends Event {
|
||||||
|
public readonly sender?: string;
|
||||||
|
|
||||||
|
public readonly senderUuid?: string;
|
||||||
|
|
||||||
|
public readonly senderDevice: number;
|
||||||
|
|
||||||
|
public readonly typing: TypingEventData;
|
||||||
|
|
||||||
|
constructor({ sender, senderUuid, senderDevice, typing }: TypingEventConfig) {
|
||||||
|
super('typing');
|
||||||
|
|
||||||
|
this.sender = sender;
|
||||||
|
this.senderUuid = senderUuid;
|
||||||
|
this.senderDevice = senderDevice;
|
||||||
|
this.typing = typing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorEvent extends Event {
|
||||||
|
constructor(public readonly error: Error) {
|
||||||
|
super('error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DecryptionErrorEventData = Readonly<{
|
||||||
|
cipherTextBytes?: ArrayBuffer;
|
||||||
|
cipherTextType?: number;
|
||||||
|
contentHint?: number;
|
||||||
|
groupId?: string;
|
||||||
|
receivedAtCounter: number;
|
||||||
|
receivedAtDate: number;
|
||||||
|
senderDevice: number;
|
||||||
|
senderUuid: string;
|
||||||
|
timestamp: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class DecryptionErrorEvent extends Event {
|
||||||
|
constructor(public readonly decryptionError: DecryptionErrorEventData) {
|
||||||
|
super('decryption-error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RetryRequestEventData = Readonly<{
|
||||||
|
groupId?: string;
|
||||||
|
requesterUuid: string;
|
||||||
|
requesterDevice: number;
|
||||||
|
senderDevice: number;
|
||||||
|
sentAt: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class RetryRequestEvent extends Event {
|
||||||
|
constructor(public readonly retryRequest: RetryRequestEventData) {
|
||||||
|
super('retry-request');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ContactEvent extends Event {
|
||||||
|
constructor(public readonly contactDetails: ModifiedContactDetails) {
|
||||||
|
super('contact');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ContactSyncEvent extends Event {
|
||||||
|
constructor() {
|
||||||
|
super('contactSync');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupEvent extends Event {
|
||||||
|
constructor(public readonly groupDetails: ModifiedGroupDetails) {
|
||||||
|
super('group');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupSyncEvent extends Event {
|
||||||
|
constructor() {
|
||||||
|
super('groupSync');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Confirmable events below
|
||||||
|
//
|
||||||
|
|
||||||
|
export type ConfirmCallback = () => void;
|
||||||
|
|
||||||
|
export class ConfirmableEvent extends Event {
|
||||||
|
constructor(type: string, public readonly confirm: ConfirmCallback) {
|
||||||
|
super(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeliveryEventData = Readonly<{
|
||||||
|
timestamp: number;
|
||||||
|
envelopeTimestamp?: number;
|
||||||
|
source?: string;
|
||||||
|
sourceUuid?: string;
|
||||||
|
sourceDevice?: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class DeliveryEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly deliveryReceipt: DeliveryEventData,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('delivery', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SentEventData = Readonly<{
|
||||||
|
destination?: string;
|
||||||
|
destinationUuid?: string;
|
||||||
|
timestamp?: number;
|
||||||
|
serverTimestamp?: number;
|
||||||
|
device?: number;
|
||||||
|
unidentifiedStatus: ProcessedSent['unidentifiedStatus'];
|
||||||
|
message: ProcessedDataMessage;
|
||||||
|
isRecipientUpdate: boolean;
|
||||||
|
receivedAtCounter: number;
|
||||||
|
receivedAtDate: number;
|
||||||
|
expirationStartTimestamp?: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class SentEvent extends ConfirmableEvent {
|
||||||
|
constructor(public readonly data: SentEventData, confirm: ConfirmCallback) {
|
||||||
|
super('sent', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProfileKeyUpdateData = Readonly<{
|
||||||
|
source?: string;
|
||||||
|
sourceUuid?: string;
|
||||||
|
profileKey: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class ProfileKeyUpdateEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly data: ProfileKeyUpdateData,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('profileKeyUpdate', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MessageEventData = Readonly<{
|
||||||
|
source?: string;
|
||||||
|
sourceUuid?: string;
|
||||||
|
sourceDevice?: number;
|
||||||
|
timestamp: number;
|
||||||
|
serverGuid?: string;
|
||||||
|
serverTimestamp?: number;
|
||||||
|
unidentifiedDeliveryReceived: boolean;
|
||||||
|
message: ProcessedDataMessage;
|
||||||
|
receivedAtCounter: number;
|
||||||
|
receivedAtDate: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class MessageEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly data: MessageEventData,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('message', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ReadEventData = Readonly<{
|
||||||
|
timestamp: number;
|
||||||
|
envelopeTimestamp: number;
|
||||||
|
source?: string;
|
||||||
|
sourceUuid?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class ReadEvent extends ConfirmableEvent {
|
||||||
|
constructor(public readonly read: ReadEventData, confirm: ConfirmCallback) {
|
||||||
|
super('read', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConfigurationEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly configuration: Proto.SyncMessage.IConfiguration,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('configuration', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ViewSyncOptions = {
|
||||||
|
source?: string;
|
||||||
|
sourceUuid?: string;
|
||||||
|
timestamp?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ViewSyncEvent extends ConfirmableEvent {
|
||||||
|
public readonly source?: string;
|
||||||
|
|
||||||
|
public readonly sourceUuid?: string;
|
||||||
|
|
||||||
|
public readonly timestamp?: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
{ source, sourceUuid, timestamp }: ViewSyncOptions,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('viewSync', confirm);
|
||||||
|
|
||||||
|
this.source = source;
|
||||||
|
this.sourceUuid = sourceUuid;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MessageRequestResponseOptions = {
|
||||||
|
threadE164?: string;
|
||||||
|
threadUuid?: string;
|
||||||
|
messageRequestResponseType: Proto.SyncMessage.IMessageRequestResponse['type'];
|
||||||
|
groupId?: string;
|
||||||
|
groupV2Id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class MessageRequestResponseEvent extends ConfirmableEvent {
|
||||||
|
public readonly threadE164?: string;
|
||||||
|
|
||||||
|
public readonly threadUuid?: string;
|
||||||
|
|
||||||
|
public readonly messageRequestResponseType?: MessageRequestResponseOptions['messageRequestResponseType'];
|
||||||
|
|
||||||
|
public readonly groupId?: string;
|
||||||
|
|
||||||
|
public readonly groupV2Id?: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
{
|
||||||
|
threadE164,
|
||||||
|
threadUuid,
|
||||||
|
messageRequestResponseType,
|
||||||
|
groupId,
|
||||||
|
groupV2Id,
|
||||||
|
}: MessageRequestResponseOptions,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('messageRequestResponse', confirm);
|
||||||
|
|
||||||
|
this.threadE164 = threadE164;
|
||||||
|
this.threadUuid = threadUuid;
|
||||||
|
this.messageRequestResponseType = messageRequestResponseType;
|
||||||
|
this.groupId = groupId;
|
||||||
|
this.groupV2Id = groupV2Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FetchLatestEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly eventType: Proto.SyncMessage.IFetchLatest['type'],
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('fetchLatest', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KeysEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly storageServiceKey: ArrayBuffer,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('keys', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StickerPackEventData = Readonly<{
|
||||||
|
id?: string;
|
||||||
|
key?: string;
|
||||||
|
isInstall: boolean;
|
||||||
|
isRemove: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class StickerPackEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly stickerPacks: ReadonlyArray<StickerPackEventData>,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('sticker-pack', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VerifiedEventData = Readonly<{
|
||||||
|
state: Proto.IVerified['state'];
|
||||||
|
destination?: string;
|
||||||
|
destinationUuid?: string;
|
||||||
|
identityKey?: ArrayBuffer;
|
||||||
|
|
||||||
|
// Used in `ts/background.ts`
|
||||||
|
viaContactSync?: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class VerifiedEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly verified: VerifiedEventData,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('verified', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ReadSyncEventData = Readonly<{
|
||||||
|
timestamp?: number;
|
||||||
|
envelopeTimestamp: number;
|
||||||
|
sender?: string;
|
||||||
|
senderUuid?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class ReadSyncEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly read: ReadSyncEventData,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('readSync', confirm);
|
||||||
|
}
|
||||||
|
}
|
352
ts/textsecure/processDataMessage.ts
Normal file
352
ts/textsecure/processDataMessage.ts
Normal file
|
@ -0,0 +1,352 @@
|
||||||
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import Long from 'long';
|
||||||
|
|
||||||
|
import { assert, strictAssert } from '../util/assert';
|
||||||
|
import { dropNull, shallowDropNull } from '../util/dropNull';
|
||||||
|
import { normalizeNumber } from '../util/normalizeNumber';
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
import { deriveGroupFields } from '../groups';
|
||||||
|
import * as Bytes from '../Bytes';
|
||||||
|
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ProcessedAttachment,
|
||||||
|
ProcessedDataMessage,
|
||||||
|
ProcessedGroupContext,
|
||||||
|
ProcessedGroupV2Context,
|
||||||
|
ProcessedQuote,
|
||||||
|
ProcessedContact,
|
||||||
|
ProcessedPreview,
|
||||||
|
ProcessedSticker,
|
||||||
|
ProcessedReaction,
|
||||||
|
ProcessedDelete,
|
||||||
|
} from './Types.d';
|
||||||
|
|
||||||
|
// TODO: remove once we move away from ArrayBuffers
|
||||||
|
const FIXMEU8 = Uint8Array;
|
||||||
|
|
||||||
|
const FLAGS = Proto.DataMessage.Flags;
|
||||||
|
export const ATTACHMENT_MAX = 32;
|
||||||
|
|
||||||
|
export function processAttachment(
|
||||||
|
attachment: Proto.IAttachmentPointer
|
||||||
|
): ProcessedAttachment;
|
||||||
|
export function processAttachment(
|
||||||
|
attachment?: Proto.IAttachmentPointer | null
|
||||||
|
): ProcessedAttachment | undefined;
|
||||||
|
|
||||||
|
export function processAttachment(
|
||||||
|
attachment?: Proto.IAttachmentPointer | null
|
||||||
|
): ProcessedAttachment | undefined {
|
||||||
|
if (!attachment) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...shallowDropNull(attachment),
|
||||||
|
|
||||||
|
cdnId: attachment.cdnId ? attachment.cdnId.toString() : undefined,
|
||||||
|
key: attachment.key ? Bytes.toBase64(attachment.key) : undefined,
|
||||||
|
digest: attachment.digest ? Bytes.toBase64(attachment.digest) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processGroupContext(
|
||||||
|
group?: Proto.IGroupContext | null
|
||||||
|
): Promise<ProcessedGroupContext | undefined> {
|
||||||
|
if (!group) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
strictAssert(group.id, 'group context without id');
|
||||||
|
strictAssert(
|
||||||
|
group.type !== undefined && group.type !== null,
|
||||||
|
'group context without type'
|
||||||
|
);
|
||||||
|
|
||||||
|
const masterKey = await deriveMasterKeyFromGroupV1(
|
||||||
|
typedArrayToArrayBuffer(group.id)
|
||||||
|
);
|
||||||
|
const data = deriveGroupFields(new FIXMEU8(masterKey));
|
||||||
|
|
||||||
|
const derivedGroupV2Id = Bytes.toBase64(data.id);
|
||||||
|
|
||||||
|
const result: ProcessedGroupContext = {
|
||||||
|
id: Bytes.toBinary(group.id),
|
||||||
|
type: group.type,
|
||||||
|
name: dropNull(group.name),
|
||||||
|
membersE164: group.membersE164 ?? [],
|
||||||
|
avatar: processAttachment(group.avatar),
|
||||||
|
derivedGroupV2Id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.type === Proto.GroupContext.Type.DELIVER) {
|
||||||
|
result.name = undefined;
|
||||||
|
result.membersE164 = [];
|
||||||
|
result.avatar = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processGroupV2Context(
|
||||||
|
groupV2?: Proto.IGroupContextV2 | null
|
||||||
|
): ProcessedGroupV2Context | undefined {
|
||||||
|
if (!groupV2) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
strictAssert(groupV2.masterKey, 'groupV2 context without masterKey');
|
||||||
|
const data = deriveGroupFields(groupV2.masterKey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
masterKey: Bytes.toBase64(groupV2.masterKey),
|
||||||
|
revision: dropNull(groupV2.revision),
|
||||||
|
groupChange: groupV2.groupChange
|
||||||
|
? Bytes.toBase64(groupV2.groupChange)
|
||||||
|
: undefined,
|
||||||
|
id: Bytes.toBase64(data.id),
|
||||||
|
secretParams: Bytes.toBase64(data.secretParams),
|
||||||
|
publicParams: Bytes.toBase64(data.publicParams),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processQuote(
|
||||||
|
quote?: Proto.DataMessage.IQuote | null
|
||||||
|
): ProcessedQuote | undefined {
|
||||||
|
if (!quote) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: normalizeNumber(dropNull(quote.id)),
|
||||||
|
authorUuid: dropNull(quote.authorUuid),
|
||||||
|
text: dropNull(quote.text),
|
||||||
|
attachments: (quote.attachments ?? []).map(attachment => {
|
||||||
|
return {
|
||||||
|
contentType: dropNull(attachment.contentType),
|
||||||
|
fileName: dropNull(attachment.fileName),
|
||||||
|
thumbnail: processAttachment(attachment.thumbnail),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
bodyRanges: quote.bodyRanges ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processContact(
|
||||||
|
contact?: ReadonlyArray<Proto.DataMessage.IContact> | null
|
||||||
|
): ReadonlyArray<ProcessedContact> | undefined {
|
||||||
|
if (!contact) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return contact.map(item => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
avatar: item.avatar
|
||||||
|
? {
|
||||||
|
avatar: processAttachment(item.avatar.avatar),
|
||||||
|
isProfile: Boolean(item.avatar.isProfile),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLinkPreviewDateValid(value: unknown): value is number {
|
||||||
|
return (
|
||||||
|
typeof value === 'number' &&
|
||||||
|
!Number.isNaN(value) &&
|
||||||
|
Number.isFinite(value) &&
|
||||||
|
value > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanLinkPreviewDate(
|
||||||
|
value?: Long | number | null
|
||||||
|
): number | undefined {
|
||||||
|
const result = normalizeNumber(value ?? undefined);
|
||||||
|
return isLinkPreviewDateValid(result) ? result : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processPreview(
|
||||||
|
preview?: ReadonlyArray<Proto.DataMessage.IPreview> | null
|
||||||
|
): ReadonlyArray<ProcessedPreview> | undefined {
|
||||||
|
if (!preview) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return preview.map(item => {
|
||||||
|
return {
|
||||||
|
url: dropNull(item.url),
|
||||||
|
title: dropNull(item.title),
|
||||||
|
image: item.image ? processAttachment(item.image) : undefined,
|
||||||
|
description: dropNull(item.description),
|
||||||
|
date: cleanLinkPreviewDate(item.date),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processSticker(
|
||||||
|
sticker?: Proto.DataMessage.ISticker | null
|
||||||
|
): ProcessedSticker | undefined {
|
||||||
|
if (!sticker) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
packId: sticker.packId ? Bytes.toHex(sticker.packId) : undefined,
|
||||||
|
packKey: sticker.packKey ? Bytes.toBase64(sticker.packKey) : undefined,
|
||||||
|
stickerId: normalizeNumber(dropNull(sticker.stickerId)),
|
||||||
|
data: processAttachment(sticker.data),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processReaction(
|
||||||
|
reaction?: Proto.DataMessage.IReaction | null
|
||||||
|
): ProcessedReaction | undefined {
|
||||||
|
if (!reaction) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
emoji: dropNull(reaction.emoji),
|
||||||
|
remove: Boolean(reaction.remove),
|
||||||
|
targetAuthorUuid: dropNull(reaction.targetAuthorUuid),
|
||||||
|
targetTimestamp: normalizeNumber(dropNull(reaction.targetTimestamp)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processDelete(
|
||||||
|
del?: Proto.DataMessage.IDelete | null
|
||||||
|
): ProcessedDelete | undefined {
|
||||||
|
if (!del) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
targetSentTimestamp: normalizeNumber(dropNull(del.targetSentTimestamp)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function processDataMessage(
|
||||||
|
message: Proto.IDataMessage,
|
||||||
|
envelopeTimestamp: number
|
||||||
|
): Promise<ProcessedDataMessage> {
|
||||||
|
/* eslint-disable no-bitwise */
|
||||||
|
|
||||||
|
// Now that its decrypted, validate the message and clean it up for consumer
|
||||||
|
// processing
|
||||||
|
// Note that messages may (generally) only perform one action and we ignore remaining
|
||||||
|
// fields after the first action.
|
||||||
|
|
||||||
|
if (!message.timestamp) {
|
||||||
|
throw new Error('Missing timestamp on dataMessage');
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = normalizeNumber(message.timestamp);
|
||||||
|
|
||||||
|
if (envelopeTimestamp !== timestamp) {
|
||||||
|
throw new Error(
|
||||||
|
`Timestamp ${timestamp} in DataMessage did not ` +
|
||||||
|
`match envelope timestamp ${envelopeTimestamp}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: ProcessedDataMessage = {
|
||||||
|
body: dropNull(message.body),
|
||||||
|
attachments: (
|
||||||
|
message.attachments ?? []
|
||||||
|
).map((attachment: Proto.IAttachmentPointer) =>
|
||||||
|
processAttachment(attachment)
|
||||||
|
),
|
||||||
|
group: await processGroupContext(message.group),
|
||||||
|
groupV2: processGroupV2Context(message.groupV2),
|
||||||
|
flags: message.flags ?? 0,
|
||||||
|
expireTimer: message.expireTimer ?? 0,
|
||||||
|
profileKey: message.profileKey
|
||||||
|
? Bytes.toBase64(message.profileKey)
|
||||||
|
: undefined,
|
||||||
|
timestamp,
|
||||||
|
quote: processQuote(message.quote),
|
||||||
|
contact: processContact(message.contact),
|
||||||
|
preview: processPreview(message.preview),
|
||||||
|
sticker: processSticker(message.sticker),
|
||||||
|
requiredProtocolVersion: normalizeNumber(
|
||||||
|
dropNull(message.requiredProtocolVersion)
|
||||||
|
),
|
||||||
|
isViewOnce: Boolean(message.isViewOnce),
|
||||||
|
reaction: processReaction(message.reaction),
|
||||||
|
delete: processDelete(message.delete),
|
||||||
|
bodyRanges: message.bodyRanges ?? [],
|
||||||
|
groupCallUpdate: dropNull(message.groupCallUpdate),
|
||||||
|
};
|
||||||
|
|
||||||
|
const isEndSession = Boolean(result.flags & FLAGS.END_SESSION);
|
||||||
|
const isExpirationTimerUpdate = Boolean(
|
||||||
|
result.flags & FLAGS.EXPIRATION_TIMER_UPDATE
|
||||||
|
);
|
||||||
|
const isProfileKeyUpdate = Boolean(result.flags & FLAGS.PROFILE_KEY_UPDATE);
|
||||||
|
// The following assertion codifies an assumption: 0 or 1 flags are set, but never
|
||||||
|
// more. This assumption is fine as of this writing, but may not always be.
|
||||||
|
const flagCount = [
|
||||||
|
isEndSession,
|
||||||
|
isExpirationTimerUpdate,
|
||||||
|
isProfileKeyUpdate,
|
||||||
|
].filter(Boolean).length;
|
||||||
|
assert(
|
||||||
|
flagCount <= 1,
|
||||||
|
`Expected exactly <=1 flags to be set, but got ${flagCount}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isEndSession) {
|
||||||
|
result.body = undefined;
|
||||||
|
result.attachments = [];
|
||||||
|
result.group = undefined;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExpirationTimerUpdate) {
|
||||||
|
result.body = undefined;
|
||||||
|
result.attachments = [];
|
||||||
|
} else if (isProfileKeyUpdate) {
|
||||||
|
result.body = undefined;
|
||||||
|
result.attachments = [];
|
||||||
|
} else if (result.flags !== 0) {
|
||||||
|
throw new Error(`Unknown flags in message: ${result.flags}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.group) {
|
||||||
|
switch (result.group.type) {
|
||||||
|
case Proto.GroupContext.Type.UPDATE:
|
||||||
|
result.body = undefined;
|
||||||
|
result.attachments = [];
|
||||||
|
break;
|
||||||
|
case Proto.GroupContext.Type.QUIT:
|
||||||
|
result.body = undefined;
|
||||||
|
result.attachments = [];
|
||||||
|
break;
|
||||||
|
case Proto.GroupContext.Type.DELIVER:
|
||||||
|
// Cleaned up in `processGroupContext`
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
const err = new Error(
|
||||||
|
`Unknown group message type: ${result.group.type}`
|
||||||
|
);
|
||||||
|
err.warn = true;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachmentCount = result.attachments.length;
|
||||||
|
if (attachmentCount > ATTACHMENT_MAX) {
|
||||||
|
throw new Error(
|
||||||
|
`Too many attachments: ${attachmentCount} included in one message, ` +
|
||||||
|
`max is ${ATTACHMENT_MAX}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
60
ts/textsecure/processSyncMessage.ts
Normal file
60
ts/textsecure/processSyncMessage.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
import { normalizeUuid } from '../util/normalizeUuid';
|
||||||
|
import {
|
||||||
|
ProcessedUnidentifiedDeliveryStatus,
|
||||||
|
ProcessedSent,
|
||||||
|
ProcessedSyncMessage,
|
||||||
|
} from './Types.d';
|
||||||
|
|
||||||
|
import UnidentifiedDeliveryStatus = Proto.SyncMessage.Sent.IUnidentifiedDeliveryStatus;
|
||||||
|
|
||||||
|
function processUnidentifiedDeliveryStatus(
|
||||||
|
status: UnidentifiedDeliveryStatus
|
||||||
|
): ProcessedUnidentifiedDeliveryStatus {
|
||||||
|
const { destinationUuid } = status;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...status,
|
||||||
|
|
||||||
|
destinationUuid: destinationUuid
|
||||||
|
? normalizeUuid(
|
||||||
|
destinationUuid,
|
||||||
|
'syncMessage.sent.unidentifiedStatus.destinationUuid'
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function processSent(
|
||||||
|
sent?: Proto.SyncMessage.ISent | null
|
||||||
|
): ProcessedSent | undefined {
|
||||||
|
if (!sent) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { destinationUuid, unidentifiedStatus } = sent;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...sent,
|
||||||
|
|
||||||
|
destinationUuid: destinationUuid
|
||||||
|
? normalizeUuid(destinationUuid, 'syncMessage.sent.destinationUuid')
|
||||||
|
: undefined,
|
||||||
|
|
||||||
|
unidentifiedStatus: unidentifiedStatus
|
||||||
|
? unidentifiedStatus.map(processUnidentifiedDeliveryStatus)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processSyncMessage(
|
||||||
|
syncMessage: Proto.ISyncMessage
|
||||||
|
): ProcessedSyncMessage {
|
||||||
|
return {
|
||||||
|
...syncMessage,
|
||||||
|
sent: processSent(syncMessage.sent),
|
||||||
|
};
|
||||||
|
}
|
|
@ -51,6 +51,9 @@ export type AttachmentType = {
|
||||||
cdnNumber?: number;
|
cdnNumber?: number;
|
||||||
cdnId?: string;
|
cdnId?: string;
|
||||||
cdnKey?: string;
|
cdnKey?: string;
|
||||||
|
|
||||||
|
/** Legacy field. Used only for downloading old attachments */
|
||||||
|
id?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type BaseAttachmentDraftType = {
|
type BaseAttachmentDraftType = {
|
||||||
|
|
|
@ -1,17 +1,50 @@
|
||||||
// Copyright 2019-2021 Signal Messenger, LLC
|
// Copyright 2019-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
|
||||||
/* global
|
import { isNumber, pick, reject, groupBy, values } from 'lodash';
|
||||||
textsecure,
|
import pMap from 'p-map';
|
||||||
Signal,
|
import Queue from 'p-queue';
|
||||||
log,
|
|
||||||
navigator,
|
|
||||||
reduxStore,
|
|
||||||
reduxActions,
|
|
||||||
URLSearchParams
|
|
||||||
*/
|
|
||||||
|
|
||||||
const BLESSED_PACKS = {
|
import { strictAssert } from '../util/assert';
|
||||||
|
import { dropNull } from '../util/dropNull';
|
||||||
|
import { makeLookup } from '../util/makeLookup';
|
||||||
|
import { maybeParseUrl } from '../util/url';
|
||||||
|
import { base64ToArrayBuffer, deriveStickerPackKey } from '../Crypto';
|
||||||
|
import type {
|
||||||
|
StickerType,
|
||||||
|
StickerPackType,
|
||||||
|
StickerPackStatusType,
|
||||||
|
} from '../sql/Interface';
|
||||||
|
import Data from '../sql/Client';
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
|
export type RecentStickerType = Readonly<{
|
||||||
|
stickerId: number;
|
||||||
|
packId: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type BlessedType = Pick<StickerPackType, 'key' | 'status'>;
|
||||||
|
|
||||||
|
export type InitialState = {
|
||||||
|
packs: Record<string, StickerPackType>;
|
||||||
|
recentStickers: Array<RecentStickerType>;
|
||||||
|
blessedPacks: Record<string, boolean>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DownloadMap = Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
status?: StickerPackStatusType;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
// TODO: remove once we move away from ArrayBuffers
|
||||||
|
const FIXMEU8 = Uint8Array;
|
||||||
|
|
||||||
|
export const BLESSED_PACKS: Record<string, BlessedType> = {
|
||||||
'9acc9e8aba563d26a4994e69263e3b25': {
|
'9acc9e8aba563d26a4994e69263e3b25': {
|
||||||
key: 'Wm3/OUjCjvubeq+T7MN1xp/DFueAd+0mhnoU0QoPahI=',
|
key: 'Wm3/OUjCjvubeq+T7MN1xp/DFueAd+0mhnoU0QoPahI=',
|
||||||
status: 'downloaded',
|
status: 'downloaded',
|
||||||
|
@ -30,104 +63,81 @@ const BLESSED_PACKS = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const VALID_PACK_ID_REGEXP = /^[0-9a-f]{32}$/i;
|
const STICKER_PACK_DEFAULTS: StickerPackType = {
|
||||||
|
id: '',
|
||||||
|
key: '',
|
||||||
|
|
||||||
const { isNumber, pick, reject, groupBy, values } = require('lodash');
|
author: '',
|
||||||
const pMap = require('p-map');
|
coverStickerId: 0,
|
||||||
const Queue = require('p-queue').default;
|
createdAt: 0,
|
||||||
|
downloadAttempts: 0,
|
||||||
const { makeLookup } = require('../../ts/util/makeLookup');
|
status: 'ephemeral',
|
||||||
const { maybeParseUrl } = require('../../ts/util/url');
|
stickerCount: 0,
|
||||||
const {
|
stickers: {},
|
||||||
base64ToArrayBuffer,
|
title: '',
|
||||||
deriveStickerPackKey,
|
|
||||||
} = require('../../ts/Crypto');
|
|
||||||
const {
|
|
||||||
addStickerPackReference,
|
|
||||||
createOrUpdateSticker,
|
|
||||||
createOrUpdateStickerPack,
|
|
||||||
deleteStickerPack,
|
|
||||||
deleteStickerPackReference,
|
|
||||||
getAllStickerPacks,
|
|
||||||
getAllStickers,
|
|
||||||
getRecentStickers,
|
|
||||||
updateStickerPackStatus,
|
|
||||||
} = require('../../ts/sql/Client').default;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
BLESSED_PACKS,
|
|
||||||
copyStickerToAttachments,
|
|
||||||
deletePack,
|
|
||||||
deletePackReference,
|
|
||||||
downloadStickerPack,
|
|
||||||
downloadEphemeralPack,
|
|
||||||
getDataFromLink,
|
|
||||||
getInitialState,
|
|
||||||
getInstalledStickerPacks,
|
|
||||||
getSticker,
|
|
||||||
getStickerPack,
|
|
||||||
getStickerPackStatus,
|
|
||||||
load,
|
|
||||||
maybeDeletePack,
|
|
||||||
downloadQueuedPacks,
|
|
||||||
isPackIdValid,
|
|
||||||
redactPackId,
|
|
||||||
removeEphemeralPack,
|
|
||||||
savePackMetadata,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let initialState = null;
|
const VALID_PACK_ID_REGEXP = /^[0-9a-f]{32}$/i;
|
||||||
let packsToDownload = null;
|
|
||||||
|
let initialState: InitialState | undefined;
|
||||||
|
let packsToDownload: DownloadMap | undefined;
|
||||||
const downloadQueue = new Queue({ concurrency: 1, timeout: 1000 * 60 * 2 });
|
const downloadQueue = new Queue({ concurrency: 1, timeout: 1000 * 60 * 2 });
|
||||||
|
|
||||||
async function load() {
|
export async function load(): Promise<void> {
|
||||||
const [packs, recentStickers] = await Promise.all([
|
const [packs, recentStickers] = await Promise.all([
|
||||||
getPacksForRedux(),
|
getPacksForRedux(),
|
||||||
getRecentStickersForRedux(),
|
getRecentStickersForRedux(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const blessedPacks: Record<string, boolean> = Object.create(null);
|
||||||
|
for (const key of Object.keys(BLESSED_PACKS)) {
|
||||||
|
blessedPacks[key] = true;
|
||||||
|
}
|
||||||
|
|
||||||
initialState = {
|
initialState = {
|
||||||
packs,
|
packs,
|
||||||
recentStickers,
|
recentStickers,
|
||||||
blessedPacks: BLESSED_PACKS,
|
blessedPacks,
|
||||||
};
|
};
|
||||||
|
|
||||||
packsToDownload = capturePacksToDownload(packs);
|
packsToDownload = capturePacksToDownload(packs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDataFromLink(link) {
|
export function getDataFromLink(
|
||||||
|
link: string
|
||||||
|
): undefined | { id: string; key: string } {
|
||||||
const url = maybeParseUrl(link);
|
const url = maybeParseUrl(link);
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hash } = url;
|
const { hash } = url;
|
||||||
if (!hash) {
|
if (!hash) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let params;
|
let params;
|
||||||
try {
|
try {
|
||||||
params = new URLSearchParams(hash.slice(1));
|
params = new URLSearchParams(hash.slice(1));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = params.get('pack_id');
|
const id = params.get('pack_id');
|
||||||
if (!isPackIdValid(id)) {
|
if (!isPackIdValid(id)) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = params.get('pack_key');
|
const key = params.get('pack_key');
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { id, key };
|
return { id, key };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInstalledStickerPacks() {
|
export function getInstalledStickerPacks(): Array<StickerPackType> {
|
||||||
const state = reduxStore.getState();
|
const state = window.reduxStore.getState();
|
||||||
const { stickers } = state;
|
const { stickers } = state;
|
||||||
const { packs } = stickers;
|
const { packs } = stickers;
|
||||||
if (!packs) {
|
if (!packs) {
|
||||||
|
@ -138,20 +148,24 @@ function getInstalledStickerPacks() {
|
||||||
return items.filter(pack => pack.status === 'installed');
|
return items.filter(pack => pack.status === 'installed');
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadQueuedPacks() {
|
export function downloadQueuedPacks(): void {
|
||||||
|
strictAssert(packsToDownload, 'Stickers not initialized');
|
||||||
|
|
||||||
const ids = Object.keys(packsToDownload);
|
const ids = Object.keys(packsToDownload);
|
||||||
ids.forEach(id => {
|
for (const id of ids) {
|
||||||
const { key, status } = packsToDownload[id];
|
const { key, status } = packsToDownload[id];
|
||||||
|
|
||||||
// The queuing is done inside this function, no need to await here
|
// The queuing is done inside this function, no need to await here
|
||||||
downloadStickerPack(id, key, { finalStatus: status });
|
downloadStickerPack(id, key, { finalStatus: status });
|
||||||
});
|
}
|
||||||
|
|
||||||
packsToDownload = {};
|
packsToDownload = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function capturePacksToDownload(existingPackLookup) {
|
function capturePacksToDownload(
|
||||||
const toDownload = Object.create(null);
|
existingPackLookup: Record<string, StickerPackType>
|
||||||
|
): DownloadMap {
|
||||||
|
const toDownload: DownloadMap = Object.create(null);
|
||||||
|
|
||||||
// First, ensure that blessed packs are in good shape
|
// First, ensure that blessed packs are in good shape
|
||||||
const blessedIds = Object.keys(BLESSED_PACKS);
|
const blessedIds = Object.keys(BLESSED_PACKS);
|
||||||
|
@ -190,7 +204,7 @@ function capturePacksToDownload(existingPackLookup) {
|
||||||
|
|
||||||
if (doesPackNeedDownload(existing)) {
|
if (doesPackNeedDownload(existing)) {
|
||||||
const status =
|
const status =
|
||||||
existing.attemptedStatus === 'installed' ? 'installed' : null;
|
existing.attemptedStatus === 'installed' ? 'installed' : undefined;
|
||||||
toDownload[id] = {
|
toDownload[id] = {
|
||||||
id,
|
id,
|
||||||
key: existing.key,
|
key: existing.key,
|
||||||
|
@ -202,7 +216,7 @@ function capturePacksToDownload(existingPackLookup) {
|
||||||
return toDownload;
|
return toDownload;
|
||||||
}
|
}
|
||||||
|
|
||||||
function doesPackNeedDownload(pack) {
|
function doesPackNeedDownload(pack?: StickerPackType): boolean {
|
||||||
if (!pack) {
|
if (!pack) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -226,14 +240,14 @@ function doesPackNeedDownload(pack) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPacksForRedux() {
|
async function getPacksForRedux(): Promise<Record<string, StickerPackType>> {
|
||||||
const [packs, stickers] = await Promise.all([
|
const [packs, stickers] = await Promise.all([
|
||||||
getAllStickerPacks(),
|
Data.getAllStickerPacks(),
|
||||||
getAllStickers(),
|
Data.getAllStickers(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const stickersByPack = groupBy(stickers, sticker => sticker.packId);
|
const stickersByPack = groupBy(stickers, sticker => sticker.packId);
|
||||||
const fullSet = packs.map(pack => ({
|
const fullSet: Array<StickerPackType> = packs.map(pack => ({
|
||||||
...pack,
|
...pack,
|
||||||
stickers: makeLookup(stickersByPack[pack.id] || [], 'id'),
|
stickers: makeLookup(stickersByPack[pack.id] || [], 'id'),
|
||||||
}));
|
}));
|
||||||
|
@ -241,40 +255,41 @@ async function getPacksForRedux() {
|
||||||
return makeLookup(fullSet, 'id');
|
return makeLookup(fullSet, 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRecentStickersForRedux() {
|
async function getRecentStickersForRedux(): Promise<Array<RecentStickerType>> {
|
||||||
const recent = await getRecentStickers();
|
const recent = await Data.getRecentStickers();
|
||||||
return recent.map(sticker => ({
|
return recent.map(sticker => ({
|
||||||
packId: sticker.packId,
|
packId: sticker.packId,
|
||||||
stickerId: sticker.id,
|
stickerId: sticker.id,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitialState() {
|
export function getInitialState(): InitialState {
|
||||||
|
strictAssert(initialState !== undefined, 'Stickers not initialized');
|
||||||
return initialState;
|
return initialState;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPackIdValid(packId) {
|
export function isPackIdValid(packId: unknown): packId is string {
|
||||||
return typeof packId === 'string' && VALID_PACK_ID_REGEXP.test(packId);
|
return typeof packId === 'string' && VALID_PACK_ID_REGEXP.test(packId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function redactPackId(packId) {
|
export function redactPackId(packId: string): string {
|
||||||
return `[REDACTED]${packId.slice(-3)}`;
|
return `[REDACTED]${packId.slice(-3)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getReduxStickerActions() {
|
function getReduxStickerActions() {
|
||||||
const actions = reduxActions;
|
const actions = window.reduxActions;
|
||||||
|
strictAssert(actions && actions.stickers, 'Redux not ready');
|
||||||
|
|
||||||
if (actions && actions.stickers) {
|
return actions.stickers;
|
||||||
return actions.stickers;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decryptSticker(packKey, ciphertext) {
|
async function decryptSticker(
|
||||||
|
packKey: string,
|
||||||
|
ciphertext: ArrayBuffer
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
const binaryKey = base64ToArrayBuffer(packKey);
|
const binaryKey = base64ToArrayBuffer(packKey);
|
||||||
const derivedKey = await deriveStickerPackKey(binaryKey);
|
const derivedKey = await deriveStickerPackKey(binaryKey);
|
||||||
const plaintext = await textsecure.crypto.decryptAttachment(
|
const plaintext = await window.textsecure.crypto.decryptAttachment(
|
||||||
ciphertext,
|
ciphertext,
|
||||||
derivedKey
|
derivedKey
|
||||||
);
|
);
|
||||||
|
@ -282,26 +297,35 @@ async function decryptSticker(packKey, ciphertext) {
|
||||||
return plaintext;
|
return plaintext;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadSticker(packId, packKey, proto, options) {
|
async function downloadSticker(
|
||||||
const { ephemeral } = options || {};
|
packId: string,
|
||||||
|
packKey: string,
|
||||||
|
proto: Proto.StickerPack.ISticker,
|
||||||
|
{ ephemeral }: { ephemeral?: boolean } = {}
|
||||||
|
): Promise<Omit<StickerType, 'isCoverOnly'>> {
|
||||||
|
const { id, emoji } = proto;
|
||||||
|
strictAssert(id !== undefined && id !== null, "Sticker id can't be null");
|
||||||
|
|
||||||
const ciphertext = await textsecure.messaging.getSticker(packId, proto.id);
|
const ciphertext = await window.textsecure.messaging.getSticker(packId, id);
|
||||||
const plaintext = await decryptSticker(packKey, ciphertext);
|
const plaintext = await decryptSticker(packKey, ciphertext);
|
||||||
|
|
||||||
const sticker = ephemeral
|
const sticker = ephemeral
|
||||||
? await Signal.Migrations.processNewEphemeralSticker(plaintext, options)
|
? await window.Signal.Migrations.processNewEphemeralSticker(plaintext)
|
||||||
: await Signal.Migrations.processNewSticker(plaintext, options);
|
: await window.Signal.Migrations.processNewSticker(plaintext);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...pick(proto, ['id', 'emoji']),
|
id,
|
||||||
|
emoji: dropNull(emoji),
|
||||||
...sticker,
|
...sticker,
|
||||||
packId,
|
packId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function savePackMetadata(packId, packKey, options = {}) {
|
export async function savePackMetadata(
|
||||||
const { messageId } = options;
|
packId: string,
|
||||||
|
packKey: string,
|
||||||
|
{ messageId }: { messageId?: string } = {}
|
||||||
|
): Promise<void> {
|
||||||
const existing = getStickerPack(packId);
|
const existing = getStickerPack(packId);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return;
|
return;
|
||||||
|
@ -309,20 +333,23 @@ async function savePackMetadata(packId, packKey, options = {}) {
|
||||||
|
|
||||||
const { stickerPackAdded } = getReduxStickerActions();
|
const { stickerPackAdded } = getReduxStickerActions();
|
||||||
const pack = {
|
const pack = {
|
||||||
|
...STICKER_PACK_DEFAULTS,
|
||||||
|
|
||||||
id: packId,
|
id: packId,
|
||||||
key: packKey,
|
key: packKey,
|
||||||
status: 'known',
|
status: 'known' as const,
|
||||||
};
|
};
|
||||||
stickerPackAdded(pack);
|
stickerPackAdded(pack);
|
||||||
|
|
||||||
await createOrUpdateStickerPack(pack);
|
await Data.createOrUpdateStickerPack(pack);
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
await addStickerPackReference(messageId, packId);
|
await Data.addStickerPackReference(messageId, packId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeEphemeralPack(packId) {
|
export async function removeEphemeralPack(packId: string): Promise<void> {
|
||||||
const existing = getStickerPack(packId);
|
const existing = getStickerPack(packId);
|
||||||
|
strictAssert(existing, `No existing sticker pack with id: ${packId}`);
|
||||||
if (
|
if (
|
||||||
existing.status !== 'ephemeral' &&
|
existing.status !== 'ephemeral' &&
|
||||||
!(existing.status === 'error' && existing.attemptedStatus === 'ephemeral')
|
!(existing.status === 'error' && existing.attemptedStatus === 'ephemeral')
|
||||||
|
@ -335,16 +362,18 @@ async function removeEphemeralPack(packId) {
|
||||||
|
|
||||||
const stickers = values(existing.stickers);
|
const stickers = values(existing.stickers);
|
||||||
const paths = stickers.map(sticker => sticker.path);
|
const paths = stickers.map(sticker => sticker.path);
|
||||||
await pMap(paths, Signal.Migrations.deleteTempFile, {
|
await pMap(paths, window.Signal.Migrations.deleteTempFile, {
|
||||||
concurrency: 3,
|
concurrency: 3,
|
||||||
timeout: 1000 * 60 * 2,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove it from database in case it made it there
|
// Remove it from database in case it made it there
|
||||||
await deleteStickerPack(packId);
|
await Data.deleteStickerPack(packId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadEphemeralPack(packId, packKey) {
|
export async function downloadEphemeralPack(
|
||||||
|
packId: string,
|
||||||
|
packKey: string
|
||||||
|
): Promise<void> {
|
||||||
const {
|
const {
|
||||||
stickerAdded,
|
stickerAdded,
|
||||||
stickerPackAdded,
|
stickerPackAdded,
|
||||||
|
@ -358,7 +387,7 @@ async function downloadEphemeralPack(packId, packKey) {
|
||||||
existingPack.status === 'installed' ||
|
existingPack.status === 'installed' ||
|
||||||
existingPack.status === 'pending')
|
existingPack.status === 'pending')
|
||||||
) {
|
) {
|
||||||
log.warn(
|
window.log.warn(
|
||||||
`Ephemeral download for pack ${redactPackId(
|
`Ephemeral download for pack ${redactPackId(
|
||||||
packId
|
packId
|
||||||
)} requested, we already know about it. Skipping.`
|
)} requested, we already know about it. Skipping.`
|
||||||
|
@ -369,17 +398,19 @@ async function downloadEphemeralPack(packId, packKey) {
|
||||||
try {
|
try {
|
||||||
// Synchronous placeholder to help with race conditions
|
// Synchronous placeholder to help with race conditions
|
||||||
const placeholder = {
|
const placeholder = {
|
||||||
|
...STICKER_PACK_DEFAULTS,
|
||||||
|
|
||||||
id: packId,
|
id: packId,
|
||||||
key: packKey,
|
key: packKey,
|
||||||
status: 'ephemeral',
|
status: 'ephemeral' as const,
|
||||||
};
|
};
|
||||||
stickerPackAdded(placeholder);
|
stickerPackAdded(placeholder);
|
||||||
|
|
||||||
const ciphertext = await textsecure.messaging.getStickerPackManifest(
|
const ciphertext = await window.textsecure.messaging.getStickerPackManifest(
|
||||||
packId
|
packId
|
||||||
);
|
);
|
||||||
const plaintext = await decryptSticker(packKey, ciphertext);
|
const plaintext = await decryptSticker(packKey, ciphertext);
|
||||||
const proto = textsecure.protobuf.StickerPack.decode(plaintext);
|
const proto = Proto.StickerPack.decode(new FIXMEU8(plaintext));
|
||||||
const firstStickerProto = proto.stickers ? proto.stickers[0] : null;
|
const firstStickerProto = proto.stickers ? proto.stickers[0] : null;
|
||||||
const stickerCount = proto.stickers.length;
|
const stickerCount = proto.stickers.length;
|
||||||
|
|
||||||
|
@ -402,16 +433,20 @@ async function downloadEphemeralPack(packId, packKey) {
|
||||||
const coverIncludedInList = nonCoverStickers.length < stickerCount;
|
const coverIncludedInList = nonCoverStickers.length < stickerCount;
|
||||||
|
|
||||||
const pack = {
|
const pack = {
|
||||||
|
...STICKER_PACK_DEFAULTS,
|
||||||
|
|
||||||
id: packId,
|
id: packId,
|
||||||
key: packKey,
|
key: packKey,
|
||||||
coverStickerId,
|
coverStickerId,
|
||||||
stickerCount,
|
stickerCount,
|
||||||
status: 'ephemeral',
|
status: 'ephemeral' as const,
|
||||||
...pick(proto, ['title', 'author']),
|
...pick(proto, ['title', 'author']),
|
||||||
};
|
};
|
||||||
stickerPackAdded(pack);
|
stickerPackAdded(pack);
|
||||||
|
|
||||||
const downloadStickerJob = async stickerProto => {
|
const downloadStickerJob = async (
|
||||||
|
stickerProto: Proto.StickerPack.ISticker
|
||||||
|
): Promise<void> => {
|
||||||
const stickerInfo = await downloadSticker(packId, packKey, stickerProto, {
|
const stickerInfo = await downloadSticker(packId, packKey, stickerProto, {
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
});
|
});
|
||||||
|
@ -438,7 +473,6 @@ async function downloadEphemeralPack(packId, packKey) {
|
||||||
// Then the rest
|
// Then the rest
|
||||||
await pMap(nonCoverStickers, downloadStickerJob, {
|
await pMap(nonCoverStickers, downloadStickerJob, {
|
||||||
concurrency: 3,
|
concurrency: 3,
|
||||||
timeout: 1000 * 60 * 2,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Because the user could install this pack while we are still downloading this
|
// Because the user could install this pack while we are still downloading this
|
||||||
|
@ -451,20 +485,30 @@ async function downloadEphemeralPack(packId, packKey) {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
log.error(
|
window.log.error(
|
||||||
`Ephemeral download error for sticker pack ${redactPackId(packId)}:`,
|
`Ephemeral download error for sticker pack ${redactPackId(packId)}:`,
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadStickerPack(packId, packKey, options = {}) {
|
export type DownloadStickerPackOptions = Readonly<{
|
||||||
|
messageId?: string;
|
||||||
|
fromSync?: boolean;
|
||||||
|
finalStatus?: StickerPackStatusType;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export async function downloadStickerPack(
|
||||||
|
packId: string,
|
||||||
|
packKey: string,
|
||||||
|
options: DownloadStickerPackOptions = {}
|
||||||
|
): Promise<void> {
|
||||||
// This will ensure that only one download process is in progress at any given time
|
// This will ensure that only one download process is in progress at any given time
|
||||||
return downloadQueue.add(async () => {
|
return downloadQueue.add(async () => {
|
||||||
try {
|
try {
|
||||||
await doDownloadStickerPack(packId, packKey, options);
|
await doDownloadStickerPack(packId, packKey, options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
window.log.error(
|
||||||
'doDownloadStickerPack threw an error:',
|
'doDownloadStickerPack threw an error:',
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
|
@ -472,8 +516,15 @@ async function downloadStickerPack(packId, packKey, options = {}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doDownloadStickerPack(packId, packKey, options = {}) {
|
async function doDownloadStickerPack(
|
||||||
const { messageId, fromSync } = options;
|
packId: string,
|
||||||
|
packKey: string,
|
||||||
|
{
|
||||||
|
finalStatus = 'downloaded',
|
||||||
|
messageId,
|
||||||
|
fromSync = false,
|
||||||
|
}: DownloadStickerPackOptions
|
||||||
|
): Promise<void> {
|
||||||
const {
|
const {
|
||||||
stickerAdded,
|
stickerAdded,
|
||||||
stickerPackAdded,
|
stickerPackAdded,
|
||||||
|
@ -481,7 +532,6 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
installStickerPack,
|
installStickerPack,
|
||||||
} = getReduxStickerActions();
|
} = getReduxStickerActions();
|
||||||
|
|
||||||
const finalStatus = options.finalStatus || 'downloaded';
|
|
||||||
if (finalStatus !== 'downloaded' && finalStatus !== 'installed') {
|
if (finalStatus !== 'downloaded' && finalStatus !== 'installed') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`doDownloadStickerPack: invalid finalStatus of ${finalStatus} requested.`
|
`doDownloadStickerPack: invalid finalStatus of ${finalStatus} requested.`
|
||||||
|
@ -490,7 +540,7 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
|
|
||||||
const existing = getStickerPack(packId);
|
const existing = getStickerPack(packId);
|
||||||
if (!doesPackNeedDownload(existing)) {
|
if (!doesPackNeedDownload(existing)) {
|
||||||
log.warn(
|
window.log.warn(
|
||||||
`Download for pack ${redactPackId(
|
`Download for pack ${redactPackId(
|
||||||
packId
|
packId
|
||||||
)} requested, but it does not need re-download. Skipping.`
|
)} requested, but it does not need re-download. Skipping.`
|
||||||
|
@ -503,14 +553,14 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
const downloadAttempts =
|
const downloadAttempts =
|
||||||
(existing ? existing.downloadAttempts || 0 : 0) + attemptIncrement;
|
(existing ? existing.downloadAttempts || 0 : 0) + attemptIncrement;
|
||||||
if (downloadAttempts > 3) {
|
if (downloadAttempts > 3) {
|
||||||
log.warn(
|
window.log.warn(
|
||||||
`Refusing to attempt another download for pack ${redactPackId(
|
`Refusing to attempt another download for pack ${redactPackId(
|
||||||
packId
|
packId
|
||||||
)}, attempt number ${downloadAttempts}`
|
)}, attempt number ${downloadAttempts}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existing.status !== 'error') {
|
if (existing && existing.status !== 'error') {
|
||||||
await updateStickerPackStatus(packId, 'error');
|
await Data.updateStickerPackStatus(packId, 'error');
|
||||||
stickerPackUpdated(packId, {
|
stickerPackUpdated(packId, {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
});
|
});
|
||||||
|
@ -519,32 +569,34 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let coverProto;
|
let coverProto: Proto.StickerPack.ISticker | undefined;
|
||||||
let coverStickerId;
|
let coverStickerId: number | undefined;
|
||||||
let coverIncludedInList;
|
let coverIncludedInList = false;
|
||||||
let nonCoverStickers;
|
let nonCoverStickers: Array<Proto.StickerPack.ISticker> = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Synchronous placeholder to help with race conditions
|
// Synchronous placeholder to help with race conditions
|
||||||
const placeholder = {
|
const placeholder = {
|
||||||
|
...STICKER_PACK_DEFAULTS,
|
||||||
|
|
||||||
id: packId,
|
id: packId,
|
||||||
key: packKey,
|
key: packKey,
|
||||||
attemptedStatus: finalStatus,
|
attemptedStatus: finalStatus,
|
||||||
downloadAttempts,
|
downloadAttempts,
|
||||||
status: 'pending',
|
status: 'pending' as const,
|
||||||
};
|
};
|
||||||
stickerPackAdded(placeholder);
|
stickerPackAdded(placeholder);
|
||||||
|
|
||||||
const ciphertext = await textsecure.messaging.getStickerPackManifest(
|
const ciphertext = await window.textsecure.messaging.getStickerPackManifest(
|
||||||
packId
|
packId
|
||||||
);
|
);
|
||||||
const plaintext = await decryptSticker(packKey, ciphertext);
|
const plaintext = await decryptSticker(packKey, ciphertext);
|
||||||
const proto = textsecure.protobuf.StickerPack.decode(plaintext);
|
const proto = Proto.StickerPack.decode(new FIXMEU8(plaintext));
|
||||||
const firstStickerProto = proto.stickers ? proto.stickers[0] : null;
|
const firstStickerProto = proto.stickers ? proto.stickers[0] : undefined;
|
||||||
const stickerCount = proto.stickers.length;
|
const stickerCount = proto.stickers.length;
|
||||||
|
|
||||||
coverProto = proto.cover || firstStickerProto;
|
coverProto = proto.cover || firstStickerProto;
|
||||||
coverStickerId = coverProto ? coverProto.id : null;
|
coverStickerId = dropNull(coverProto ? coverProto.id : undefined);
|
||||||
|
|
||||||
if (!coverProto || !isNumber(coverStickerId)) {
|
if (!coverProto || !isNumber(coverStickerId)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -568,7 +620,7 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
// - 'downloaded'
|
// - 'downloaded'
|
||||||
// - 'error'
|
// - 'error'
|
||||||
// - 'installed'
|
// - 'installed'
|
||||||
const pack = {
|
const pack: StickerPackType = {
|
||||||
id: packId,
|
id: packId,
|
||||||
key: packKey,
|
key: packKey,
|
||||||
attemptedStatus: finalStatus,
|
attemptedStatus: finalStatus,
|
||||||
|
@ -576,28 +628,32 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
downloadAttempts,
|
downloadAttempts,
|
||||||
stickerCount,
|
stickerCount,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
stickers: {},
|
||||||
...pick(proto, ['title', 'author']),
|
...pick(proto, ['title', 'author']),
|
||||||
};
|
};
|
||||||
await createOrUpdateStickerPack(pack);
|
await Data.createOrUpdateStickerPack(pack);
|
||||||
stickerPackAdded(pack);
|
stickerPackAdded(pack);
|
||||||
|
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
await addStickerPackReference(messageId, packId);
|
await Data.addStickerPackReference(messageId, packId);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
window.log.error(
|
||||||
`Error downloading manifest for sticker pack ${redactPackId(packId)}:`,
|
`Error downloading manifest for sticker pack ${redactPackId(packId)}:`,
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
|
|
||||||
const pack = {
|
const pack = {
|
||||||
|
...STICKER_PACK_DEFAULTS,
|
||||||
|
|
||||||
id: packId,
|
id: packId,
|
||||||
key: packKey,
|
key: packKey,
|
||||||
attemptedStatus: finalStatus,
|
attemptedStatus: finalStatus,
|
||||||
downloadAttempts,
|
downloadAttempts,
|
||||||
status: 'error',
|
status: 'error' as const,
|
||||||
};
|
};
|
||||||
await createOrUpdateStickerPack(pack);
|
await Data.createOrUpdateStickerPack(pack);
|
||||||
stickerPackAdded(pack);
|
stickerPackAdded(pack);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -606,13 +662,15 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
// We have a separate try/catch here because we're starting to download stickers here
|
// We have a separate try/catch here because we're starting to download stickers here
|
||||||
// and we want to preserve more of the pack on an error.
|
// and we want to preserve more of the pack on an error.
|
||||||
try {
|
try {
|
||||||
const downloadStickerJob = async stickerProto => {
|
const downloadStickerJob = async (
|
||||||
|
stickerProto: Proto.StickerPack.ISticker
|
||||||
|
): Promise<void> => {
|
||||||
const stickerInfo = await downloadSticker(packId, packKey, stickerProto);
|
const stickerInfo = await downloadSticker(packId, packKey, stickerProto);
|
||||||
const sticker = {
|
const sticker = {
|
||||||
...stickerInfo,
|
...stickerInfo,
|
||||||
isCoverOnly: !coverIncludedInList && stickerInfo.id === coverStickerId,
|
isCoverOnly: !coverIncludedInList && stickerInfo.id === coverStickerId,
|
||||||
};
|
};
|
||||||
await createOrUpdateSticker(sticker);
|
await Data.createOrUpdateSticker(sticker);
|
||||||
stickerAdded(sticker);
|
stickerAdded(sticker);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -622,7 +680,6 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
// Then the rest
|
// Then the rest
|
||||||
await pMap(nonCoverStickers, downloadStickerJob, {
|
await pMap(nonCoverStickers, downloadStickerJob, {
|
||||||
concurrency: 3,
|
concurrency: 3,
|
||||||
timeout: 1000 * 60 * 2,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Allow for the user marking this pack as installed in the middle of our download;
|
// Allow for the user marking this pack as installed in the middle of our download;
|
||||||
|
@ -636,19 +693,19 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
await installStickerPack(packId, packKey, { fromSync });
|
await installStickerPack(packId, packKey, { fromSync });
|
||||||
} else {
|
} else {
|
||||||
// Mark the pack as complete
|
// Mark the pack as complete
|
||||||
await updateStickerPackStatus(packId, finalStatus);
|
await Data.updateStickerPackStatus(packId, finalStatus);
|
||||||
stickerPackUpdated(packId, {
|
stickerPackUpdated(packId, {
|
||||||
status: finalStatus,
|
status: finalStatus,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
window.log.error(
|
||||||
`Error downloading stickers for sticker pack ${redactPackId(packId)}:`,
|
`Error downloading stickers for sticker pack ${redactPackId(packId)}:`,
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
|
|
||||||
const errorStatus = 'error';
|
const errorStatus = 'error';
|
||||||
await updateStickerPackStatus(packId, errorStatus);
|
await Data.updateStickerPackStatus(packId, errorStatus);
|
||||||
if (stickerPackUpdated) {
|
if (stickerPackUpdated) {
|
||||||
stickerPackUpdated(packId, {
|
stickerPackUpdated(packId, {
|
||||||
attemptedStatus: finalStatus,
|
attemptedStatus: finalStatus,
|
||||||
|
@ -658,45 +715,53 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStickerPack(packId) {
|
export function getStickerPack(packId: string): StickerPackType | undefined {
|
||||||
const state = reduxStore.getState();
|
const state = window.reduxStore.getState();
|
||||||
const { stickers } = state;
|
const { stickers } = state;
|
||||||
const { packs } = stickers;
|
const { packs } = stickers;
|
||||||
if (!packs) {
|
if (!packs) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return packs[packId];
|
return packs[packId];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStickerPackStatus(packId) {
|
export function getStickerPackStatus(
|
||||||
|
packId: string
|
||||||
|
): StickerPackStatusType | undefined {
|
||||||
const pack = getStickerPack(packId);
|
const pack = getStickerPack(packId);
|
||||||
if (!pack) {
|
if (!pack) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pack.status;
|
return pack.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSticker(packId, stickerId) {
|
export function getSticker(
|
||||||
|
packId: string,
|
||||||
|
stickerId: number
|
||||||
|
): StickerType | undefined {
|
||||||
const pack = getStickerPack(packId);
|
const pack = getStickerPack(packId);
|
||||||
|
|
||||||
if (!pack || !pack.stickers) {
|
if (!pack || !pack.stickers) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pack.stickers[stickerId];
|
return pack.stickers[stickerId];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyStickerToAttachments(packId, stickerId) {
|
export async function copyStickerToAttachments(
|
||||||
|
packId: string,
|
||||||
|
stickerId: number
|
||||||
|
): Promise<StickerType | undefined> {
|
||||||
const sticker = getSticker(packId, stickerId);
|
const sticker = getSticker(packId, stickerId);
|
||||||
if (!sticker) {
|
if (!sticker) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { path } = sticker;
|
const { path } = sticker;
|
||||||
const absolutePath = Signal.Migrations.getAbsoluteStickerPath(path);
|
const absolutePath = window.Signal.Migrations.getAbsoluteStickerPath(path);
|
||||||
const newPath = await Signal.Migrations.copyIntoAttachmentsDirectory(
|
const newPath = await window.Signal.Migrations.copyIntoAttachmentsDirectory(
|
||||||
absolutePath
|
absolutePath
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -709,7 +774,7 @@ async function copyStickerToAttachments(packId, stickerId) {
|
||||||
// In the case where a sticker pack is uninstalled, we want to delete it if there are no
|
// In the case where a sticker pack is uninstalled, we want to delete it if there are no
|
||||||
// more references left. We'll delete a nonexistent reference, then check if there are
|
// more references left. We'll delete a nonexistent reference, then check if there are
|
||||||
// any references left, just like usual.
|
// any references left, just like usual.
|
||||||
async function maybeDeletePack(packId) {
|
export async function maybeDeletePack(packId: string): Promise<void> {
|
||||||
// This hardcoded string is fine because message ids are GUIDs
|
// This hardcoded string is fine because message ids are GUIDs
|
||||||
await deletePackReference('NOT-USED', packId);
|
await deletePackReference('NOT-USED', packId);
|
||||||
}
|
}
|
||||||
|
@ -717,7 +782,10 @@ async function maybeDeletePack(packId) {
|
||||||
// We don't generally delete packs outright; we just remove references to them, and if
|
// We don't generally delete packs outright; we just remove references to them, and if
|
||||||
// the last reference is deleted, we finally then remove the pack itself from database
|
// the last reference is deleted, we finally then remove the pack itself from database
|
||||||
// and from disk.
|
// and from disk.
|
||||||
async function deletePackReference(messageId, packId) {
|
export async function deletePackReference(
|
||||||
|
messageId: string,
|
||||||
|
packId: string
|
||||||
|
): Promise<void> {
|
||||||
const isBlessed = Boolean(BLESSED_PACKS[packId]);
|
const isBlessed = Boolean(BLESSED_PACKS[packId]);
|
||||||
if (isBlessed) {
|
if (isBlessed) {
|
||||||
return;
|
return;
|
||||||
|
@ -725,7 +793,7 @@ async function deletePackReference(messageId, packId) {
|
||||||
|
|
||||||
// This call uses locking to prevent race conditions with other reference removals,
|
// This call uses locking to prevent race conditions with other reference removals,
|
||||||
// or an incoming message creating a new message->pack reference
|
// or an incoming message creating a new message->pack reference
|
||||||
const paths = await deleteStickerPackReference(messageId, packId);
|
const paths = await Data.deleteStickerPackReference(messageId, packId);
|
||||||
|
|
||||||
// If we don't get a list of paths back, then the sticker pack was not deleted
|
// If we don't get a list of paths back, then the sticker pack was not deleted
|
||||||
if (!paths || !paths.length) {
|
if (!paths || !paths.length) {
|
||||||
|
@ -735,14 +803,13 @@ async function deletePackReference(messageId, packId) {
|
||||||
const { removeStickerPack } = getReduxStickerActions();
|
const { removeStickerPack } = getReduxStickerActions();
|
||||||
removeStickerPack(packId);
|
removeStickerPack(packId);
|
||||||
|
|
||||||
await pMap(paths, Signal.Migrations.deleteSticker, {
|
await pMap(paths, window.Signal.Migrations.deleteSticker, {
|
||||||
concurrency: 3,
|
concurrency: 3,
|
||||||
timeout: 1000 * 60 * 2,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The override; doesn't honor our ref-counting scheme - just deletes it all.
|
// The override; doesn't honor our ref-counting scheme - just deletes it all.
|
||||||
async function deletePack(packId) {
|
export async function deletePack(packId: string): Promise<void> {
|
||||||
const isBlessed = Boolean(BLESSED_PACKS[packId]);
|
const isBlessed = Boolean(BLESSED_PACKS[packId]);
|
||||||
if (isBlessed) {
|
if (isBlessed) {
|
||||||
return;
|
return;
|
||||||
|
@ -750,13 +817,12 @@ async function deletePack(packId) {
|
||||||
|
|
||||||
// This call uses locking to prevent race conditions with other reference removals,
|
// This call uses locking to prevent race conditions with other reference removals,
|
||||||
// or an incoming message creating a new message->pack reference
|
// or an incoming message creating a new message->pack reference
|
||||||
const paths = await deleteStickerPack(packId);
|
const paths = await Data.deleteStickerPack(packId);
|
||||||
|
|
||||||
const { removeStickerPack } = getReduxStickerActions();
|
const { removeStickerPack } = getReduxStickerActions();
|
||||||
removeStickerPack(packId);
|
removeStickerPack(packId);
|
||||||
|
|
||||||
await pMap(paths, Signal.Migrations.deleteSticker, {
|
await pMap(paths, window.Signal.Migrations.deleteSticker, {
|
||||||
concurrency: 3,
|
concurrency: 3,
|
||||||
timeout: 1000 * 60 * 2,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -4,8 +4,8 @@
|
||||||
export type BodyRangeType = {
|
export type BodyRangeType = {
|
||||||
start: number;
|
start: number;
|
||||||
length: number;
|
length: number;
|
||||||
mentionUuid: string;
|
mentionUuid?: string;
|
||||||
replacementText: string;
|
replacementText?: string;
|
||||||
conversationID?: string;
|
conversationID?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,29 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import {
|
import { DownloadAttachmentType } from '../textsecure.d';
|
||||||
AttachmentPointerClass,
|
|
||||||
DownloadAttachmentType,
|
|
||||||
} from '../textsecure.d';
|
|
||||||
|
|
||||||
type AttachmentData = AttachmentPointerClass & {
|
import { AttachmentType } from '../types/Attachment';
|
||||||
id?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function downloadAttachment(
|
export async function downloadAttachment(
|
||||||
attachmentData: AttachmentData
|
attachmentData: AttachmentType
|
||||||
): Promise<DownloadAttachmentType | null> {
|
): Promise<DownloadAttachmentType | null> {
|
||||||
|
let migratedAttachment: AttachmentType;
|
||||||
|
|
||||||
|
const { id: legacyId } = attachmentData;
|
||||||
|
if (legacyId === undefined) {
|
||||||
|
migratedAttachment = attachmentData;
|
||||||
|
} else {
|
||||||
|
migratedAttachment = {
|
||||||
|
...attachmentData,
|
||||||
|
cdnId: String(legacyId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let downloaded;
|
let downloaded;
|
||||||
try {
|
try {
|
||||||
if (attachmentData.id) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
attachmentData.cdnId = attachmentData.id;
|
|
||||||
}
|
|
||||||
downloaded = await window.textsecure.messageReceiver.downloadAttachment(
|
downloaded = await window.textsecure.messageReceiver.downloadAttachment(
|
||||||
attachmentData
|
migratedAttachment
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Attachments on the server expire after 30 days, then start returning 404
|
// Attachments on the server expire after 30 days, then start returning 404
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
|
||||||
|
export type NullToUndefined<T> = Extract<T, null> extends never
|
||||||
|
? T
|
||||||
|
: Exclude<T, null> | undefined;
|
||||||
|
|
||||||
export function dropNull<T>(
|
export function dropNull<T>(
|
||||||
value: NonNullable<T> | null | undefined
|
value: NonNullable<T> | null | undefined
|
||||||
|
@ -9,3 +14,25 @@ export function dropNull<T>(
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export function shallowDropNull<O extends { [key: string]: any }>(
|
||||||
|
value: O | null | undefined
|
||||||
|
):
|
||||||
|
| {
|
||||||
|
[Property in keyof O]: NullToUndefined<O[Property]>;
|
||||||
|
}
|
||||||
|
| undefined {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const result: any = {};
|
||||||
|
|
||||||
|
for (const [key, propertyValue] of Object.entries(value)) {
|
||||||
|
result[key] = dropNull(propertyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { LocalizerType } from '../types/Util';
|
import { LocalizerType } from '../types/Util';
|
||||||
import { AccessControlClass } from '../textsecure.d';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
|
const AccessControlEnum = Proto.AccessControl.AccessRequired;
|
||||||
|
|
||||||
type AccessControlOption = {
|
type AccessControlOption = {
|
||||||
text: string;
|
text: string;
|
||||||
|
@ -10,17 +12,16 @@ type AccessControlOption = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getAccessControlOptions(
|
export function getAccessControlOptions(
|
||||||
accessEnum: typeof AccessControlClass.AccessRequired,
|
|
||||||
i18n: LocalizerType
|
i18n: LocalizerType
|
||||||
): Array<AccessControlOption> {
|
): Array<AccessControlOption> {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: i18n('GroupV2--all-members'),
|
text: i18n('GroupV2--all-members'),
|
||||||
value: accessEnum.MEMBER,
|
value: AccessControlEnum.MEMBER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n('GroupV2--only-admins'),
|
text: i18n('GroupV2--only-admins'),
|
||||||
value: accessEnum.ADMINISTRATOR,
|
value: AccessControlEnum.ADMINISTRATOR,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { ConversationAttributesType } from '../model-types.d';
|
import { ConversationAttributesType } from '../model-types.d';
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import { isDirectConversation, isMe } from './whatTypeOfConversation';
|
import { isDirectConversation, isMe } from './whatTypeOfConversation';
|
||||||
import { isInSystemContacts } from './isInSystemContacts';
|
import { isInSystemContacts } from './isInSystemContacts';
|
||||||
|
|
||||||
|
@ -24,8 +25,7 @@ export function isConversationAccepted(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageRequestEnum =
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
window.textsecure.protobuf.SyncMessage.MessageRequestResponse.Type;
|
|
||||||
|
|
||||||
const { messageRequestResponseType } = conversationAttrs;
|
const { messageRequestResponseType } = conversationAttrs;
|
||||||
if (messageRequestResponseType === messageRequestEnum.ACCEPT) {
|
if (messageRequestResponseType === messageRequestEnum.ACCEPT) {
|
||||||
|
|
|
@ -198,13 +198,6 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-07-21T18:34:59.251Z"
|
"updated": "2020-07-21T18:34:59.251Z"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"rule": "jQuery-load(",
|
|
||||||
"path": "js/modules/stickers.js",
|
|
||||||
"line": "async function load() {",
|
|
||||||
"reasonCategory": "falseMatch",
|
|
||||||
"updated": "2019-04-26T17:48:30.675Z"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/permissions_popup_start.js",
|
"path": "js/permissions_popup_start.js",
|
||||||
|
@ -14141,6 +14134,20 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-02-07T19:52:28.522Z"
|
"updated": "2020-02-07T19:52:28.522Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "ts/types/Stickers.js",
|
||||||
|
"line": "async function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2021-07-02T02:57:58.052Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "ts/types/Stickers.ts",
|
||||||
|
"line": "export async function load(): Promise<void> {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2019-04-26T17:48:30.675Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/util/hooks.js",
|
"path": "ts/util/hooks.js",
|
||||||
|
@ -14157,4 +14164,4 @@
|
||||||
"updated": "2021-03-18T21:41:28.361Z",
|
"updated": "2021-03-18T21:41:28.361Z",
|
||||||
"reasonDetail": "A generic hook. Typically not to be used with non-DOM values."
|
"reasonDetail": "A generic hook. Typically not to be used with non-DOM values."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -57,6 +57,7 @@ const excludedFilesRegexps = [
|
||||||
'^sticker-creator/dist/bundle.js',
|
'^sticker-creator/dist/bundle.js',
|
||||||
'^test/test.js',
|
'^test/test.js',
|
||||||
'^ts/test[^/]*/.+',
|
'^ts/test[^/]*/.+',
|
||||||
|
'^ts/sql/mainWorker.bundle.js',
|
||||||
|
|
||||||
// Copied from dependency
|
// Copied from dependency
|
||||||
'^js/Mp3LameEncoder.min.js',
|
'^js/Mp3LameEncoder.min.js',
|
||||||
|
|
|
@ -37,7 +37,7 @@ import {
|
||||||
multiRecipient409ResponseSchema,
|
multiRecipient409ResponseSchema,
|
||||||
multiRecipient410ResponseSchema,
|
multiRecipient410ResponseSchema,
|
||||||
} from '../textsecure/WebAPI';
|
} from '../textsecure/WebAPI';
|
||||||
import { ContentClass } from '../textsecure.d';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
import { assert } from './assert';
|
import { assert } from './assert';
|
||||||
import { isGroupV2 } from './whatTypeOfConversation';
|
import { isGroupV2 } from './whatTypeOfConversation';
|
||||||
|
@ -53,6 +53,9 @@ const MAX_CONCURRENCY = 5;
|
||||||
// sendWithSenderKey is recursive, but we don't want to loop back too many times.
|
// sendWithSenderKey is recursive, but we don't want to loop back too many times.
|
||||||
const MAX_RECURSION = 5;
|
const MAX_RECURSION = 5;
|
||||||
|
|
||||||
|
// TODO: remove once we move away from ArrayBuffers
|
||||||
|
const FIXMEU8 = Uint8Array;
|
||||||
|
|
||||||
// Public API:
|
// Public API:
|
||||||
|
|
||||||
export async function sendToGroup({
|
export async function sendToGroup({
|
||||||
|
@ -106,7 +109,7 @@ export async function sendContentMessageToGroup({
|
||||||
timestamp,
|
timestamp,
|
||||||
}: {
|
}: {
|
||||||
contentHint: number;
|
contentHint: number;
|
||||||
contentMessage: ContentClass;
|
contentMessage: Proto.Content;
|
||||||
conversation: ConversationModel;
|
conversation: ConversationModel;
|
||||||
isPartialSend?: boolean;
|
isPartialSend?: boolean;
|
||||||
online?: boolean;
|
online?: boolean;
|
||||||
|
@ -165,7 +168,7 @@ export async function sendContentMessageToGroup({
|
||||||
|
|
||||||
export async function sendToGroupViaSenderKey(options: {
|
export async function sendToGroupViaSenderKey(options: {
|
||||||
contentHint: number;
|
contentHint: number;
|
||||||
contentMessage: ContentClass;
|
contentMessage: Proto.Content;
|
||||||
conversation: ConversationModel;
|
conversation: ConversationModel;
|
||||||
isPartialSend?: boolean;
|
isPartialSend?: boolean;
|
||||||
online?: boolean;
|
online?: boolean;
|
||||||
|
@ -185,9 +188,7 @@ export async function sendToGroupViaSenderKey(options: {
|
||||||
sendOptions,
|
sendOptions,
|
||||||
timestamp,
|
timestamp,
|
||||||
} = options;
|
} = options;
|
||||||
const {
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
ContentHint,
|
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
|
||||||
|
|
||||||
const logId = conversation.idForLogging();
|
const logId = conversation.idForLogging();
|
||||||
window.log.info(
|
window.log.info(
|
||||||
|
@ -372,7 +373,9 @@ export async function sendToGroupViaSenderKey(options: {
|
||||||
contentHint,
|
contentHint,
|
||||||
devices: devicesForSenderKey,
|
devices: devicesForSenderKey,
|
||||||
distributionId,
|
distributionId,
|
||||||
contentMessage: contentMessage.toArrayBuffer(),
|
contentMessage: toArrayBuffer(
|
||||||
|
Proto.Content.encode(contentMessage).finish()
|
||||||
|
),
|
||||||
groupId,
|
groupId,
|
||||||
});
|
});
|
||||||
const accessKeys = getXorOfAccessKeys(devicesForSenderKey);
|
const accessKeys = getXorOfAccessKeys(devicesForSenderKey);
|
||||||
|
@ -431,7 +434,11 @@ export async function sendToGroupViaSenderKey(options: {
|
||||||
const normalRecipients = getUuidsFromDevices(devicesForNormalSend);
|
const normalRecipients = getUuidsFromDevices(devicesForNormalSend);
|
||||||
if (normalRecipients.length === 0) {
|
if (normalRecipients.length === 0) {
|
||||||
return {
|
return {
|
||||||
dataMessage: contentMessage.dataMessage?.toArrayBuffer(),
|
dataMessage: contentMessage.dataMessage
|
||||||
|
? toArrayBuffer(
|
||||||
|
Proto.DataMessage.encode(contentMessage.dataMessage).finish()
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
successfulIdentifiers: senderKeyRecipients,
|
successfulIdentifiers: senderKeyRecipients,
|
||||||
unidentifiedDeliveries: senderKeyRecipients,
|
unidentifiedDeliveries: senderKeyRecipients,
|
||||||
};
|
};
|
||||||
|
@ -449,7 +456,11 @@ export async function sendToGroupViaSenderKey(options: {
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dataMessage: contentMessage.dataMessage?.toArrayBuffer(),
|
dataMessage: contentMessage.dataMessage
|
||||||
|
? toArrayBuffer(
|
||||||
|
Proto.DataMessage.encode(contentMessage.dataMessage).finish()
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
errors: normalSendResult.errors,
|
errors: normalSendResult.errors,
|
||||||
failoverIdentifiers: normalSendResult.failoverIdentifiers,
|
failoverIdentifiers: normalSendResult.failoverIdentifiers,
|
||||||
successfulIdentifiers: [
|
successfulIdentifiers: [
|
||||||
|
@ -669,7 +680,7 @@ async function encryptForSenderKey({
|
||||||
);
|
);
|
||||||
const ourAddress = getOurAddress();
|
const ourAddress = getOurAddress();
|
||||||
const senderKeyStore = new SenderKeys();
|
const senderKeyStore = new SenderKeys();
|
||||||
const message = Buffer.from(padMessage(contentMessage));
|
const message = Buffer.from(padMessage(new FIXMEU8(contentMessage)));
|
||||||
|
|
||||||
const ciphertextMessage = await window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
const ciphertextMessage = await window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
||||||
ourAddress,
|
ourAddress,
|
||||||
|
|
|
@ -9,7 +9,9 @@ import {
|
||||||
InMemoryAttachmentDraftType,
|
InMemoryAttachmentDraftType,
|
||||||
OnDiskAttachmentDraftType,
|
OnDiskAttachmentDraftType,
|
||||||
} from '../types/Attachment';
|
} from '../types/Attachment';
|
||||||
import { IMAGE_JPEG } from '../types/MIME';
|
import type { StickerPackType as StickerPackDBType } from '../sql/Interface';
|
||||||
|
import * as Stickers from '../types/Stickers';
|
||||||
|
import { IMAGE_JPEG, IMAGE_WEBP } from '../types/MIME';
|
||||||
import { ConversationModel } from '../models/conversations';
|
import { ConversationModel } from '../models/conversations';
|
||||||
import {
|
import {
|
||||||
GroupV2PendingMemberType,
|
GroupV2PendingMemberType,
|
||||||
|
@ -48,6 +50,7 @@ import {
|
||||||
LinkPreviewWithDomain,
|
LinkPreviewWithDomain,
|
||||||
} from '../types/LinkPreview';
|
} from '../types/LinkPreview';
|
||||||
import * as LinkPreview from '../types/LinkPreview';
|
import * as LinkPreview from '../types/LinkPreview';
|
||||||
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
type AttachmentOptions = {
|
type AttachmentOptions = {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
|
@ -616,8 +619,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
</div>
|
</div>
|
||||||
`)[0];
|
`)[0];
|
||||||
|
|
||||||
const messageRequestEnum =
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
window.textsecure.protobuf.SyncMessage.MessageRequestResponse.Type;
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
id: model.id,
|
id: model.id,
|
||||||
|
@ -845,8 +847,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
const { model }: { model: ConversationModel } = this;
|
const { model }: { model: ConversationModel } = this;
|
||||||
const { id } = model;
|
const { id } = model;
|
||||||
|
|
||||||
const messageRequestEnum =
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
window.textsecure.protobuf.SyncMessage.MessageRequestResponse.Type;
|
|
||||||
|
|
||||||
const contactSupport = () => {
|
const contactSupport = () => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
|
@ -1564,8 +1565,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
blockAndReportSpam(model: ConversationModel): Promise<void> {
|
blockAndReportSpam(model: ConversationModel): Promise<void> {
|
||||||
const messageRequestEnum =
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
window.textsecure.protobuf.SyncMessage.MessageRequestResponse.Type;
|
|
||||||
|
|
||||||
return this.longRunningTaskWrapper({
|
return this.longRunningTaskWrapper({
|
||||||
name: 'blockAndReportSpam',
|
name: 'blockAndReportSpam',
|
||||||
|
@ -2202,7 +2202,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
contentType: blob.type,
|
contentType: blob.type,
|
||||||
data,
|
data,
|
||||||
size: data.byteLength,
|
size: data.byteLength,
|
||||||
flags: window.textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE,
|
flags: Proto.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: The RecorderView removes itself on send
|
// Note: The RecorderView removes itself on send
|
||||||
|
@ -2443,12 +2443,12 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
conversation.sendMessage(
|
conversation.sendMessage(
|
||||||
null,
|
undefined, // body
|
||||||
[],
|
[],
|
||||||
null,
|
undefined, // quote
|
||||||
[],
|
[],
|
||||||
stickerNoPath,
|
stickerNoPath,
|
||||||
undefined,
|
undefined, // BodyRanges
|
||||||
{ ...sendMessageOptions, timestamp }
|
{ ...sendMessageOptions, timestamp }
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2469,11 +2469,11 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
);
|
);
|
||||||
|
|
||||||
conversation.sendMessage(
|
conversation.sendMessage(
|
||||||
messageBody || null,
|
messageBody || undefined,
|
||||||
attachmentsToSend,
|
attachmentsToSend,
|
||||||
null, // quote
|
undefined, // quote
|
||||||
preview,
|
preview,
|
||||||
null, // sticker
|
undefined, // sticker
|
||||||
undefined, // BodyRanges
|
undefined, // BodyRanges
|
||||||
{ ...sendMessageOptions, timestamp }
|
{ ...sendMessageOptions, timestamp }
|
||||||
);
|
);
|
||||||
|
@ -2953,14 +2953,14 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
showStickerPackPreview(packId: string, packKey: string) {
|
showStickerPackPreview(packId: string, packKey: string) {
|
||||||
window.Signal.Stickers.downloadEphemeralPack(packId, packKey);
|
Stickers.downloadEphemeralPack(packId, packKey);
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
packId,
|
packId,
|
||||||
onClose: async () => {
|
onClose: async () => {
|
||||||
this.stickerPreviewModalView.remove();
|
this.stickerPreviewModalView.remove();
|
||||||
this.stickerPreviewModalView = null;
|
this.stickerPreviewModalView = null;
|
||||||
await window.Signal.Stickers.removeEphemeralPack(packId);
|
await Stickers.removeEphemeralPack(packId);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3199,7 +3199,6 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
JSX: window.Signal.State.Roots.createGroupLinkManagement(
|
JSX: window.Signal.State.Roots.createGroupLinkManagement(
|
||||||
window.reduxStore,
|
window.reduxStore,
|
||||||
{
|
{
|
||||||
accessEnum: window.textsecure.protobuf.AccessControl.AccessRequired,
|
|
||||||
changeHasGroupLink: this.changeHasGroupLink.bind(this),
|
changeHasGroupLink: this.changeHasGroupLink.bind(this),
|
||||||
conversationId: model.id,
|
conversationId: model.id,
|
||||||
copyGroupLink: this.copyGroupLink.bind(this),
|
copyGroupLink: this.copyGroupLink.bind(this),
|
||||||
|
@ -3224,7 +3223,6 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
JSX: window.Signal.State.Roots.createGroupV2Permissions(
|
JSX: window.Signal.State.Roots.createGroupV2Permissions(
|
||||||
window.reduxStore,
|
window.reduxStore,
|
||||||
{
|
{
|
||||||
accessEnum: window.textsecure.protobuf.AccessControl.AccessRequired,
|
|
||||||
conversationId: model.id,
|
conversationId: model.id,
|
||||||
setAccessControlAttributesSetting: this.setAccessControlAttributesSetting.bind(
|
setAccessControlAttributesSetting: this.setAccessControlAttributesSetting.bind(
|
||||||
this
|
this
|
||||||
|
@ -3282,8 +3280,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
showConversationDetails() {
|
showConversationDetails() {
|
||||||
const { model }: { model: ConversationModel } = this;
|
const { model }: { model: ConversationModel } = this;
|
||||||
|
|
||||||
const messageRequestEnum =
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
window.textsecure.protobuf.SyncMessage.MessageRequestResponse.Type;
|
|
||||||
|
|
||||||
// these methods are used in more than one place and should probably be
|
// these methods are used in more than one place and should probably be
|
||||||
// dried up and hoisted to methods on ConversationView
|
// dried up and hoisted to methods on ConversationView
|
||||||
|
@ -3303,7 +3300,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACCESS_ENUM = window.textsecure.protobuf.AccessControl.AccessRequired;
|
const ACCESS_ENUM = Proto.AccessControl.AccessRequired;
|
||||||
|
|
||||||
const hasGroupLink = Boolean(
|
const hasGroupLink = Boolean(
|
||||||
model.get('groupInviteLinkPassword') &&
|
model.get('groupInviteLinkPassword') &&
|
||||||
|
@ -4029,15 +4026,29 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
url: string,
|
url: string,
|
||||||
abortSignal: Readonly<AbortSignal>
|
abortSignal: Readonly<AbortSignal>
|
||||||
): Promise<null | LinkPreviewResult> {
|
): Promise<null | LinkPreviewResult> {
|
||||||
const isPackDownloaded = (pack: any) =>
|
const isPackDownloaded = (
|
||||||
pack && (pack.status === 'downloaded' || pack.status === 'installed');
|
pack?: StickerPackDBType
|
||||||
const isPackValid = (pack: any) =>
|
): pack is StickerPackDBType => {
|
||||||
pack &&
|
if (!pack) {
|
||||||
(pack.status === 'ephemeral' ||
|
return false;
|
||||||
pack.status === 'downloaded' ||
|
}
|
||||||
pack.status === 'installed');
|
|
||||||
|
|
||||||
const dataFromLink = window.Signal.Stickers.getDataFromLink(url);
|
return pack.status === 'downloaded' || pack.status === 'installed';
|
||||||
|
};
|
||||||
|
const isPackValid = (
|
||||||
|
pack?: StickerPackDBType
|
||||||
|
): pack is StickerPackDBType => {
|
||||||
|
if (!pack) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
pack.status === 'ephemeral' ||
|
||||||
|
pack.status === 'downloaded' ||
|
||||||
|
pack.status === 'installed'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataFromLink = Stickers.getDataFromLink(url);
|
||||||
if (!dataFromLink) {
|
if (!dataFromLink) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -4047,16 +4058,16 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
const keyBytes = window.Signal.Crypto.bytesFromHexString(key);
|
const keyBytes = window.Signal.Crypto.bytesFromHexString(key);
|
||||||
const keyBase64 = window.Signal.Crypto.arrayBufferToBase64(keyBytes);
|
const keyBase64 = window.Signal.Crypto.arrayBufferToBase64(keyBytes);
|
||||||
|
|
||||||
const existing = window.Signal.Stickers.getStickerPack(id);
|
const existing = Stickers.getStickerPack(id);
|
||||||
if (!isPackDownloaded(existing)) {
|
if (!isPackDownloaded(existing)) {
|
||||||
await window.Signal.Stickers.downloadEphemeralPack(id, keyBase64);
|
await Stickers.downloadEphemeralPack(id, keyBase64);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abortSignal.aborted) {
|
if (abortSignal.aborted) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pack = window.Signal.Stickers.getStickerPack(id);
|
const pack = Stickers.getStickerPack(id);
|
||||||
|
|
||||||
if (!isPackValid(pack)) {
|
if (!isPackValid(pack)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -4083,7 +4094,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
...sticker,
|
...sticker,
|
||||||
data,
|
data,
|
||||||
size: data.byteLength,
|
size: data.byteLength,
|
||||||
contentType: 'image/webp',
|
contentType: IMAGE_WEBP,
|
||||||
},
|
},
|
||||||
description: null,
|
description: null,
|
||||||
date: null,
|
date: null,
|
||||||
|
@ -4096,7 +4107,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
if (id) {
|
if (id) {
|
||||||
await window.Signal.Stickers.removeEphemeralPack(id);
|
await Stickers.removeEphemeralPack(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
60
ts/window.d.ts
vendored
60
ts/window.d.ts
vendored
|
@ -235,7 +235,6 @@ declare global {
|
||||||
};
|
};
|
||||||
log: LoggerType;
|
log: LoggerType;
|
||||||
nodeSetImmediate: typeof setImmediate;
|
nodeSetImmediate: typeof setImmediate;
|
||||||
normalizeUuids: (obj: any, paths: Array<string>, context: string) => void;
|
|
||||||
onFullScreenChange: (fullScreen: boolean) => void;
|
onFullScreenChange: (fullScreen: boolean) => void;
|
||||||
platform: string;
|
platform: string;
|
||||||
preloadedImages: Array<WhatIsThis>;
|
preloadedImages: Array<WhatIsThis>;
|
||||||
|
@ -311,14 +310,31 @@ declare global {
|
||||||
loadPreviewData: (preview: unknown) => WhatIsThis;
|
loadPreviewData: (preview: unknown) => WhatIsThis;
|
||||||
loadStickerData: (sticker: unknown) => WhatIsThis;
|
loadStickerData: (sticker: unknown) => WhatIsThis;
|
||||||
readStickerData: (path: string) => Promise<ArrayBuffer>;
|
readStickerData: (path: string) => Promise<ArrayBuffer>;
|
||||||
|
deleteSticker: (path: string) => Promise<void>;
|
||||||
|
getAbsoluteStickerPath: (path: string) => string;
|
||||||
|
processNewEphemeralSticker: (
|
||||||
|
stickerData: ArrayBuffer
|
||||||
|
) => {
|
||||||
|
path: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
processNewSticker: (
|
||||||
|
stickerData: ArrayBuffer
|
||||||
|
) => {
|
||||||
|
path: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
copyIntoAttachmentsDirectory: (path: string) => Promise<string>;
|
||||||
upgradeMessageSchema: (attributes: unknown) => WhatIsThis;
|
upgradeMessageSchema: (attributes: unknown) => WhatIsThis;
|
||||||
processNewAttachment: (
|
processNewAttachment: (
|
||||||
attachment: DownloadAttachmentType
|
attachment: DownloadAttachmentType
|
||||||
) => Promise<AttachmentType>;
|
) => Promise<AttachmentType>;
|
||||||
|
|
||||||
copyIntoTempDirectory: any;
|
copyIntoTempDirectory: any;
|
||||||
deleteDraftFile: any;
|
deleteDraftFile: (path: string) => Promise<void>;
|
||||||
deleteTempFile: any;
|
deleteTempFile: (path: string) => Promise<void>;
|
||||||
getAbsoluteDraftPath: any;
|
getAbsoluteDraftPath: any;
|
||||||
getAbsoluteTempPath: any;
|
getAbsoluteTempPath: any;
|
||||||
openFileInFolder: any;
|
openFileInFolder: any;
|
||||||
|
@ -327,36 +343,6 @@ declare global {
|
||||||
saveAttachmentToDisk: any;
|
saveAttachmentToDisk: any;
|
||||||
writeNewDraftData: any;
|
writeNewDraftData: any;
|
||||||
};
|
};
|
||||||
Stickers: {
|
|
||||||
getDataFromLink: any;
|
|
||||||
copyStickerToAttachments: (
|
|
||||||
packId: string,
|
|
||||||
stickerId: number
|
|
||||||
) => Promise<typeof window.Signal.Types.Sticker>;
|
|
||||||
deletePackReference: (id: string, packId: string) => Promise<void>;
|
|
||||||
downloadEphemeralPack: (packId: string, key: string) => Promise<void>;
|
|
||||||
downloadQueuedPacks: () => void;
|
|
||||||
downloadStickerPack: (
|
|
||||||
id: string,
|
|
||||||
key: string,
|
|
||||||
options: WhatIsThis
|
|
||||||
) => void;
|
|
||||||
getInitialState: () => WhatIsThis;
|
|
||||||
load: () => void;
|
|
||||||
removeEphemeralPack: (packId: string) => Promise<void>;
|
|
||||||
savePackMetadata: (
|
|
||||||
packId: string,
|
|
||||||
packKey: string,
|
|
||||||
metadata: unknown
|
|
||||||
) => void;
|
|
||||||
getStickerPackStatus: (packId: string) => 'downloaded' | 'installed';
|
|
||||||
getSticker: (
|
|
||||||
packId: string,
|
|
||||||
stickerId: number
|
|
||||||
) => typeof window.Signal.Types.Sticker;
|
|
||||||
getStickerPack: (packId: string) => WhatIsThis;
|
|
||||||
getInstalledStickerPacks: () => WhatIsThis;
|
|
||||||
};
|
|
||||||
Types: {
|
Types: {
|
||||||
Attachment: {
|
Attachment: {
|
||||||
save: any;
|
save: any;
|
||||||
|
@ -548,6 +534,8 @@ declare global {
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
interface Error {
|
interface Error {
|
||||||
originalError?: Event;
|
originalError?: Event;
|
||||||
|
reason?: any;
|
||||||
|
stackForLog?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uint8Array and ArrayBuffer are type-compatible in TypeScript's covariant
|
// Uint8Array and ArrayBuffer are type-compatible in TypeScript's covariant
|
||||||
|
@ -702,7 +690,11 @@ export type WhisperType = {
|
||||||
TapToViewMessagesListener: WhatIsThis;
|
TapToViewMessagesListener: WhatIsThis;
|
||||||
|
|
||||||
deliveryReceiptQueue: PQueue<WhatIsThis>;
|
deliveryReceiptQueue: PQueue<WhatIsThis>;
|
||||||
deliveryReceiptBatcher: BatcherType<WhatIsThis>;
|
deliveryReceiptBatcher: BatcherType<{
|
||||||
|
source?: string;
|
||||||
|
sourceUuid?: string;
|
||||||
|
timestamp: number;
|
||||||
|
}>;
|
||||||
RotateSignedPreKeyListener: WhatIsThis;
|
RotateSignedPreKeyListener: WhatIsThis;
|
||||||
|
|
||||||
AlreadyGroupMemberToast: typeof window.Whisper.ToastView;
|
AlreadyGroupMemberToast: typeof window.Whisper.ToastView;
|
||||||
|
|
Loading…
Reference in a new issue