diff --git a/js/background.js b/js/background.js index 36741687e..48d63175f 100644 --- a/js/background.js +++ b/js/background.js @@ -1391,10 +1391,11 @@ pubKey ); await window.lokiFileServerAPI.updateOurDeviceMapping(); - // TODO: we should ensure the message was sent and retry automatically if not const device = new libsession.Types.PubKey(pubKey); const unlinkMessage = new libsession.Messages.Outgoing.DeviceUnlinkMessage( - pubKey + { + timestamp: Date.now(), + } ); await libsession.getMessageQueue().send(device, unlinkMessage); diff --git a/js/models/conversations.js b/js/models/conversations.js index 6611f9578..25fac7d90 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -1382,7 +1382,7 @@ const groupInvitMessage = new libsession.Messages.Outgoing.GroupInvitationMessage( { identifier: id, - + timestamp: Date.now(), serverName: groupInvitation.name, channelId: groupInvitation.channelId, serverAddress: groupInvitation.address, @@ -2720,7 +2720,7 @@ const ourConversation = window.ConversationController.get(ourNumber); let profileKey = null; if (this.get('profileSharing')) { - profileKey = storage.get('profileKey'); + profileKey = new Uint8Array(storage.get('profileKey')); } const avatarPointer = ourConversation.get('avatarPointer'); const { displayName } = ourConversation.getLokiProfile(); diff --git a/js/models/messages.js b/js/models/messages.js index 159ae9479..15abc5ca2 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1087,8 +1087,10 @@ } const { body, attachments, preview, quote } = await this.uploadData(); + const ourNumber = window.storage.get('primaryDevicePubKey'); + const ourConversation = window.ConversationController.get(ourNumber); - const chatMessage = new libsession.Messages.Outgoing.ChatMessage({ + const chatParams = { identifier: this.id, body, timestamp: this.get('sent_at'), @@ -1096,8 +1098,14 @@ attachments, preview, quote, - lokiProfile: this.conversation.getOurProfile(), - }); + }; + if (ourConversation) { + chatParams.lokiProfile = ourConversation.getOurProfile(); + } + + const chatMessage = new libsession.Messages.Outgoing.ChatMessage( + chatParams + ); // Special-case the self-send case - we send only a sync message if (recipients.length === 1) { diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 3c7d9b7f7..6f7a81652 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -96,8 +96,9 @@ class LokiMessageAPI { // eslint-disable-next-line more/no-then snode = await primitives.firstTrue(promises); } catch (e) { + const snodeStr = snode ? `${snode.ip}:${snode.port}` : 'null'; log.warn( - `loki_message:::sendMessage - ${e.code} ${e.message} to ${pubKey} via ${snode.ip}:${snode.port}` + `loki_message:::sendMessage - ${e.code} ${e.message} to ${pubKey} via snode:${snodeStr}` ); if (e instanceof textsecure.WrongDifficultyError) { // Force nonce recalculation diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index e39ca1984..573e31caa 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -553,7 +553,7 @@ timestamp: Date.now(), primaryDevicePubKey, secondaryDevicePubKey: ourPubKey, - requestSignature, + requestSignature: new Uint8Array(requestSignature), } ); await window.libsession @@ -616,14 +616,7 @@ ); // We need to send the our profile to the secondary device - const { displayName } = ourConversation.getLokiProfile(); - const avatarPointer = ourConversation.get('avatarPointer'); - const profileKey = window.storage.get('profileKey'); - const lokiProfile = { - displayName, - profileKey, - avatarPointer, - }; + const lokiProfile = ourConversation.getOurProfile(); // Try to upload to the file server and then send a message try { @@ -631,7 +624,10 @@ const requestPairingMessage = new libsession.Messages.Outgoing.DeviceLinkGrantMessage( { timestamp: Date.now(), - ...authorisation, + primaryDevicePubKey: ourPubKey, + secondaryDevicePubKey: secondaryDeviceStr, + requestSignature: new Uint8Array(requestSignature), + grantSignature: new Uint8Array(grantSignature), lokiProfile, } ); diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js new file mode 100644 index 000000000..acec0709d --- /dev/null +++ b/libtextsecure/outgoing_message.js @@ -0,0 +1,535 @@ +/* global + textsecure, + libsignal, + window, + libloki, + StringView, + lokiMessageAPI, +*/ + +/* eslint-disable more/no-then */ +/* eslint-disable no-unreachable */ +const NUM_SEND_CONNECTIONS = 3; + +const getTTLForType = type => { + switch (type) { + case 'pairing-request': + return window.libsession.Constants.TTL_DEFAULT.PAIRING_REQUEST; + case 'device-unpairing': + return window.libsession.Constants.TTL_DEFAULT.DEVICE_UNPAIRING; + case 'onlineBroadcast': + return window.libsession.Constants.TTL_DEFAULT.ONLINE_BROADCAST; + default: + return window.libsession.Constants.TTL_DEFAULT.REGULAR_MESSAGE; + } +}; + +function _getPaddedMessageLength(messageLength) { + const messageLengthWithTerminator = messageLength + 1; + let messagePartCount = Math.floor(messageLengthWithTerminator / 160); + + if (messageLengthWithTerminator % 160 !== 0) { + messagePartCount += 1; + } + + return messagePartCount * 160; +} + +function _convertMessageToText(messageBuffer) { + const plaintext = new Uint8Array( + _getPaddedMessageLength(messageBuffer.byteLength + 1) - 1 + ); + plaintext.set(new Uint8Array(messageBuffer)); + plaintext[messageBuffer.byteLength] = 0x80; + + return plaintext; +} + +function _getPlaintext(messageBuffer) { + return _convertMessageToText(messageBuffer); +} + +function wrapInWebsocketMessage(outgoingObject, timestamp) { + const source = + outgoingObject.type === + textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER + ? null + : outgoingObject.ourKey; + + const messageEnvelope = new textsecure.protobuf.Envelope({ + type: outgoingObject.type, + source, + sourceDevice: outgoingObject.sourceDevice, + timestamp, + content: outgoingObject.content, + }); + const requestMessage = new textsecure.protobuf.WebSocketRequestMessage({ + id: new Uint8Array(libsignal.crypto.getRandomBytes(1))[0], // random ID for now + verb: 'PUT', + path: '/api/v1/message', + body: messageEnvelope.encode().toArrayBuffer(), + }); + const websocketMessage = new textsecure.protobuf.WebSocketMessage({ + type: textsecure.protobuf.WebSocketMessage.Type.REQUEST, + request: requestMessage, + }); + const bytes = new Uint8Array(websocketMessage.encode().toArrayBuffer()); + return bytes; +} + +function getStaleDeviceIdsForNumber(number) { + return textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => { + if (deviceIds.length === 0) { + return [1]; + } + const updateDevices = []; + return Promise.all( + deviceIds.map(deviceId => { + const address = new libsignal.SignalProtocolAddress(number, deviceId); + const sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + return sessionCipher.hasOpenSession().then(hasSession => { + if (!hasSession) { + updateDevices.push(deviceId); + } + }); + }) + ).then(() => updateDevices); + }); +} + +function OutgoingMessage( + server, + timestamp, + numbers, + message, + silent, + callback, + options = {} +) { + if (message instanceof textsecure.protobuf.DataMessage) { + const content = new textsecure.protobuf.Content(); + content.dataMessage = message; + // eslint-disable-next-line no-param-reassign + message = content; + } + this.server = server; + this.timestamp = timestamp; + this.numbers = numbers; + this.message = message; // ContentMessage proto + this.callback = callback; + this.silent = silent; + + this.numbersCompleted = 0; + this.errors = []; + this.successfulNumbers = []; + this.fallBackEncryption = false; + this.failoverNumbers = []; + this.unidentifiedDeliveries = []; + + const { + numberInfo, + senderCertificate, + online, + messageType, + isPublic, + isMediumGroup, + publicSendData, + autoSession, + } = options || {}; + this.numberInfo = numberInfo; + this.isPublic = isPublic; + this.isMediumGroup = !!isMediumGroup; + this.isGroup = !!( + this.message && + this.message.dataMessage && + this.message.dataMessage.group + ); + this.publicSendData = publicSendData; + this.senderCertificate = senderCertificate; + this.online = online; + this.messageType = messageType || 'outgoing'; + this.autoSession = autoSession || false; +} + +OutgoingMessage.prototype = { + constructor: OutgoingMessage, + numberCompleted() { + this.numbersCompleted += 1; + if (this.numbersCompleted >= this.numbers.length) { + this.callback({ + successfulNumbers: this.successfulNumbers, + failoverNumbers: this.failoverNumbers, + errors: this.errors, + unidentifiedDeliveries: this.unidentifiedDeliveries, + messageType: this.messageType, + }); + } + }, + registerError(number, reason, error) { + if (!error || (error.name === 'HTTPError' && error.code !== 404)) { + // eslint-disable-next-line no-param-reassign + error = new textsecure.OutgoingMessageError( + number, + this.message.toArrayBuffer(), + this.timestamp, + error + ); + } + + // eslint-disable-next-line no-param-reassign + error.number = number; + // eslint-disable-next-line no-param-reassign + error.reason = reason; + this.errors[this.errors.length] = error; + this.numberCompleted(); + }, + reloadDevicesAndSend(primaryPubKey, multiDevice = true) { + const ourNumber = textsecure.storage.user.getNumber(); + + if (!multiDevice) { + if (primaryPubKey === ourNumber) { + return Promise.resolve(); + } + + return this.doSendMessage(primaryPubKey, [primaryPubKey]); + } + + return ( + window.libsession.Protocols.MultiDeviceProtocol.getAllDevices( + primaryPubKey + ) + // Don't send to ourselves + .then(devicesPubKeys => + devicesPubKeys.filter(pubKey => pubKey.key !== ourNumber) + ) + .then(devicesPubKeys => { + if (devicesPubKeys.length === 0) { + // No need to start the sending of message without a recipient + return Promise.resolve(); + } + return this.doSendMessage(primaryPubKey, devicesPubKeys); + }) + ); + }, + + getKeysForNumber(number, updateDevices) { + const handleResult = response => + Promise.all( + response.devices.map(device => { + // eslint-disable-next-line no-param-reassign + device.identityKey = response.identityKey; + if ( + updateDevices === undefined || + updateDevices.indexOf(device.deviceId) > -1 + ) { + const address = new libsignal.SignalProtocolAddress( + number, + device.deviceId + ); + const builder = new libsignal.SessionBuilder( + textsecure.storage.protocol, + address + ); + if (device.registrationId === 0) { + window.log.info('device registrationId 0!'); + } + return builder + .processPreKey(device) + .then(async () => { + // TODO: only remove the keys that were used above! + await libloki.storage.removeContactPreKeyBundle(number); + return true; + }) + .catch(error => { + if (error.message === 'Identity key changed') { + // eslint-disable-next-line no-param-reassign + error.timestamp = this.timestamp; + // eslint-disable-next-line no-param-reassign + error.originalMessage = this.message.toArrayBuffer(); + // eslint-disable-next-line no-param-reassign + error.identityKey = device.identityKey; + } + throw error; + }); + } + + return false; + }) + ); + let promise = Promise.resolve(true); + updateDevices.forEach(device => { + promise = promise.then(() => + Promise.all([ + textsecure.storage.protocol.loadContactPreKey(number), + textsecure.storage.protocol.loadContactSignedPreKey(number), + ]) + .then(keys => { + const [preKey, signedPreKey] = keys; + if (preKey === undefined || signedPreKey === undefined) { + return false; + } + const identityKey = StringView.hexToArrayBuffer(number); + return handleResult({ + identityKey, + devices: [ + { deviceId: device, preKey, signedPreKey, registrationId: 0 }, + ], + }).then(results => results.every(value => value === true)); + }) + .catch(e => { + throw e; + }) + ); + }); + + return promise; + }, + + // Default ttl to 24 hours if no value provided + async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60 * 1000) { + const pubKey = number; + + try { + // TODO: Make NUM_CONCURRENT_CONNECTIONS a global constant + const options = { + numConnections: NUM_SEND_CONNECTIONS, + }; + options.isPublic = this.isPublic; + if (this.isPublic) { + options.publicSendData = this.publicSendData; + } + await lokiMessageAPI.sendMessage(pubKey, data, timestamp, ttl, options); + } catch (e) { + if (e.name === 'HTTPError' && e.code !== 409 && e.code !== 410) { + throw new textsecure.SendMessageNetworkError(number, '', e, timestamp); + } else if (e.name === 'TimedOutError') { + throw new textsecure.PoWError(number, e); + } + throw e; + } + }, + + async buildMessage(devicePubKey) { + const updatedDevices = await getStaleDeviceIdsForNumber(devicePubKey); + const keysFound = await this.getKeysForNumber(devicePubKey, updatedDevices); + + // Check if we need to attach the preKeys + const enableFallBackEncryption = !keysFound; + const flags = this.message.dataMessage + ? this.message.dataMessage.get_flags() + : null; + // END_SESSION means Session reset message + const isEndSession = + flags === textsecure.protobuf.DataMessage.Flags.END_SESSION; + const isSessionRequest = false; + + if (enableFallBackEncryption || isEndSession) { + // Encrypt them with the fallback + const pkb = await libloki.storage.getPreKeyBundleForContact(devicePubKey); + this.message.preKeyBundleMessage = new textsecure.protobuf.PreKeyBundleMessage( + pkb + ); + window.log.info('attaching prekeys to outgoing message'); + } + + const messageBuffer = this.message.toArrayBuffer(); + const logDetails = { + message: this.message, + }; + + const ourPubKey = textsecure.storage.user.getNumber(); + const ourPrimaryPubkey = window.storage.get('primaryDevicePubKey'); + const secondaryPubKeys = + (await window.libsession.Protocols.MultiDeviceProtocol.getSecondaryDevices( + ourPubKey + )) || []; + let aliasedPubkey = devicePubKey; + if (devicePubKey === ourPubKey) { + aliasedPubkey = 'OUR_PUBKEY'; // should not happen + } else if (devicePubKey === ourPrimaryPubkey) { + aliasedPubkey = 'OUR_PRIMARY_PUBKEY'; + } else if (secondaryPubKeys.some(device => device.key === devicePubKey)) { + aliasedPubkey = 'OUR SECONDARY PUBKEY'; + } + libloki.api.debug.logSessionMessageSending( + `Sending :${this.messageType} message to ${aliasedPubkey} details:`, + logDetails + ); + + const plaintext = _getPlaintext(messageBuffer); + + // No limit on message keys if we're communicating with our other devices + // FIXME options not used at all; if (ourPubkey === number) { + // options.messageKeysLimit = false; + // } + const ttl = getTTLForType(this.messageType); + const ourKey = textsecure.storage.user.getNumber(); + + return { + ttl, + ourKey, + sourceDevice: 1, + plaintext, + pubKey: devicePubKey, + isSessionRequest, + enableFallBackEncryption, + }; + }, + + async encryptMessage(clearMessage) { + if (clearMessage === null) { + window.log.warn( + 'clearMessage is null on encryptMessage... Returning null' + ); + return null; + } + const { + ttl, + ourKey, + sourceDevice, + plaintext, + pubKey, + isSessionRequest, + enableFallBackEncryption, + } = clearMessage; + // Session doesn't use the deviceId scheme, it's always 1. + // Instead, there are multiple device public keys. + const deviceId = 1; + + const address = new libsignal.SignalProtocolAddress(pubKey, deviceId); + + let sessionCipher; + + if (enableFallBackEncryption) { + sessionCipher = new libloki.crypto.FallBackSessionCipher(address); + } else { + sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + } + + const innerCiphertext = await sessionCipher.encrypt(plaintext); + + const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher( + textsecure.storage.protocol + ); + + const senderCert = new textsecure.protobuf.SenderCertificate(); + + senderCert.sender = ourKey; + senderCert.senderDevice = deviceId; + + const ciphertext = await secretSessionCipher.encrypt( + address.getName(), + senderCert, + innerCiphertext + ); + const type = textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER; + const content = window.Signal.Crypto.arrayBufferToBase64(ciphertext); + + return { + type, + ttl, + ourKey, + sourceDevice, + content, + pubKey, + isSessionRequest, + }; + }, + // Send a message to a public group + async sendPublicMessage(number) { + await this.transmitMessage( + number, + this.message.dataMessage, + this.timestamp, + 0 // ttl + ); + + this.successfulNumbers[this.successfulNumbers.length] = number; + this.numberCompleted(); + }, + // Send a message to a private group member or a session chat (one to one) + async sendSessionMessage(outgoingObjects) { + // TODO: handle multiple devices/messages per transmit + const promises = outgoingObjects.map(async outgoingObject => { + if (!outgoingObject) { + return; + } + const { pubKey: destination, ttl } = outgoingObject; + + try { + const socketMessage = wrapInWebsocketMessage( + outgoingObject, + this.timestamp + ); + await this.transmitMessage( + destination, + socketMessage, + this.timestamp, + ttl + ); + this.successfulNumbers.push(destination); + } catch (e) { + e.number = destination; + this.errors.push(e); + } + }); + + await Promise.all(promises); + + this.numbersCompleted += this.successfulNumbers.length; + this.numberCompleted(); + }, + async buildAndEncrypt(devicePubKey) { + const clearMessage = await this.buildMessage(devicePubKey); + return this.encryptMessage(clearMessage); + }, + // eslint-disable-next-line no-unused-vars + async doSendMessage(primaryPubKey, devicesPubKeys) { + if (this.isPublic) { + await this.sendPublicMessage(primaryPubKey); + return; + } + this.numbers = devicesPubKeys; + + if (this.isMediumGroup) { + await this.sendMediumGroupMessage(primaryPubKey); + return; + } + + const outgoingObjects = await Promise.all( + devicesPubKeys.map(pk => this.buildAndEncrypt(pk, primaryPubKey)) + ); + + this.sendSessionMessage(outgoingObjects); + }, + + sendToNumber(number, multiDevice = true) { + return this.reloadDevicesAndSend(number, multiDevice).catch(error => { + if (error.message === 'Identity key changed') { + // eslint-disable-next-line no-param-reassign + error = new textsecure.OutgoingIdentityKeyError( + number, + error.originalMessage, + error.timestamp, + error.identityKey + ); + this.registerError(number, 'Identity key changed', error); + } else { + this.registerError( + number, + `Failed to retrieve new device keys for number ${number}`, + error + ); + } + }); + }, +}; + +window.textsecure = window.textsecure || {}; +window.textsecure.OutgoingMessage = OutgoingMessage; diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 1b45085e2..f09aaa36d 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -458,6 +458,7 @@ MessageSender.prototype = { if (myDevice !== 1 && myDevice !== '1') { const syncReadMessages = new libsession.Messages.Outgoing.SyncReadMessage( { + timestamp: Date.now(), readMessages: reads, } ); diff --git a/ts/components/DevicePairingDialog.tsx b/ts/components/DevicePairingDialog.tsx index e56bba982..c15604d88 100644 --- a/ts/components/DevicePairingDialog.tsx +++ b/ts/components/DevicePairingDialog.tsx @@ -262,14 +262,6 @@ export class DevicePairingDialog extends React.Component { private requestReceived(secondaryDevicePubKey: string | EventHandlerNonNull) { // FIFO: push at the front of the array with unshift() this.state.pubKeyRequests.unshift(secondaryDevicePubKey); - window.pushToast({ - title: window.i18n('gotPairingRequest'), - description: `${window.shortenPubkey( - secondaryDevicePubKey - )} ${window.i18n( - 'showPairingWordsTitle' - )}: ${window.mnemonic.pubkey_to_secret_words(secondaryDevicePubKey)}`, - }); if (!this.state.currentPubKey) { this.nextPubKey(); this.stopReceivingRequests(); diff --git a/ts/components/EditProfileDialog.tsx b/ts/components/EditProfileDialog.tsx index 54abe0337..0c21c7e9c 100644 --- a/ts/components/EditProfileDialog.tsx +++ b/ts/components/EditProfileDialog.tsx @@ -337,7 +337,7 @@ export class EditProfileDialog extends React.Component { setProfileName: this.state.profileName, }, () => { - // Update settinngs in dialog complete; + // Update settings in dialog complete; // now callback to reloadactions panel avatar this.props.callback(this.state.avatar); } diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index 296cd02b4..2664b6ba0 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -24,6 +24,7 @@ interface Props { } export class ActionsPanel extends React.Component { + private ourConversation: any; constructor(props: Props) { super(props); this.state = { @@ -31,6 +32,7 @@ export class ActionsPanel extends React.Component { }; this.editProfileHandle = this.editProfileHandle.bind(this); + this.refreshAvatarCallback = this.refreshAvatarCallback.bind(this); } public componentDidMount() { @@ -42,10 +44,36 @@ export class ActionsPanel extends React.Component { this.setState({ avatarPath: conversation.getAvatarPath(), }); + // When our primary device updates its avatar, we will need for a message sync to know about that. + // Once we get the avatar update, we need to refresh this react component. + // So we listen to changes on our profile avatar and use the updated avatarPath (done on message received). + this.ourConversation = conversation; + + this.ourConversation.on( + 'change', + () => { + this.refreshAvatarCallback(this.ourConversation); + }, + 'refreshAvatarCallback' + ); } ); } + public refreshAvatarCallback(conversation: any) { + if (conversation.changed?.profileAvatar) { + this.setState({ + avatarPath: conversation.getAvatarPath(), + }); + } + } + + public componentWillUnmount() { + if (this.ourConversation) { + this.ourConversation.off('change', null, 'refreshAvatarCallback'); + } + } + public Section = ({ isSelected, onSelect, diff --git a/ts/session/messages/outgoing/content/link/DeviceLinkGrantMessage.ts b/ts/session/messages/outgoing/content/link/DeviceLinkGrantMessage.ts index a07217104..af4750782 100644 --- a/ts/session/messages/outgoing/content/link/DeviceLinkGrantMessage.ts +++ b/ts/session/messages/outgoing/content/link/DeviceLinkGrantMessage.ts @@ -25,6 +25,13 @@ export class DeviceLinkGrantMessage extends DeviceLinkRequestMessage { requestSignature: params.requestSignature, }); + if (!(params.lokiProfile.profileKey instanceof Uint8Array)) { + throw new TypeError('profileKey must be of type Uint8Array'); + } + if (!(params.grantSignature instanceof Uint8Array)) { + throw new TypeError('grantSignature must be of type Uint8Array'); + } + this.displayName = params.lokiProfile.displayName; this.avatarPointer = params.lokiProfile.avatarPointer; this.profileKey = params.lokiProfile.profileKey; diff --git a/ts/session/messages/outgoing/content/link/DeviceLinkRequestMessage.ts b/ts/session/messages/outgoing/content/link/DeviceLinkRequestMessage.ts index ab4e91ef9..86454c786 100644 --- a/ts/session/messages/outgoing/content/link/DeviceLinkRequestMessage.ts +++ b/ts/session/messages/outgoing/content/link/DeviceLinkRequestMessage.ts @@ -15,6 +15,16 @@ export class DeviceLinkRequestMessage extends ContentMessage { constructor(params: DeviceLinkMessageParams) { super({ timestamp: params.timestamp, identifier: params.identifier }); + + if (!(params.requestSignature instanceof Uint8Array)) { + throw new TypeError('requestSignature must be of type Uint8Array'); + } + if (typeof params.primaryDevicePubKey !== 'string') { + throw new TypeError('primaryDevicePubKey must be of type string'); + } + if (typeof params.secondaryDevicePubKey !== 'string') { + throw new TypeError('secondaryDevicePubKey must be of type string'); + } this.primaryDevicePubKey = params.primaryDevicePubKey; this.secondaryDevicePubKey = params.secondaryDevicePubKey; this.requestSignature = params.requestSignature; diff --git a/ts/session/snode_api/serviceNodeAPI.ts b/ts/session/snode_api/serviceNodeAPI.ts index 2742fa9a4..3d8aa459f 100644 --- a/ts/session/snode_api/serviceNodeAPI.ts +++ b/ts/session/snode_api/serviceNodeAPI.ts @@ -298,8 +298,6 @@ export async function storeOnNode( return false; } - const res = snodeRes as any; - const json = JSON.parse(snodeRes.body); // Make sure we aren't doing too much PoW const currentDifficulty = window.storage.get('PoWDifficulty', null);