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.
session-desktop/libloki/api.js

342 lines
10 KiB
JavaScript

/* global window, textsecure, dcodeIO, StringView, ConversationController, _ */
/* eslint-disable no-bitwise */
// eslint-disable-next-line func-names
(function() {
window.libloki = window.libloki || {};
const DebugFlagsEnum = {
GROUP_SYNC_MESSAGES: 1,
CONTACT_SYNC_MESSAGES: 2,
AUTO_FRIEND_REQUEST_MESSAGES: 4,
SESSION_REQUEST_MESSAGES: 8,
SESSION_MESSAGE_SENDING: 16,
SESSION_BACKGROUND_MESSAGE: 32,
GROUP_REQUEST_INFO: 64,
NORMAL_FRIEND_REQUEST_MESSAGES: 128,
// If you add any new flag, be sure it is bitwise safe! (unique and 2 multiples)
ALL: 65535,
};
const debugFlags = DebugFlagsEnum.ALL;
const debugLogFn = (...args) => {
if (true) {
// process.env.NODE_ENV.includes('test-integration') ||
window.console.warn(...args);
}
};
function logSessionMessageSending(...args) {
if (debugFlags & DebugFlagsEnum.SESSION_MESSAGE_SENDING) {
debugLogFn(...args);
}
}
function logGroupSync(...args) {
if (debugFlags & DebugFlagsEnum.GROUP_SYNC_MESSAGES) {
debugLogFn(...args);
}
}
function logGroupRequestInfo(...args) {
if (debugFlags & DebugFlagsEnum.GROUP_REQUEST_INFO) {
debugLogFn(...args);
}
}
function logContactSync(...args) {
if (debugFlags & DebugFlagsEnum.GROUP_CONTACT_MESSAGES) {
debugLogFn(...args);
}
}
function logAutoFriendRequest(...args) {
if (debugFlags & DebugFlagsEnum.AUTO_FRIEND_REQUEST_MESSAGES) {
debugLogFn(...args);
}
}
function logNormalFriendRequest(...args) {
if (debugFlags & DebugFlagsEnum.NORMAL_FRIEND_REQUEST_MESSAGES) {
debugLogFn(...args);
}
}
function logSessionRequest(...args) {
if (debugFlags & DebugFlagsEnum.SESSION_REQUEST_MESSAGES) {
debugLogFn(...args);
}
}
function logBackgroundMessage(...args) {
if (debugFlags & DebugFlagsEnum.SESSION_BACKGROUND_MESSAGE) {
debugLogFn(...args);
}
}
// Returns the primary device pubkey for this secondary device pubkey
// or the same pubkey if there is no other device
async function getPrimaryDevicePubkey(pubKey) {
const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey(
pubKey
);
return authorisation ? authorisation.primaryDevicePubKey : pubKey;
}
async function sendBackgroundMessage(pubKey, debugMessageType) {
const primaryPubKey = await getPrimaryDevicePubkey(pubKey);
if (primaryPubKey !== pubKey) {
// if we got the secondary device pubkey first,
// call ourself again with the primary device pubkey
await sendBackgroundMessage(primaryPubKey, debugMessageType);
return;
}
const backgroundMessage = textsecure.OutgoingMessage.buildBackgroundMessage(
pubKey,
debugMessageType
);
await backgroundMessage.sendToNumber(pubKey);
}
async function sendAutoFriendRequestMessage(pubKey) {
const primaryPubKey = await getPrimaryDevicePubkey(pubKey);
if (primaryPubKey !== pubKey) {
// if we got the secondary device pubkey first,
// call ourself again with the primary device pubkey
await sendAutoFriendRequestMessage(primaryPubKey);
return;
}
const autoFrMessage = textsecure.OutgoingMessage.buildAutoFriendRequestMessage(
pubKey
);
await autoFrMessage.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 unpairingMessage = textsecure.OutgoingMessage.buildUnpairingMessage(
pubKey
);
return unpairingMessage.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(sessionContacts) {
if (sessionContacts.length === 0) {
return null;
}
const rawContacts = await Promise.all(
sessionContacts.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;
}
function createGroupSyncProtoMessage(sessionGroup) {
// We are getting a single open group here
const rawGroup = {
id: window.Signal.Crypto.bytesFromString(sessionGroup.id),
name: sessionGroup.get('name'),
members: sessionGroup.get('members') || [],
blocked: sessionGroup.isBlocked(),
expireTimer: sessionGroup.get('expireTimer'),
admins: sessionGroup.get('groupAdmins') || [],
};
// Convert raw group to a buffer
const groupDetail = new textsecure.protobuf.GroupDetails(rawGroup).encode();
// Serialise array of byteBuffers into 1 byteBuffer
const byteBuffer = serialiseByteBuffers([groupDetail]);
const data = new Uint8Array(byteBuffer.toArrayBuffer());
const groups = new textsecure.protobuf.SyncMessage.Groups({
data,
});
const syncMessage = new textsecure.protobuf.SyncMessage({
groups,
});
return syncMessage;
}
function createOpenGroupsSyncProtoMessage(conversations) {
// We only want to sync across open groups that we haven't left
const sessionOpenGroups = conversations.filter(
c => c.isPublic() && !c.isRss() && !c.get('left')
);
if (sessionOpenGroups.length === 0) {
return null;
}
const openGroups = sessionOpenGroups.map(
conversation =>
new textsecure.protobuf.SyncMessage.OpenGroupDetails({
url: conversation.id.split('@').pop(),
channelId: conversation.get('channelId'),
})
);
const syncMessage = new textsecure.protobuf.SyncMessage({
openGroups,
});
return syncMessage;
}
async function sendPairingAuthorisation(authorisation, recipientPubKey) {
const pairingAuthorisation = createPairingAuthorisationProtoMessage(
authorisation
);
const ourNumber = textsecure.storage.user.getNumber();
const ourConversation = await ConversationController.getOrCreateAndWait(
ourNumber,
'private'
);
// Send
const p = new Promise((resolve, reject) => {
const callback = result => {
// callback
if (result.errors.length > 0) {
reject(result.errors[0]);
} else {
resolve();
}
};
const pairingRequestMessage = textsecure.OutgoingMessage.buildPairingRequestMessage(
recipientPubKey,
ourNumber,
ourConversation,
authorisation,
pairingAuthorisation,
callback
);
pairingRequestMessage.sendToNumber(recipientPubKey);
});
return p;
}
function sendSessionRequestsToMembers(members = []) {
// For every member, see if we need to establish a session:
members.forEach(memberPubKey => {
const haveSession = _.some(
textsecure.storage.protocol.sessions,
s => s.number === memberPubKey
);
const ourPubKey = textsecure.storage.user.getNumber();
if (!haveSession && memberPubKey !== ourPubKey) {
// eslint-disable-next-line more/no-then
ConversationController.getOrCreateAndWait(memberPubKey, 'private').then(
() => {
const sessionRequestMessage = textsecure.OutgoingMessage.buildSessionRequestMessage(
memberPubKey
);
sessionRequestMessage.sendToNumber(memberPubKey);
}
);
}
});
}
const debug = {
logContactSync,
logGroupSync,
logAutoFriendRequest,
logSessionRequest,
logSessionMessageSending,
logBackgroundMessage,
logGroupRequestInfo,
logNormalFriendRequest,
};
window.libloki.api = {
sendBackgroundMessage,
sendAutoFriendRequestMessage,
sendSessionRequestsToMembers,
sendPairingAuthorisation,
createPairingAuthorisationProtoMessage,
sendUnpairingMessageToSecondary,
createContactSyncProtoMessage,
createGroupSyncProtoMessage,
createOpenGroupsSyncProtoMessage,
debug,
};
})();