|  |  |  | /* global window, textsecure, Whisper, dcodeIO, StringView, ConversationController */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // eslint-disable-next-line func-names
 | 
					
						
							|  |  |  | (function() { | 
					
						
							|  |  |  |   window.libloki = window.libloki || {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async function sendBackgroundMessage(pubKey) { | 
					
						
							|  |  |  |     return sendOnlineBroadcastMessage(pubKey); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async function sendOnlineBroadcastMessage(pubKey, isPing = false) { | 
					
						
							|  |  |  |     const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey( | 
					
						
							|  |  |  |       pubKey | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     if (authorisation && authorisation.primaryDevicePubKey !== pubKey) { | 
					
						
							|  |  |  |       sendOnlineBroadcastMessage(authorisation.primaryDevicePubKey); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const p2pAddress = null; | 
					
						
							|  |  |  |     const p2pPort = null; | 
					
						
							|  |  |  |     // We result loki address message for sending "background" messages
 | 
					
						
							|  |  |  |     const type = textsecure.protobuf.LokiAddressMessage.Type.HOST_UNREACHABLE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const lokiAddressMessage = new textsecure.protobuf.LokiAddressMessage({ | 
					
						
							|  |  |  |       p2pAddress, | 
					
						
							|  |  |  |       p2pPort, | 
					
						
							|  |  |  |       type, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     const content = new textsecure.protobuf.Content({ | 
					
						
							|  |  |  |       lokiAddressMessage, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const options = { messageType: 'onlineBroadcast', isPing }; | 
					
						
							|  |  |  |     // Send a empty message with information about how to contact us directly
 | 
					
						
							|  |  |  |     const outgoingMessage = new textsecure.OutgoingMessage( | 
					
						
							|  |  |  |       null, // server
 | 
					
						
							|  |  |  |       Date.now(), // timestamp,
 | 
					
						
							|  |  |  |       [pubKey], // numbers
 | 
					
						
							|  |  |  |       content, // message
 | 
					
						
							|  |  |  |       true, // silent
 | 
					
						
							|  |  |  |       () => null, // callback
 | 
					
						
							|  |  |  |       options | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     await outgoingMessage.sendToNumber(pubKey); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function createPairingAuthorisationProtoMessage({ | 
					
						
							|  |  |  |     primaryDevicePubKey, | 
					
						
							|  |  |  |     secondaryDevicePubKey, | 
					
						
							|  |  |  |     requestSignature, | 
					
						
							|  |  |  |     grantSignature, | 
					
						
							|  |  |  |   }) { | 
					
						
							|  |  |  |     if (!primaryDevicePubKey || !secondaryDevicePubKey || !requestSignature) { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							|  |  |  |         'createPairingAuthorisationProtoMessage: pubkeys missing' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (requestSignature.constructor !== ArrayBuffer) { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							|  |  |  |         'createPairingAuthorisationProtoMessage expects a signature as ArrayBuffer' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (grantSignature && grantSignature.constructor !== ArrayBuffer) { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							|  |  |  |         'createPairingAuthorisationProtoMessage expects a signature as ArrayBuffer' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return new textsecure.protobuf.PairingAuthorisationMessage({ | 
					
						
							|  |  |  |       requestSignature: new Uint8Array(requestSignature), | 
					
						
							|  |  |  |       grantSignature: grantSignature ? new Uint8Array(grantSignature) : null, | 
					
						
							|  |  |  |       primaryDevicePubKey, | 
					
						
							|  |  |  |       secondaryDevicePubKey, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function sendUnpairingMessageToSecondary(pubKey) { | 
					
						
							|  |  |  |     const flags = textsecure.protobuf.DataMessage.Flags.UNPAIRING_REQUEST; | 
					
						
							|  |  |  |     const dataMessage = new textsecure.protobuf.DataMessage({ | 
					
						
							|  |  |  |       flags, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     const content = new textsecure.protobuf.Content({ | 
					
						
							|  |  |  |       dataMessage, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     const options = { messageType: 'device-unpairing' }; | 
					
						
							|  |  |  |     const outgoingMessage = new textsecure.OutgoingMessage( | 
					
						
							|  |  |  |       null, // server
 | 
					
						
							|  |  |  |       Date.now(), // timestamp,
 | 
					
						
							|  |  |  |       [pubKey], // numbers
 | 
					
						
							|  |  |  |       content, // message
 | 
					
						
							|  |  |  |       true, // silent
 | 
					
						
							|  |  |  |       () => null, // callback
 | 
					
						
							|  |  |  |       options | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return outgoingMessage.sendToNumber(pubKey); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   // Serialise as <Element0.length><Element0><Element1.length><Element1>...
 | 
					
						
							|  |  |  |   // This is an implementation of the reciprocal of contacts_parser.js
 | 
					
						
							|  |  |  |   function serialiseByteBuffers(buffers) { | 
					
						
							|  |  |  |     const result = new dcodeIO.ByteBuffer(); | 
					
						
							|  |  |  |     buffers.forEach(buffer => { | 
					
						
							|  |  |  |       // bytebuffer container expands and increments
 | 
					
						
							|  |  |  |       // offset automatically
 | 
					
						
							|  |  |  |       result.writeInt32(buffer.limit); | 
					
						
							|  |  |  |       result.append(buffer); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     result.limit = result.offset; | 
					
						
							|  |  |  |     result.reset(); | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   async function createContactSyncProtoMessage(conversations) { | 
					
						
							|  |  |  |     // Extract required contacts information out of conversations
 | 
					
						
							|  |  |  |     const rawContacts = await Promise.all( | 
					
						
							|  |  |  |       conversations.map(async conversation => { | 
					
						
							|  |  |  |         const profile = conversation.getLokiProfile(); | 
					
						
							|  |  |  |         const number = conversation.getNumber(); | 
					
						
							|  |  |  |         const name = profile | 
					
						
							|  |  |  |           ? profile.displayName | 
					
						
							|  |  |  |           : conversation.getProfileName(); | 
					
						
							|  |  |  |         const status = await conversation.safeGetVerified(); | 
					
						
							|  |  |  |         const protoState = textsecure.storage.protocol.convertVerifiedStatusToProtoState( | 
					
						
							|  |  |  |           status | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         const verified = new textsecure.protobuf.Verified({ | 
					
						
							|  |  |  |           state: protoState, | 
					
						
							|  |  |  |           destination: number, | 
					
						
							|  |  |  |           identityKey: StringView.hexToArrayBuffer(number), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |           name, | 
					
						
							|  |  |  |           verified, | 
					
						
							|  |  |  |           number, | 
					
						
							|  |  |  |           nickname: conversation.getNickname(), | 
					
						
							|  |  |  |           blocked: conversation.isBlocked(), | 
					
						
							|  |  |  |           expireTimer: conversation.get('expireTimer'), | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     // Convert raw contacts to an array of buffers
 | 
					
						
							|  |  |  |     const contactDetails = rawContacts | 
					
						
							|  |  |  |       .filter(x => x.number !== textsecure.storage.user.getNumber()) | 
					
						
							|  |  |  |       .map(x => new textsecure.protobuf.ContactDetails(x)) | 
					
						
							|  |  |  |       .map(x => x.encode()); | 
					
						
							|  |  |  |     // Serialise array of byteBuffers into 1 byteBuffer
 | 
					
						
							|  |  |  |     const byteBuffer = serialiseByteBuffers(contactDetails); | 
					
						
							|  |  |  |     const data = new Uint8Array(byteBuffer.toArrayBuffer()); | 
					
						
							|  |  |  |     const contacts = new textsecure.protobuf.SyncMessage.Contacts({ | 
					
						
							|  |  |  |       data, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     const syncMessage = new textsecure.protobuf.SyncMessage({ | 
					
						
							|  |  |  |       contacts, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     return syncMessage; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   async function sendPairingAuthorisation(authorisation, recipientPubKey) { | 
					
						
							|  |  |  |     const pairingAuthorisation = createPairingAuthorisationProtoMessage( | 
					
						
							|  |  |  |       authorisation | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const ourNumber = textsecure.storage.user.getNumber(); | 
					
						
							|  |  |  |     const ourConversation = await ConversationController.getOrCreateAndWait( | 
					
						
							|  |  |  |       ourNumber, | 
					
						
							|  |  |  |       'private' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const content = new textsecure.protobuf.Content({ | 
					
						
							|  |  |  |       pairingAuthorisation, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     const isGrant = authorisation.primaryDevicePubKey === ourNumber; | 
					
						
							|  |  |  |     if (isGrant) { | 
					
						
							|  |  |  |       // Send profile name to secondary device
 | 
					
						
							|  |  |  |       const lokiProfile = ourConversation.getLokiProfile(); | 
					
						
							|  |  |  |       // profile.avatar is the path to the local image
 | 
					
						
							|  |  |  |       // replace with the avatar URL
 | 
					
						
							|  |  |  |       const avatarPointer = ourConversation.get('avatarPointer'); | 
					
						
							|  |  |  |       lokiProfile.avatar = avatarPointer; | 
					
						
							|  |  |  |       const profile = new textsecure.protobuf.DataMessage.LokiProfile( | 
					
						
							|  |  |  |         lokiProfile | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       const profileKey = window.storage.get('profileKey'); | 
					
						
							|  |  |  |       const dataMessage = new textsecure.protobuf.DataMessage({ | 
					
						
							|  |  |  |         profile, | 
					
						
							|  |  |  |         profileKey, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       // Attach contact list
 | 
					
						
							|  |  |  |       const conversations = await window.Signal.Data.getConversationsWithFriendStatus( | 
					
						
							|  |  |  |         window.friends.friendRequestStatusEnum.friends, | 
					
						
							|  |  |  |         { ConversationCollection: Whisper.ConversationCollection } | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       const syncMessage = await createContactSyncProtoMessage(conversations); | 
					
						
							|  |  |  |       content.syncMessage = syncMessage; | 
					
						
							|  |  |  |       content.dataMessage = dataMessage; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // Send
 | 
					
						
							|  |  |  |     const options = { messageType: 'pairing-request' }; | 
					
						
							|  |  |  |     const p = new Promise((resolve, reject) => { | 
					
						
							|  |  |  |       const timestamp = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const outgoingMessage = new textsecure.OutgoingMessage( | 
					
						
							|  |  |  |         null, // server
 | 
					
						
							|  |  |  |         timestamp, | 
					
						
							|  |  |  |         [recipientPubKey], // numbers
 | 
					
						
							|  |  |  |         content, // message
 | 
					
						
							|  |  |  |         true, // silent
 | 
					
						
							|  |  |  |         result => { | 
					
						
							|  |  |  |           // callback
 | 
					
						
							|  |  |  |           if (result.errors.length > 0) { | 
					
						
							|  |  |  |             reject(result.errors[0]); | 
					
						
							|  |  |  |           } else { | 
					
						
							|  |  |  |             resolve(); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         options | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       outgoingMessage.sendToNumber(recipientPubKey); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     return p; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   window.libloki.api = { | 
					
						
							|  |  |  |     sendBackgroundMessage, | 
					
						
							|  |  |  |     sendOnlineBroadcastMessage, | 
					
						
							|  |  |  |     sendPairingAuthorisation, | 
					
						
							|  |  |  |     createPairingAuthorisationProtoMessage, | 
					
						
							|  |  |  |     sendUnpairingMessageToSecondary, | 
					
						
							|  |  |  |     createContactSyncProtoMessage, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | })(); |