| 
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -28,6 +28,7 @@ function OutgoingMessage(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  this.numbersCompleted = 0;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  this.errors = [];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  this.successfulNumbers = [];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  this.fallBackEncryption = false;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				OutgoingMessage.prototype = {
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -63,11 +64,12 @@ OutgoingMessage.prototype = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return () =>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (deviceIds.length === 0) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          return this.registerError(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            number,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            'Got empty device list when loading device keys',
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            null
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          deviceIds = [1];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // return this.registerError(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          //   number,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          //   'Got empty device list when loading device keys',
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          //   null
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return this.doSendMessage(number, deviceIds, recurse);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      });
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -110,7 +112,7 @@ OutgoingMessage.prototype = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          return null;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // TODO: check if still applicable
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (updateDevices === undefined) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      return this.server.getKeysForNumber(number).then(handleResult);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -121,15 +123,17 @@ OutgoingMessage.prototype = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          textsecure.storage.protocol.loadContactPreKey(number),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          textsecure.storage.protocol.loadContactSignedPreKey(number)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ]).then((keys) => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            const [preKey, signedPreKey] = keys;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (preKey == undefined || signedPreKey == undefined) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              log.error("Will need to request keys!")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              const identityKey = StringView.hexToArrayBuffer(number);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              return handleResult({ identityKey, devices: [{ deviceId: device, preKey, signedPreKey, registrationId: 0}] })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          const [preKey, signedPreKey] = keys;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          if (preKey == undefined || signedPreKey == undefined) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            log.error("Will need to request keys!")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            this.fallBackEncryption = true;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return Promise.resolve();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            const identityKey = StringView.hexToArrayBuffer(number);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return handleResult({ identityKey, devices: [{ deviceId: device, preKey, signedPreKey, registrationId: 0 }] })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          .catch(e => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (e.name === 'HTTPError' && e.code === 404) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              if (device !== 1) {
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -153,7 +157,7 @@ OutgoingMessage.prototype = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      const [response, status] = await this.lokiserver.sendMessage(pubKey, JSON.stringify(jsonData), ttl);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      return response;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    catch(e) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    catch (e) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // 409 and 410 should bubble and be handled by doSendMessage
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // 404 should throw UnregisteredUserError
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -186,11 +190,13 @@ OutgoingMessage.prototype = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  getPlaintext() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (!this.plaintext) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      const messageBuffer = this.message.toArrayBuffer();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      this.plaintext = new Uint8Array(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        this.getPaddedMessageLength(messageBuffer.byteLength + 1) - 1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      this.plaintext = new Uint8Array(messageBuffer.byteLength);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // TODO: figure out why we needed padding in the first place
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // this.plaintext = new Uint8Array(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      //   this.getPaddedMessageLength(messageBuffer.byteLength + 1) - 1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      this.plaintext.set(new Uint8Array(messageBuffer));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      this.plaintext[messageBuffer.byteLength] = 0x80;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      //this.plaintext[messageBuffer.byteLength] = 0x80;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return this.plaintext;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  },
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -211,11 +217,43 @@ OutgoingMessage.prototype = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          options.messageKeysLimit = false;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        const sessionCipher = new libsignal.SessionCipher(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          textsecure.storage.protocol,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          address,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          options
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let sessionCipher;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (this.fallBackEncryption) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // TODO: move to own file?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          FallBackSessionCipher = function (address) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            this.recipientPubKey = StringView.hexToArrayBuffer(address.getName());
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            this.encrypt = async (plaintext) => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              const myPrivateKey = myKeyPair.privKey;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              const symmetricKey = libsignal.Curve.calculateAgreement(this.recipientPubKey, myPrivateKey);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              const iv = libsignal.crypto.getRandomBytes(16);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plaintext, iv);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              const ivAndCiphertext = new Uint8Array(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                iv.byteLength + ciphertext.byteLength
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              ivAndCiphertext.set(new Uint8Array(iv));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              ivAndCiphertext.set(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                new Uint8Array(ciphertext),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                iv.byteLength
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              return {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  type           : 6, //friend request
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  body           : new dcodeIO.ByteBuffer.wrap(ivAndCiphertext).toString('binary'),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  registrationId : null
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          sessionCipher = new FallBackSessionCipher(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            address
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          sessionCipher = new libsignal.SessionCipher(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            textsecure.storage.protocol,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            address,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            options
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ciphers[address.getDeviceId()] = sessionCipher;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return sessionCipher.encrypt(plaintext).then(ciphertext => ({
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          type: ciphertext.type,
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
				
			
			 | 
			 | 
			
				
 
 |