You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			500 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			500 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
| /*
 | |
|  * vim: ts=4:sw=4:expandtab
 | |
|  */
 | |
| // sendMessage(numbers = [], message = PushMessageContentProto, callback(success/failure map))
 | |
| window.textsecure.messaging = function() {
 | |
|     'use strict';
 | |
| 
 | |
|     var self = {};
 | |
| 
 | |
|     // success_callback(server success/failure map), error_callback(error_msg)
 | |
|     // message == PushMessageContentProto (NOT STRING)
 | |
|     function sendMessageToDevices(timestamp, number, deviceObjectList, message, success_callback, error_callback) {
 | |
|         var jsonData = [];
 | |
|         var relay = undefined;
 | |
|         var promises = [];
 | |
| 
 | |
|         var addEncryptionFor = function(i) {
 | |
|             if (deviceObjectList[i].relay !== undefined) {
 | |
|                 if (relay === undefined)
 | |
|                     relay = deviceObjectList[i].relay;
 | |
|                 else if (relay != deviceObjectList[i].relay)
 | |
|                     return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
 | |
|             } else {
 | |
|                 if (relay === undefined)
 | |
|                     relay = "";
 | |
|                 else if (relay != "")
 | |
|                     return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
 | |
|             }
 | |
| 
 | |
|             return textsecure.protocol_wrapper.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
 | |
|                 return textsecure.protocol_wrapper.getRegistrationId(deviceObjectList[i].encodedNumber).then(function(registrationId) {
 | |
|                     return textsecure.storage.devices.removeTempKeysFromDevice(deviceObjectList[i].encodedNumber).then(function() {
 | |
|                         jsonData[i] = {
 | |
|                             type: encryptedMsg.type,
 | |
|                             destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
 | |
|                             destinationRegistrationId: registrationId,
 | |
|                             content: encryptedMsg.body,
 | |
|                             timestamp: timestamp
 | |
|                         };
 | |
| 
 | |
|                         if (deviceObjectList[i].relay !== undefined)
 | |
|                             jsonData[i].relay = deviceObjectList[i].relay;
 | |
|                     });
 | |
|                 });
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         for (var i = 0; i < deviceObjectList.length; i++)
 | |
|             promises[i] = addEncryptionFor(i);
 | |
|         return Promise.all(promises).then(function() {
 | |
|             var legacy = (message instanceof textsecure.protobuf.DataMessage);
 | |
|             return TextSecureServer.sendMessages(number, jsonData, legacy);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     function makeAttachmentPointer(attachment) {
 | |
|         var proto = new textsecure.protobuf.AttachmentPointer();
 | |
|         proto.key = textsecure.crypto.getRandomBytes(64);
 | |
| 
 | |
|         var iv = textsecure.crypto.getRandomBytes(16);
 | |
|         return textsecure.crypto.encryptAttachment(attachment.data, proto.key, iv).then(function(encryptedBin) {
 | |
|             return TextSecureServer.putAttachment(encryptedBin).then(function(id) {
 | |
|                 proto.id = id;
 | |
|                 proto.contentType = attachment.contentType;
 | |
|                 return proto;
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     var refreshGroup = function(number, groupId, devicesForNumber) {
 | |
|         groupId = getString(groupId);
 | |
| 
 | |
|         var doUpdate = false;
 | |
|         return Promise.all(devicesForNumber.map(function(device) {
 | |
|             return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) {
 | |
|                 return textsecure.storage.groups.needUpdateByDeviceRegistrationId(
 | |
|                     groupId, number, device.encodedNumber, registrationId
 | |
|                 ).then(function(needUpdate) {
 | |
|                     if (needUpdate) doUpdate = true;
 | |
|                 });
 | |
|             });
 | |
|         })).then(function() {
 | |
|             if (!doUpdate) return;
 | |
| 
 | |
|             return textsecure.storage.groups.getGroup(groupId).then(function(group) {
 | |
|                 var numberIndex = group.numbers.indexOf(number);
 | |
|                 if (numberIndex < 0) // This is potentially a multi-message rare racing-AJAX race
 | |
|                     return Promise.reject("Tried to refresh group to non-member");
 | |
| 
 | |
|                 var proto = new textsecure.protobuf.DataMessage();
 | |
|                 proto.group = new textsecure.protobuf.GroupContext();
 | |
| 
 | |
|                 proto.group.id = toArrayBuffer(group.id);
 | |
|                 proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
 | |
|                 proto.group.members = group.numbers;
 | |
|                 proto.group.name = group.name === undefined ? null : group.name;
 | |
| 
 | |
|                 if (group.avatar !== undefined) {
 | |
|                     return makeAttachmentPointer(group.avatar).then(function(attachment) {
 | |
|                         proto.group.avatar = attachment;
 | |
|                         return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
 | |
|                     });
 | |
|                 } else {
 | |
|                     return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
 | |
|                 }
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     var tryMessageAgain = function(number, encodedMessage, timestamp) {
 | |
|         var proto = textsecure.protobuf.DataMessage.decode(encodedMessage);
 | |
|         return new Promise(function(resolve, reject) {
 | |
|             sendMessageProto(timestamp, [number], proto, function(res) {
 | |
|                 if (res.failure.length > 0)
 | |
|                     reject(res.failure);
 | |
|                 else
 | |
|                     resolve();
 | |
|             });
 | |
|         });
 | |
|     };
 | |
|     textsecure.replay.registerFunction(tryMessageAgain, textsecure.replay.Type.SEND_MESSAGE);
 | |
| 
 | |
|     var sendMessageProto = function(timestamp, numbers, message, callback) {
 | |
|         var numbersCompleted = 0;
 | |
|         var errors = [];
 | |
|         var successfulNumbers = [];
 | |
| 
 | |
|         var numberCompleted = function() {
 | |
|             numbersCompleted++;
 | |
|             if (numbersCompleted >= numbers.length)
 | |
|                 callback({success: successfulNumbers, failure: errors});
 | |
|         };
 | |
| 
 | |
|         var registerError = function(number, message, error) {
 | |
|             if (error) {
 | |
|                 if (error.humanError)
 | |
|                     message = error.humanError;
 | |
|             } else
 | |
|                 error = new Error(message);
 | |
|             errors[errors.length] = { number: number, reason: message, error: error };
 | |
|             numberCompleted();
 | |
|         };
 | |
| 
 | |
|         var reloadDevicesAndSend = function(number, recurse) {
 | |
|             return function() {
 | |
|                 return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) {
 | |
|                     if (devicesForNumber.length == 0)
 | |
|                         return registerError(number, "Got empty device list when loading device keys", null);
 | |
|                     doSendMessage(number, devicesForNumber, recurse);
 | |
|                 });
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         function getKeysForNumber(number, updateDevices) {
 | |
|             var handleResult = function(response) {
 | |
|                 return Promise.all(response.devices.map(function(device) {
 | |
|                     if (updateDevices === undefined || updateDevices.indexOf(device.deviceId) > -1)
 | |
|                         return textsecure.storage.devices.saveKeysToDeviceObject({
 | |
|                             encodedNumber: number + "." + device.deviceId,
 | |
|                             identityKey: response.identityKey,
 | |
|                             preKey: device.preKey.publicKey,
 | |
|                             preKeyId: device.preKey.keyId,
 | |
|                             signedKey: device.signedPreKey.publicKey,
 | |
|                             signedKeyId: device.signedPreKey.keyId,
 | |
|                             signedKeySignature: device.signedPreKey.signature,
 | |
|                             registrationId: device.registrationId
 | |
|                         }).catch(function(error) {
 | |
|                             if (error.message === "Identity key changed") {
 | |
|                                 error = new textsecure.OutgoingIdentityKeyError(number, message.toArrayBuffer(), timestamp, error.identityKey);
 | |
|                                 registerError(number, "Identity key changed", error);
 | |
|                             }
 | |
|                             throw error;
 | |
|                         });
 | |
|                 }));
 | |
|             };
 | |
| 
 | |
|             if (updateDevices === undefined) {
 | |
|                 return TextSecureServer.getKeysForNumber(number).then(handleResult);
 | |
|             } else {
 | |
|                 var promises = [];
 | |
|                 for (var i in updateDevices)
 | |
|                     promises[promises.length] = TextSecureServer.getKeysForNumber(number, updateDevices[i]).then(handleResult);
 | |
| 
 | |
|                 return Promise.all(promises);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         var doSendMessage = function(number, devicesForNumber, recurse) {
 | |
|             var groupUpdate = Promise.resolve(true);
 | |
|             if (message.group && message.group.id && message.group.type != textsecure.protobuf.GroupContext.Type.QUIT)
 | |
|                 groupUpdate = refreshGroup(number, message.group.id, devicesForNumber);
 | |
|             return groupUpdate.then(function() {
 | |
|                 return sendMessageToDevices(timestamp, number, devicesForNumber, message).then(function(result) {
 | |
|                     successfulNumbers[successfulNumbers.length] = number;
 | |
|                     numberCompleted();
 | |
|                 });
 | |
|             }).catch(function(error) {
 | |
|                 if (error instanceof Error && error.name == "HTTPError" && (error.code == 410 || error.code == 409)) {
 | |
|                     if (!recurse)
 | |
|                         return registerError(number, "Hit retry limit attempting to reload device list", error);
 | |
| 
 | |
|                     var p;
 | |
|                     if (error.code == 409) {
 | |
|                         p = textsecure.storage.devices.removeDeviceIdsForNumber(number, error.response.extraDevices);
 | |
|                     } else {
 | |
|                         p = Promise.all(error.response.staleDevices.map(function(deviceId) {
 | |
|                             return textsecure.protocol_wrapper.closeOpenSessionForDevice(number + '.' + deviceId);
 | |
|                         }));
 | |
|                     }
 | |
| 
 | |
|                     p.then(function() {
 | |
|                         var resetDevices = ((error.code == 410) ? error.response.staleDevices : error.response.missingDevices);
 | |
|                         getKeysForNumber(number, resetDevices)
 | |
|                             .then(reloadDevicesAndSend(number, false))
 | |
|                             .catch(function(error) {
 | |
|                                 registerError(number, "Failed to reload device keys", error);
 | |
|                             });
 | |
|                     });
 | |
|                 } else {
 | |
|                     registerError(number, "Failed to create or send message", error);
 | |
|                 }
 | |
|             });
 | |
|         };
 | |
| 
 | |
|         var getDevicesAndSendToNumber = function(number) {
 | |
|             textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) {
 | |
|                 return Promise.all(devicesForNumber.map(function(device) {
 | |
|                     return textsecure.protocol_wrapper.hasOpenSession(device.encodedNumber).then(function(result) {
 | |
|                         if (!result)
 | |
|                             return getKeysForNumber(number, [parseInt(textsecure.utils.unencodeNumber(device.encodedNumber)[1])]);
 | |
|                     });
 | |
|                 })).then(function() {
 | |
|                     return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) {
 | |
|                         if (devicesForNumber.length == 0) {
 | |
|                             getKeysForNumber(number, [1])
 | |
|                                 .then(reloadDevicesAndSend(number, true))
 | |
|                                 .catch(function(error) {
 | |
|                                     registerError(number, "Failed to retreive new device keys for number " + number, error);
 | |
|                                 });
 | |
|                         } else
 | |
|                             doSendMessage(number, devicesForNumber, true);
 | |
|                     });
 | |
|                 });
 | |
|             });
 | |
|         };
 | |
| 
 | |
|         for (var i in numbers)
 | |
|             getDevicesAndSendToNumber(numbers[i]);
 | |
|     }
 | |
| 
 | |
|     var sendIndividualProto = function(number, proto, timestamp) {
 | |
|         return new Promise(function(resolve, reject) {
 | |
|             sendMessageProto(timestamp, [number], proto, function(res) {
 | |
|                 if (res.failure.length > 0)
 | |
|                     reject(res.failure);
 | |
|                 else
 | |
|                     resolve();
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     var sendSyncMessage = function(message, timestamp, destination) {
 | |
|         var myNumber = textsecure.storage.user.getNumber();
 | |
|         var myDevice = textsecure.storage.user.getDeviceId();
 | |
|         if (myDevice != 1) {
 | |
|             var sentMessage = new textsecure.protobuf.SyncMessage.Sent();
 | |
|             sentMessage.timestamp = timestamp;
 | |
|             sentMessage.message = message;
 | |
|             if (destination) {
 | |
|                 sentMessage.destination = destination;
 | |
|             }
 | |
|             var syncMessage = new textsecure.protobuf.SyncMessage();
 | |
|             syncMessage.sent = sentMessage;
 | |
|             var contentMessage = new textsecure.protobuf.Content();
 | |
|             contentMessage.syncMessage = syncMessage;
 | |
| 
 | |
|             return sendIndividualProto(myNumber, contentMessage, Date.now());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     self.sendRequestGroupSyncMessage = function() {
 | |
|         var myNumber = textsecure.storage.user.getNumber();
 | |
|         var myDevice = textsecure.storage.user.getDeviceId();
 | |
|         if (myDevice != 1) {
 | |
|             var request = new textsecure.protobuf.SyncMessage.Request();
 | |
|             request.type = textsecure.protobuf.SyncMessage.Request.Type.GROUPS;
 | |
|             var syncMessage = new textsecure.protobuf.SyncMessage();
 | |
|             syncMessage.request = request;
 | |
|             var contentMessage = new textsecure.protobuf.Content();
 | |
|             contentMessage.syncMessage = syncMessage;
 | |
| 
 | |
|             return sendIndividualProto(myNumber, contentMessage, Date.now());
 | |
|         }
 | |
|     };
 | |
|     self.sendRequestContactSyncMessage = function() {
 | |
|         var myNumber = textsecure.storage.user.getNumber();
 | |
|         var myDevice = textsecure.storage.user.getDeviceId();
 | |
|         if (myDevice != 1) {
 | |
|             var request = new textsecure.protobuf.SyncMessage.Request();
 | |
|             request.type = textsecure.protobuf.SyncMessage.Request.Type.CONTACTS;
 | |
|             var syncMessage = new textsecure.protobuf.SyncMessage();
 | |
|             syncMessage.request = request;
 | |
|             var contentMessage = new textsecure.protobuf.Content();
 | |
|             contentMessage.syncMessage = syncMessage;
 | |
| 
 | |
|             return sendIndividualProto(myNumber, contentMessage, Date.now());
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     var sendGroupProto = function(numbers, proto, timestamp) {
 | |
|         timestamp = timestamp || Date.now();
 | |
|         var me = textsecure.storage.user.getNumber();
 | |
|         numbers = numbers.filter(function(number) { return number != me; });
 | |
| 
 | |
|         return new Promise(function(resolve, reject) {
 | |
|             sendMessageProto(timestamp, numbers, proto, function(res) {
 | |
|                 if (res.failure.length > 0)
 | |
|                     reject(res.failure);
 | |
|                 else
 | |
|                     resolve();
 | |
|             });
 | |
|         }).then(function() {
 | |
|             return sendSyncMessage(proto, timestamp);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     self.sendMessageToNumber = function(number, messageText, attachments, timestamp) {
 | |
|         var proto = new textsecure.protobuf.DataMessage();
 | |
|         proto.body = messageText;
 | |
| 
 | |
|         var promises = [];
 | |
|         for (var i in attachments)
 | |
|             promises.push(makeAttachmentPointer(attachments[i]));
 | |
|         return Promise.all(promises).then(function(attachmentsArray) {
 | |
|             proto.attachments = attachmentsArray;
 | |
|             return sendIndividualProto(number, proto, timestamp).then(function() {
 | |
|                 return sendSyncMessage(proto, timestamp, number);
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     self.closeSession = function(number) {
 | |
|         var proto = new textsecure.protobuf.DataMessage();
 | |
|         proto.body = "TERMINATE";
 | |
|         proto.flags = textsecure.protobuf.DataMessage.Flags.END_SESSION;
 | |
|         return sendIndividualProto(number, proto, Date.now()).then(function(res) {
 | |
|             return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devices) {
 | |
|                 return Promise.all(devices.map(function(device) {
 | |
|                     return textsecure.protocol_wrapper.closeOpenSessionForDevice(device.encodedNumber);
 | |
|                 })).then(function() {
 | |
|                     return res;
 | |
|                 });
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     self.sendMessageToGroup = function(groupId, messageText, attachments, timestamp) {
 | |
|         var proto = new textsecure.protobuf.DataMessage();
 | |
|         proto.body = messageText;
 | |
|         proto.group = new textsecure.protobuf.GroupContext();
 | |
|         proto.group.id = toArrayBuffer(groupId);
 | |
|         proto.group.type = textsecure.protobuf.GroupContext.Type.DELIVER;
 | |
| 
 | |
|         return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
 | |
|             if (numbers === undefined)
 | |
|                 return Promise.reject(new Error("Unknown Group"));
 | |
| 
 | |
|             var promises = [];
 | |
|             for (var i in attachments)
 | |
|                 promises.push(makeAttachmentPointer(attachments[i]));
 | |
|             return Promise.all(promises).then(function(attachmentsArray) {
 | |
|                 proto.attachments = attachmentsArray;
 | |
|                 return sendGroupProto(numbers, proto, timestamp);
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     self.createGroup = function(numbers, name, avatar) {
 | |
|         var proto = new textsecure.protobuf.DataMessage();
 | |
|         proto.group = new textsecure.protobuf.GroupContext();
 | |
| 
 | |
|         return textsecure.storage.groups.createNewGroup(numbers).then(function(group) {
 | |
|             proto.group.id = toArrayBuffer(group.id);
 | |
|             var numbers = group.numbers;
 | |
| 
 | |
|             proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
 | |
|             proto.group.members = numbers;
 | |
|             proto.group.name = name;
 | |
| 
 | |
|             if (avatar !== undefined) {
 | |
|                 return makeAttachmentPointer(avatar).then(function(attachment) {
 | |
|                     proto.group.avatar = attachment;
 | |
|                     return sendGroupProto(numbers, proto).then(function() {
 | |
|                         return proto.group.id;
 | |
|                     });
 | |
|                 });
 | |
|             } else {
 | |
|                 return sendGroupProto(numbers, proto).then(function() {
 | |
|                     return proto.group.id;
 | |
|                 });
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     self.updateGroup = function(groupId, name, avatar, numbers) {
 | |
|         var proto = new textsecure.protobuf.DataMessage();
 | |
|         proto.group = new textsecure.protobuf.GroupContext();
 | |
| 
 | |
|         proto.group.id = toArrayBuffer(groupId);
 | |
|         proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
 | |
|         proto.group.name = name;
 | |
| 
 | |
|         return textsecure.storage.groups.addNumbers(groupId, numbers).then(function(numbers) {
 | |
|             if (numbers === undefined) {
 | |
|                 return Promise.reject(new Error("Unknown Group"));
 | |
|             }
 | |
|             proto.group.members = numbers;
 | |
| 
 | |
|             if (avatar !== undefined && avatar !== null) {
 | |
|                 return makeAttachmentPointer(avatar).then(function(attachment) {
 | |
|                     proto.group.avatar = attachment;
 | |
|                     return sendGroupProto(numbers, proto).then(function() {
 | |
|                         return proto.group.id;
 | |
|                     });
 | |
|                 });
 | |
|             } else {
 | |
|                 return sendGroupProto(numbers, proto).then(function() {
 | |
|                     return proto.group.id;
 | |
|                 });
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     self.addNumberToGroup = function(groupId, number) {
 | |
|         var proto = new textsecure.protobuf.DataMessage();
 | |
|         proto.group = new textsecure.protobuf.GroupContext();
 | |
|         proto.group.id = toArrayBuffer(groupId);
 | |
|         proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
 | |
| 
 | |
|         return textsecure.storage.groups.addNumbers(groupId, [number]).then(function(numbers) {
 | |
|             if (numbers === undefined)
 | |
|                 return Promise.reject(new Error("Unknown Group"));
 | |
|             proto.group.members = numbers;
 | |
| 
 | |
|             return sendGroupProto(numbers, proto);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     self.setGroupName = function(groupId, name) {
 | |
|         var proto = new textsecure.protobuf.DataMessage();
 | |
|         proto.group = new textsecure.protobuf.GroupContext();
 | |
|         proto.group.id = toArrayBuffer(groupId);
 | |
|         proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
 | |
|         proto.group.name = name;
 | |
| 
 | |
|         return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
 | |
|             if (numbers === undefined)
 | |
|                 return Promise.reject(new Error("Unknown Group"));
 | |
|             proto.group.members = numbers;
 | |
| 
 | |
|             return sendGroupProto(numbers, proto);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     self.setGroupAvatar = function(groupId, avatar) {
 | |
|         var proto = new textsecure.protobuf.DataMessage();
 | |
|         proto.group = new textsecure.protobuf.GroupContext();
 | |
|         proto.group.id = toArrayBuffer(groupId);
 | |
|         proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
 | |
| 
 | |
|         return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
 | |
|             if (numbers === undefined)
 | |
|                 return Promise.reject(new Error("Unknown Group"));
 | |
|             proto.group.members = numbers;
 | |
| 
 | |
|             return makeAttachmentPointer(avatar).then(function(attachment) {
 | |
|                 proto.group.avatar = attachment;
 | |
|                 return sendGroupProto(numbers, proto);
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     self.leaveGroup = function(groupId) {
 | |
|         var proto = new textsecure.protobuf.DataMessage();
 | |
|         proto.group = new textsecure.protobuf.GroupContext();
 | |
|         proto.group.id = toArrayBuffer(groupId);
 | |
|         proto.group.type = textsecure.protobuf.GroupContext.Type.QUIT;
 | |
| 
 | |
|         return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
 | |
|             if (numbers === undefined)
 | |
|                 return Promise.reject(new Error("Unknown Group"));
 | |
|             return textsecure.storage.groups.deleteGroup(groupId).then(function() {
 | |
|                 return sendGroupProto(numbers, proto);
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     return self;
 | |
| }();
 |