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.
		
		
		
		
		
			
		
			
				
	
	
		
			226 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			226 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
| /* 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,
 | |
|   };
 | |
| })();
 |