Merge pull request #157 from BeaudanBrown/loki-address-broadcast

Loki address broadcast
pull/160/head
sachaaaaa 6 years ago committed by GitHub
commit 9c98a1f654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -102,6 +102,7 @@ module.exports = grunt => {
libloki: { libloki: {
src: [ src: [
'libloki/api.js', 'libloki/api.js',
'libloki/friends.js',
'libloki/crypto.js', 'libloki/crypto.js',
'libloki/service_nodes.js', 'libloki/service_nodes.js',
'libloki/storage.js', 'libloki/storage.js',

@ -90,6 +90,7 @@ module.exports = {
updateConversation, updateConversation,
removeConversation, removeConversation,
getAllConversations, getAllConversations,
getPubKeysWithFriendStatus,
getAllConversationIds, getAllConversationIds,
getAllPrivateConversations, getAllPrivateConversations,
getAllGroupsInvolvingId, getAllGroupsInvolvingId,
@ -1127,7 +1128,7 @@ async function getSwarmNodesByPubkey(pubkey) {
}); });
if (!row) { if (!row) {
return null; return [];
} }
return jsonToObject(row.json).swarmNodes; return jsonToObject(row.json).swarmNodes;
@ -1280,6 +1281,18 @@ async function getAllConversations() {
return map(rows, row => jsonToObject(row.json)); return map(rows, row => jsonToObject(row.json));
} }
async function getPubKeysWithFriendStatus(status) {
const rows = await db.all(
`SELECT id FROM conversations WHERE
friendRequestStatus = $status
ORDER BY id ASC;`,
{
$status: status,
}
);
return map(rows, row => row.id);
}
async function getAllConversationIds() { async function getAllConversationIds() {
const rows = await db.all('SELECT id FROM conversations ORDER BY id ASC;'); const rows = await db.all('SELECT id FROM conversations ORDER BY id ASC;');
return map(rows, row => row.id); return map(rows, row => row.id);

@ -1,6 +1,7 @@
{ {
"serverUrl": "random.snode", "serverUrl": "random.snode",
"cdnUrl": "random.snode", "cdnUrl": "random.snode",
"localServerPort": "8081",
"messageServerPort": "8080", "messageServerPort": "8080",
"swarmServerPort": "8079", "swarmServerPort": "8079",
"disableAutoUpdate": false, "disableAutoUpdate": false,

@ -1,5 +1,6 @@
{ {
"storageProfile": "development1", "storageProfile": "development1",
"localServerPort": "8082",
"disableAutoUpdate": true, "disableAutoUpdate": true,
"openDevTools": true "openDevTools": true
} }

@ -182,11 +182,10 @@
} }
try { try {
const swarmNodes = await window.LokiSnodeAPI.getFreshSwarmNodes(id);
conversation.set({ swarmNodes});
await window.Signal.Data.saveConversation(conversation.attributes, { await window.Signal.Data.saveConversation(conversation.attributes, {
Conversation: Whisper.Conversation, Conversation: Whisper.Conversation,
}); });
window.LokiSnodeAPI.refreshSwarmNodesForPubKey(id);
} catch (error) { } catch (error) {
window.log.error( window.log.error(
'Conversation save failed! ', 'Conversation save failed! ',

@ -41,18 +41,7 @@
} = window.Signal.Migrations; } = window.Signal.Migrations;
// Possible conversation friend states // Possible conversation friend states
const FriendRequestStatusEnum = Object.freeze({ const FriendRequestStatusEnum = window.friends.friendRequestStatusEnum;
// New conversation, no messages sent or received
none: 0,
// This state is used to lock the input early while sending
pendingSend: 1,
// Friend request sent, awaiting response
requestSent: 2,
// Friend request received, awaiting user input
requestReceived: 3,
// We did it!
friends: 4,
});
// Possible session reset states // Possible session reset states
const SessionResetEnum = Object.freeze({ const SessionResetEnum = Object.freeze({

@ -110,6 +110,7 @@ module.exports = {
removeAllSessions, removeAllSessions,
getSwarmNodesByPubkey, getSwarmNodesByPubkey,
saveSwarmNodesForPubKey,
getConversationCount, getConversationCount,
saveConversation, saveConversation,
@ -120,6 +121,7 @@ module.exports = {
_removeConversations, _removeConversations,
getAllConversations, getAllConversations,
getPubKeysWithFriendStatus,
getAllConversationIds, getAllConversationIds,
getAllPrivateConversations, getAllPrivateConversations,
getAllGroupsInvolvingId, getAllGroupsInvolvingId,
@ -673,6 +675,14 @@ async function getSwarmNodesByPubkey(pubkey) {
return channels.getSwarmNodesByPubkey(pubkey); return channels.getSwarmNodesByPubkey(pubkey);
} }
async function saveSwarmNodesForPubKey(pubKey, swarmNodes, { Conversation }) {
const conversation = await getConversationById(pubKey, { Conversation });
conversation.set({ swarmNodes });
await updateConversation(conversation.id, conversation.attributes, {
Conversation,
});
}
async function getConversationCount() { async function getConversationCount() {
return channels.getConversationCount(); return channels.getConversationCount();
} }
@ -721,6 +731,10 @@ async function _removeConversations(ids) {
await channels.removeConversation(ids); await channels.removeConversation(ids);
} }
async function getPubKeysWithFriendStatus(status) {
return channels.getPubKeysWithFriendStatus(status);
}
async function getAllConversations({ ConversationCollection }) { async function getAllConversations({ ConversationCollection }) {
const conversations = await channels.getAllConversations(); const conversations = await channels.getAllConversations();

@ -1,6 +1,6 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
/* eslint-disable no-loop-func */ /* eslint-disable no-loop-func */
/* global log, dcodeIO, window, callWorker */ /* global log, dcodeIO, window, callWorker, Whisper */
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const _ = require('lodash'); const _ = require('lodash');
@ -105,20 +105,15 @@ class LokiMessageAPI {
throw HTTPError('sendMessage: error response', response.status, result); throw HTTPError('sendMessage: error response', response.status, result);
}; };
let swarmNodes; let swarmNodes = await window.Signal.Data.getSwarmNodesByPubkey(pubKey);
try {
swarmNodes = await window.LokiSnodeAPI.getSwarmNodesByPubkey(pubKey);
} catch (e) {
throw new window.textsecure.EmptySwarmError(pubKey, e);
}
while (successfulRequests < MINIMUM_SUCCESSFUL_REQUESTS) { while (successfulRequests < MINIMUM_SUCCESSFUL_REQUESTS) {
if (!canResolve) { if (!canResolve) {
throw new window.textsecure.DNSResolutionError('Sending messages'); throw new window.textsecure.DNSResolutionError('Sending messages');
} }
if (!swarmNodes || swarmNodes.length === 0) { if (swarmNodes.length === 0) {
swarmNodes = await window.LokiSnodeAPI.getFreshSwarmNodes(pubKey); swarmNodes = await window.LokiSnodeAPI.getFreshSwarmNodes(pubKey);
swarmNodes = _.difference(swarmNodes, completedNodes); swarmNodes = _.difference(swarmNodes, completedNodes);
if (!swarmNodes || swarmNodes.length === 0) { if (swarmNodes.length === 0) {
if (successfulRequests !== 0) { if (successfulRequests !== 0) {
// TODO: Decide how to handle some completed requests but not enough // TODO: Decide how to handle some completed requests but not enough
return; return;
@ -128,7 +123,9 @@ class LokiMessageAPI {
new Error('Ran out of swarm nodes to query') new Error('Ran out of swarm nodes to query')
); );
} }
await window.LokiSnodeAPI.saveSwarmNodes(pubKey, swarmNodes); await window.Signal.Data.saveSwarmNodesForPubKey(pubKey, swarmNodes, {
Conversation: Whisper.Conversation,
});
} }
const remainingRequests = const remainingRequests =
MINIMUM_SUCCESSFUL_REQUESTS - completedNodes.length; MINIMUM_SUCCESSFUL_REQUESTS - completedNodes.length;

@ -0,0 +1,24 @@
class LokiP2pAPI {
constructor() {
this.contactP2pDetails = {};
}
addContactP2pDetails(pubKey, address, port) {
this.contactP2pDetails[pubKey] = {
address,
port,
};
}
getContactP2pDetails(pubKey) {
return this.contactP2pDetails[pubKey] || null;
}
removeContactP2pDetails(pubKey) {
delete this.contactP2pDetails[pubKey];
}
}
module.exports = {
LokiP2pAPI,
};

@ -106,24 +106,11 @@ class LokiSnodeAPI {
return this.ourSwarmNodes; return this.ourSwarmNodes;
} }
async getSwarmNodesByPubkey(pubKey) { async refreshSwarmNodesForPubKey(pubKey) {
const swarmNodes = await window.Signal.Data.getSwarmNodesByPubkey(pubKey); const newNodes = await this.getFreshSwarmNodes(pubKey);
if (swarmNodes) { await window.Signal.Data.saveSwarmNodesForPubKey(pubKey, newNodes, {
return swarmNodes;
}
return [];
}
async saveSwarmNodes(pubKey, swarmNodes) {
const conversation = window.ConversationController.get(pubKey);
conversation.set({ swarmNodes });
await window.Signal.Data.updateConversation(
conversation.id,
conversation.attributes,
{
Conversation: Whisper.Conversation, Conversation: Whisper.Conversation,
} });
);
} }
async getFreshSwarmNodes(pubKey) { async getFreshSwarmNodes(pubKey) {

@ -8,6 +8,53 @@
return sendEmptyMessage(pubKey, true); return sendEmptyMessage(pubKey, true);
} }
async function broadcastOnlineStatus() {
const friendKeys = await window.Signal.Data.getPubKeysWithFriendStatus(
window.friends.friendRequestStatusEnum.friends
);
await Promise.all(
friendKeys.map(async pubKey => {
try {
await sendOnlineBroadcastMessage(pubKey);
} catch (e) {
log.warn(`Failed to send online broadcast message to ${pubKey}`);
}
})
);
}
async function sendOnlineBroadcastMessage(pubKey) {
// TODO: Make this actually get a loki address rather than junk string
const lokiAddressMessage = new textsecure.protobuf.LokiAddressMessage({
p2pAddress: 'testAddress',
p2pPort: parseInt(window.localServerPort, 10),
});
const content = new textsecure.protobuf.Content({
lokiAddressMessage,
});
// will be called once the transmission succeeded or failed
const callback = res => {
if (res.errors.length > 0) {
res.errors.forEach(error => log.error(error));
} else {
log.info('Online broadcast message sent successfully');
}
};
const options = { messageType: 'onlineBroadcast' };
// 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
callback, // callback
options
);
await outgoingMessage.sendToNumber(pubKey);
}
async function sendEmptyMessage(pubKey, sendContentMessage = false) { async function sendEmptyMessage(pubKey, sendContentMessage = false) {
const options = {}; const options = {};
// send an empty message. // send an empty message.
@ -52,5 +99,7 @@
window.libloki.api = { window.libloki.api = {
sendFriendRequestAccepted, sendFriendRequestAccepted,
sendEmptyMessage, sendEmptyMessage,
sendOnlineBroadcastMessage,
broadcastOnlineStatus,
}; };
})(); })();

@ -0,0 +1,22 @@
/* global window */
// eslint-disable-next-line func-names
(function() {
// Possible conversation friend states
const friendRequestStatusEnum = Object.freeze({
// New conversation, no messages sent or received
none: 0,
// This state is used to lock the input early while sending
pendingSend: 1,
// Friend request sent, awaiting response
requestSent: 2,
// Friend request received, awaiting user input
requestReceived: 3,
// We did it!
friends: 4,
});
window.friends = {
friendRequestStatusEnum,
};
})();

@ -68,6 +68,7 @@ class LocalLokiServer extends EventEmitter {
// Async wrapper for http server close // Async wrapper for http server close
close() { close() {
this.removeAllListeners();
if (this.server) { if (this.server) {
return new Promise(res => { return new Promise(res => {
this.server.close(() => res()); this.server.close(() => res());

@ -23,7 +23,7 @@ function MessageReceiver(username, password, signalingKey, options = {}) {
this.username = username; this.username = username;
this.password = password; this.password = password;
this.lokiMessageAPI = window.LokiMessageAPI; this.lokiMessageAPI = window.LokiMessageAPI;
this.localServer = new window.LocalLokiServer(); this.localServer = window.LocalLokiServer;
if (!options.serverTrustRoot) { if (!options.serverTrustRoot) {
throw new Error('Server trust root is required!'); throw new Error('Server trust root is required!');
@ -82,15 +82,11 @@ MessageReceiver.prototype.extend({
} }
}); });
this.localServer.removeAllListeners(); this.localServer.start(window.localServerPort).then(port => {
window.log.info(`Local Server started at localhost:${port}`);
window.libloki.api.broadcastOnlineStatus();
this.localServer.on('message', this.httpPollingResource.handleMessage); this.localServer.on('message', this.httpPollingResource.handleMessage);
});
// Passing 0 as the port will automatically assign an unused port
this.localServer
.start(0)
.then(port =>
window.log.info(`Local Server started at localhost:${port}`)
);
// TODO: Rework this socket stuff to work with online messaging // TODO: Rework this socket stuff to work with online messaging
const useWebSocket = false; const useWebSocket = false;
@ -135,7 +131,10 @@ MessageReceiver.prototype.extend({
} }
if (this.localServer) { if (this.localServer) {
this.localServer.removeAllListeners(); this.localServer.removeListener(
'message',
this.httpPollingResource.handleMessage
);
this.localServer = null; this.localServer = null;
} }
}, },
@ -899,6 +898,15 @@ MessageReceiver.prototype.extend({
}) })
); );
}, },
async handleLokiAddressMessage(envelope, lokiAddressMessage) {
const { p2pAddress, p2pPort } = lokiAddressMessage;
window.LokiP2pAPI.addContactP2pDetails(
envelope.source,
p2pAddress,
p2pPort
);
return this.removeFromCache(envelope);
},
handleDataMessage(envelope, msg) { handleDataMessage(envelope, msg) {
window.log.info('data message from', this.getEnvelopeId(envelope)); window.log.info('data message from', this.getEnvelopeId(envelope));
let p = Promise.resolve(); let p = Promise.resolve();
@ -1013,6 +1021,11 @@ MessageReceiver.prototype.extend({
envelope.source, envelope.source,
content.preKeyBundleMessage content.preKeyBundleMessage
); );
if (content.lokiAddressMessage)
return this.handleLokiAddressMessage(
envelope,
content.lokiAddressMessage
);
if (content.syncMessage) if (content.syncMessage)
return this.handleSyncMessage(envelope, content.syncMessage); return this.handleSyncMessage(envelope, content.syncMessage);
if (content.dataMessage) if (content.dataMessage)

@ -336,8 +336,19 @@ OutgoingMessage.prototype = {
dcodeIO.ByteBuffer.wrap(ciphertext.body, 'binary').toArrayBuffer() dcodeIO.ByteBuffer.wrap(ciphertext.body, 'binary').toArrayBuffer()
); );
} }
let ttl;
if (this.messageType === 'friend-request') {
ttl = 4 * 24 * 60 * 60; // 4 days for friend request message
} else if (this.messageType === 'onlineBroadcast') {
ttl = 10 * 60; // 10 minutes for online broadcast message
} else {
const hours = window.getMessageTTL() || 24; // 1 day default for any other message
ttl = hours * 60 * 60;
}
return { return {
type: ciphertext.type, // FallBackSessionCipher sets this to FRIEND_REQUEST type: ciphertext.type, // FallBackSessionCipher sets this to FRIEND_REQUEST
ttl,
ourKey, ourKey,
sourceDevice: 1, sourceDevice: 1,
destinationRegistrationId: ciphertext.registrationId, destinationRegistrationId: ciphertext.registrationId,
@ -349,17 +360,12 @@ OutgoingMessage.prototype = {
// TODO: handle multiple devices/messages per transmit // TODO: handle multiple devices/messages per transmit
const outgoingObject = outgoingObjects[0]; const outgoingObject = outgoingObjects[0];
const socketMessage = await this.wrapInWebsocketMessage(outgoingObject); const socketMessage = await this.wrapInWebsocketMessage(outgoingObject);
let ttl; await this.transmitMessage(
if ( number,
outgoingObject.type === socketMessage,
textsecure.protobuf.Envelope.Type.FRIEND_REQUEST this.timestamp,
) { outgoingObject.ttl
ttl = 4 * 24 * 60 * 60; // 4 days for friend request message );
} else {
const hours = window.getMessageTTL() || 24; // 1 day default for any other message
ttl = hours * 60 * 60;
}
await this.transmitMessage(number, socketMessage, this.timestamp, ttl);
this.successfulNumbers[this.successfulNumbers.length] = number; this.successfulNumbers[this.successfulNumbers.length] = number;
this.numberCompleted(); this.numberCompleted();
}) })

@ -146,6 +146,7 @@ function prepareURL(pathSegments, moreKeys) {
cdnUrl: config.get('cdnUrl'), cdnUrl: config.get('cdnUrl'),
messageServerPort: config.get('messageServerPort'), messageServerPort: config.get('messageServerPort'),
swarmServerPort: config.get('swarmServerPort'), swarmServerPort: config.get('swarmServerPort'),
localServerPort: config.get('localServerPort'),
certificateAuthority: config.get('certificateAuthority'), certificateAuthority: config.get('certificateAuthority'),
environment: config.environment, environment: config.environment,
node_version: process.versions.node, node_version: process.versions.node,

@ -276,6 +276,10 @@ window.LokiSnodeAPI = new LokiSnodeAPI({
swarmServerPort: config.swarmServerPort, swarmServerPort: config.swarmServerPort,
}); });
const { LokiP2pAPI } = require('./js/modules/loki_p2p_api');
window.LokiP2pAPI = new LokiP2pAPI();
const { LokiMessageAPI } = require('./js/modules/loki_message_api'); const { LokiMessageAPI } = require('./js/modules/loki_message_api');
window.LokiMessageAPI = new LokiMessageAPI({ window.LokiMessageAPI = new LokiMessageAPI({
@ -285,7 +289,8 @@ window.LokiMessageAPI = new LokiMessageAPI({
const { LocalLokiServer } = require('./libloki/local_loki_server'); const { LocalLokiServer } = require('./libloki/local_loki_server');
window.LocalLokiServer = LocalLokiServer; window.localServerPort = config.localServerPort;
window.LocalLokiServer = new LocalLokiServer();
window.mnemonic = require('./libloki/mnemonic'); window.mnemonic = require('./libloki/mnemonic');
const { WorkerInterface } = require('./js/modules/util_worker_interface'); const { WorkerInterface } = require('./js/modules/util_worker_interface');

@ -35,6 +35,12 @@ message Content {
optional ReceiptMessage receiptMessage = 5; optional ReceiptMessage receiptMessage = 5;
optional TypingMessage typingMessage = 6; optional TypingMessage typingMessage = 6;
optional PreKeyBundleMessage preKeyBundleMessage = 101; optional PreKeyBundleMessage preKeyBundleMessage = 101;
optional LokiAddressMessage lokiAddressMessage = 102;
}
message LokiAddressMessage {
optional string p2pAddress = 1;
optional uint32 p2pPort = 2;
} }
message PreKeyBundleMessage { message PreKeyBundleMessage {

@ -360,6 +360,7 @@
<script type="text/javascript" src="../js/storage.js" data-cover></script> <script type="text/javascript" src="../js/storage.js" data-cover></script>
<script type="text/javascript" src="../js/signal_protocol_store.js" data-cover></script> <script type="text/javascript" src="../js/signal_protocol_store.js" data-cover></script>
<script type="text/javascript" src="../js/libtextsecure.js" data-cover></script> <script type="text/javascript" src="../js/libtextsecure.js" data-cover></script>
<script type="text/javascript" src="../js/libloki.js" data-cover></script>
<script type="text/javascript" src="../js/libphonenumber-util.js"></script> <script type="text/javascript" src="../js/libphonenumber-util.js"></script>
<script type='text/javascript' src='../js/models/profile.js' data-cover></script> <script type='text/javascript' src='../js/models/profile.js' data-cover></script>

Loading…
Cancel
Save