Merge pull request #557 from BeaudanBrown/add-server

Add server
pull/568/head
Beaudan Campbell-Brown 6 years ago committed by GitHub
commit ac80ef0d4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -166,6 +166,11 @@
"description": "description":
"Only available on development modes, menu option to open up the standalone device setup sequence" "Only available on development modes, menu option to open up the standalone device setup sequence"
}, },
"connectingLoad": {
"message": "Connecting...",
"description":
"Message shown on the as a loading screen while we are connecting to something"
},
"loading": { "loading": {
"message": "Loading...", "message": "Loading...",
"description": "description":
@ -1940,6 +1945,16 @@
"message": "Show QR code", "message": "Show QR code",
"description": "Button action that the user can click to view their QR code" "description": "Button action that the user can click to view their QR code"
}, },
"showAddServer": {
"message": "Add public server",
"description":
"Button action that the user can click to connect to a new public server"
},
"addServerDialogTitle": {
"message": "Connect to new public server",
"description":
"Title for the dialog box used to connect to a new public server"
},
"seedViewTitle": { "seedViewTitle": {
"message": "message":
@ -1991,6 +2006,15 @@
"passwordsDoNotMatch": { "passwordsDoNotMatch": {
"message": "Passwords do not match" "message": "Passwords do not match"
}, },
"publicChatExists": {
"message": "You are already connected to this public channel"
},
"connectToServerFail": {
"message": "Failed to connect to server. Check URL"
},
"connectToServerSuccess": {
"message": "Successfully connected to new public chat server"
},
"setPasswordFail": { "setPasswordFail": {
"message": "Failed to set password" "message": "Failed to set password"
}, },

@ -282,6 +282,29 @@
</div> </div>
</div> </div>
</script> </script>
<script type='text/x-tmpl-mustache' id='connecting-to-server-template'>
<div class="content">
{{ #title }}
<h4>{{ title }}</h4>
{{ /title }}
<div class='buttons'>
<button class='cancel' tabindex='2'>{{ cancel }}</button>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='add-server-template'>
<div class="content">
{{ #title }}
<h4>{{ title }}</h4>
{{ /title }}
<input type='text' id='server-url' placeholder='Server Url' autofocus>
<div class='error'></div>
<div class='buttons'>
<button class='cancel' tabindex='2'>{{ cancel }}</button>
<button class='ok' tabindex='1'>{{ ok }}</button>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='qr-code-template'> <script type='text/x-tmpl-mustache' id='qr-code-template'>
<div class="content"> <div class="content">
<div id="qr"> <div id="qr">
@ -692,6 +715,8 @@
<script type='text/javascript' src='js/views/password_dialog_view.js'></script> <script type='text/javascript' src='js/views/password_dialog_view.js'></script>
<script type='text/javascript' src='js/views/seed_dialog_view.js'></script> <script type='text/javascript' src='js/views/seed_dialog_view.js'></script>
<script type='text/javascript' src='js/views/qr_dialog_view.js'></script> <script type='text/javascript' src='js/views/qr_dialog_view.js'></script>
<script type='text/javascript' src='js/views/connecting_to_server_dialog_view.js'></script>
<script type='text/javascript' src='js/views/add_server_dialog_view.js'></script>
<script type='text/javascript' src='js/views/beta_release_disclaimer_view.js'></script> <script type='text/javascript' src='js/views/beta_release_disclaimer_view.js'></script>
<script type='text/javascript' src='js/views/identicon_svg_view.js'></script> <script type='text/javascript' src='js/views/identicon_svg_view.js'></script>
<script type='text/javascript' src='js/views/install_view.js'></script> <script type='text/javascript' src='js/views/install_view.js'></script>

@ -233,6 +233,9 @@
window.lokiPublicChatAPI = new window.LokiPublicChatAPI(ourKey); window.lokiPublicChatAPI = new window.LokiPublicChatAPI(ourKey);
// singleton to interface the File server // singleton to interface the File server
window.lokiFileServerAPI = new window.LokiFileServerAPI(ourKey); window.lokiFileServerAPI = new window.LokiFileServerAPI(ourKey);
await window.lokiFileServerAPI.establishConnection(
window.getDefaultFileServer()
);
// are there limits on tracking, is this unneeded? // are there limits on tracking, is this unneeded?
// window.mixpanel.track("Desktop boot"); // window.mixpanel.track("Desktop boot");
window.lokiP2pAPI = new window.LokiP2pAPI(ourKey); window.lokiP2pAPI = new window.LokiP2pAPI(ourKey);
@ -749,6 +752,12 @@
} }
}); });
Whisper.events.on('showAddServerDialog', async options => {
if (appView) {
appView.showAddServerDialog(options);
}
});
Whisper.events.on('showQRDialog', async () => { Whisper.events.on('showQRDialog', async () => {
if (appView) { if (appView) {
const ourNumber = textsecure.storage.user.getNumber(); const ourNumber = textsecure.storage.user.getNumber();

@ -1421,7 +1421,7 @@
options.messageType = message.get('type'); options.messageType = message.get('type');
options.isPublic = this.isPublic(); options.isPublic = this.isPublic();
if (options.isPublic) { if (options.isPublic) {
options.publicSendData = this.getPublicSendData(); options.publicSendData = await this.getPublicSendData();
} }
const groupNumbers = this.getRecipients(); const groupNumbers = this.getRecipients();
@ -2122,6 +2122,21 @@
}; };
}, },
// maybe "Backend" instead of "Source"? // maybe "Backend" instead of "Source"?
async setPublicSource(newServer, newChannelId) {
if (!this.isPublic()) {
return;
}
if (
this.get('server') !== newServer ||
this.get('channelId') !== newChannelId
) {
this.set({ server: newServer });
this.set({ channelId: newChannelId });
await window.Signal.Data.updateConversation(this.id, this.attributes, {
Conversation: Whisper.Conversation,
});
}
},
getPublicSource() { getPublicSource() {
if (!this.isPublic()) { if (!this.isPublic()) {
return null; return null;
@ -2132,10 +2147,18 @@
conversationId: this.get('id'), conversationId: this.get('id'),
}; };
}, },
getPublicSendData() { async getPublicSendData() {
const serverAPI = lokiPublicChatAPI.findOrCreateServer( const serverAPI = await lokiPublicChatAPI.findOrCreateServer(
this.get('server') this.get('server')
); );
if (!serverAPI) {
window.log.warn(
`Failed to get serverAPI (${this.get('server')}) for conversation (${
this.id
})`
);
return null;
}
const channelAPI = serverAPI.findOrCreateChannel( const channelAPI = serverAPI.findOrCreateChannel(
this.get('channelId'), this.get('channelId'),
this.id this.id
@ -2397,6 +2420,9 @@
async deletePublicMessage(message) { async deletePublicMessage(message) {
const channelAPI = this.getPublicSendData(); const channelAPI = this.getPublicSendData();
if (!channelAPI) {
return false;
}
const success = await channelAPI.deleteMessage(message.getServerId()); const success = await channelAPI.deleteMessage(message.getServerId());
if (success) { if (success) {
this.removeMessage(message.id); this.removeMessage(message.id);

@ -28,21 +28,32 @@ class LokiAppDotNetAPI extends EventEmitter {
} }
// server getter/factory // server getter/factory
findOrCreateServer(serverUrl) { async findOrCreateServer(serverUrl) {
let thisServer = this.servers.find( let thisServer = this.servers.find(
server => server.baseServerUrl === serverUrl server => server.baseServerUrl === serverUrl
); );
if (!thisServer) { if (!thisServer) {
log.info(`LokiAppDotNetAPI creating ${serverUrl}`); log.info(`LokiAppDotNetAPI creating ${serverUrl}`);
thisServer = new LokiAppDotNetServerAPI(this, serverUrl); thisServer = new LokiAppDotNetServerAPI(this, serverUrl);
const gotToken = await thisServer.getOrRefreshServerToken();
if (!gotToken) {
log.warn(`Invalid server ${serverUrl}`);
return null;
}
log.info(`set token ${thisServer.token}`);
this.servers.push(thisServer); this.servers.push(thisServer);
} }
return thisServer; return thisServer;
} }
// channel getter/factory // channel getter/factory
findOrCreateChannel(serverUrl, channelId, conversationId) { async findOrCreateChannel(serverUrl, channelId, conversationId) {
const server = this.findOrCreateServer(serverUrl); const server = await this.findOrCreateServer(serverUrl);
if (!server) {
log.error(`Failed to create server for: ${serverUrl}`);
return null;
}
return server.findOrCreateChannel(channelId, conversationId); return server.findOrCreateChannel(channelId, conversationId);
} }
@ -82,11 +93,6 @@ class LokiAppDotNetServerAPI {
this.channels = []; this.channels = [];
this.tokenPromise = null; this.tokenPromise = null;
this.baseServerUrl = url; this.baseServerUrl = url;
const ref = this;
(async function justToEnableAsyncToGetToken() {
ref.token = await ref.getOrRefreshServerToken();
log.info(`set token ${ref.token}`);
})();
} }
// channel getter/factory // channel getter/factory
@ -174,14 +180,14 @@ class LokiAppDotNetServerAPI {
// request an token from the server // request an token from the server
async requestToken() { async requestToken() {
const url = new URL(`${this.baseServerUrl}/loki/v1/get_challenge`);
const params = {
pubKey: this.chatAPI.ourKey,
};
url.search = new URLSearchParams(params);
let res; let res;
try { try {
const url = new URL(`${this.baseServerUrl}/loki/v1/get_challenge`);
const params = {
pubKey: this.chatAPI.ourKey,
};
url.search = new URLSearchParams(params);
res = await nodeFetch(url); res = await nodeFetch(url);
} catch (e) { } catch (e) {
return null; return null;
@ -232,15 +238,12 @@ class LokiAppDotNetServerAPI {
url.search = new URLSearchParams(params); url.search = new URLSearchParams(params);
} }
let result; let result;
let { token } = this; const token = await this.getOrRefreshServerToken();
if (!token) { if (!token) {
token = await this.getOrRefreshServerToken(); log.error('NO TOKEN');
if (!token) { return {
log.error('NO TOKEN'); err: 'noToken',
return { };
err: 'noToken',
};
}
} }
try { try {
const fetchOptions = {}; const fetchOptions = {};

@ -1,19 +1,23 @@
/* global log */
const LokiAppDotNetAPI = require('./loki_app_dot_net_api'); const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
/* global log */ /* global log */
const DEVICE_MAPPING_ANNOTATION_KEY = 'network.loki.messenger.devicemapping'; const DEVICE_MAPPING_ANNOTATION_KEY = 'network.loki.messenger.devicemapping';
// returns the LokiFileServerAPI constructor with the serverUrl already consumed
function LokiFileServerAPIWrapper(serverUrl) {
return LokiFileServerAPI.bind(null, serverUrl);
}
class LokiFileServerAPI { class LokiFileServerAPI {
constructor(serverUrl, ourKey) { constructor(ourKey) {
this.ourKey = ourKey; this.ourKey = ourKey;
this._adnApi = new LokiAppDotNetAPI(ourKey); this._adnApi = new LokiAppDotNetAPI(ourKey);
this._server = this._adnApi.findOrCreateServer(serverUrl); }
async establishConnection(serverUrl) {
this._server = await this._adnApi.findOrCreateServer(serverUrl);
// TODO: Handle this failure gracefully
if (!this._server) {
log.error('Failed to establish connection to file server');
}
} }
async getUserDeviceMapping(pubKey) { async getUserDeviceMapping(pubKey) {
@ -59,4 +63,4 @@ class LokiFileServerAPI {
} }
} }
module.exports = LokiFileServerAPIWrapper; module.exports = LokiFileServerAPI;

@ -88,6 +88,11 @@ class LokiMessageAPI {
}; };
if (isPublic) { if (isPublic) {
if (!publicSendData) {
throw new window.textsecure.PublicChatError(
'Missing public send data for public chat message'
);
}
const res = await publicSendData.sendMessage( const res = await publicSendData.sendMessage(
data.body, data.body,
data.quote, data.quote,

@ -0,0 +1,88 @@
/* global Whisper, i18n, _ */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.AddServerDialogView = Whisper.View.extend({
templateName: 'add-server-template',
className: 'loki-dialog add-server modal',
initialize(options = {}) {
this.title = i18n('addServerDialogTitle');
this.okText = options.okText || i18n('ok');
this.cancelText = options.cancelText || i18n('cancel');
this.$('input').focus();
this.render();
},
events: {
keyup: 'onKeyup',
'click .ok': 'confirm',
'click .cancel': 'close',
},
render_attributes() {
return {
title: this.title,
ok: this.okText,
cancel: this.cancelText,
};
},
confirm() {
// Remove error if there is one
this.showError(null);
const serverUrl = this.$('#server-url').val().toLowerCase();
// TODO: Make this not hard coded
const channelId = 1;
const dialog = new Whisper.ConnectingToServerDialogView({
serverUrl,
channelId,
});
const dialogDelayTimer = setTimeout(() => {
this.el.append(dialog.el);
}, 200);
dialog.once('connectionResult', result => {
clearTimeout(dialogDelayTimer);
if (result.cancelled) {
this.showError(null);
return;
}
if (result.errorCode) {
this.showError(result.errorCode);
return;
}
window.Whisper.events.trigger('showToast', {
message: i18n('connectToServerSuccess'),
});
this.close();
});
dialog.trigger('attemptConnection');
},
close() {
this.remove();
},
showError(message) {
if (_.isEmpty(message)) {
this.$('.error').text('');
this.$('.error').hide();
} else {
this.$('.error').text(`Error: ${message}`);
this.$('.error').show();
}
this.$('input').focus();
},
onKeyup(event) {
switch (event.key) {
case 'Enter':
this.confirm();
break;
case 'Escape':
case 'Esc':
this.close();
break;
default:
break;
}
},
});
})();

@ -200,5 +200,9 @@
const dialog = new Whisper.QRDialogView({ string }); const dialog = new Whisper.QRDialogView({ string });
this.el.append(dialog.el); this.el.append(dialog.el);
}, },
showAddServerDialog() {
const dialog = new Whisper.AddServerDialogView();
this.el.append(dialog.el);
},
}); });
})(); })();

@ -0,0 +1,83 @@
/* global Whisper, i18n, lokiPublicChatAPI, ConversationController, friends */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.ConnectingToServerDialogView = Whisper.View.extend({
templateName: 'connecting-to-server-template',
className: 'loki-dialog connecting-to-server modal',
initialize(options = {}) {
this.title = i18n('connectingLoad');
this.cancelText = options.cancelText || i18n('cancel');
this.serverUrl = options.serverUrl;
this.channelId = options.channelId;
this.once('attemptConnection', () =>
this.attemptConnection(options.serverUrl, options.channelId)
);
this.render();
},
events: {
keyup: 'onKeyup',
'click .cancel': 'close',
},
async attemptConnection(serverUrl, channelId) {
const rawServerUrl = serverUrl
.replace(/^https?:\/\//i, '')
.replace(/[/\\]+$/i, '');
const sslServerUrl = `https://${rawServerUrl}`;
const conversationId = `publicChat:${channelId}@${rawServerUrl}`;
const conversationExists = ConversationController.get(conversationId);
if (conversationExists) {
// We are already a member of this public chat
return this.resolveWith({ errorCode: i18n('publicChatExists') });
}
const serverAPI = await lokiPublicChatAPI.findOrCreateServer(
sslServerUrl
);
if (!serverAPI) {
// Url incorrect or server not compatible
return this.resolveWith({ errorCode: i18n('connectToServerFail') });
}
const conversation = await ConversationController.getOrCreateAndWait(
conversationId,
'group'
);
serverAPI.findOrCreateChannel(channelId, conversationId);
await conversation.setPublicSource(sslServerUrl, channelId);
await conversation.setFriendRequestStatus(
friends.friendRequestStatusEnum.friends
);
return this.resolveWith({ conversation });
},
resolveWith(result) {
this.trigger('connectionResult', result);
this.remove();
},
render_attributes() {
return {
title: this.title,
cancel: this.cancelText,
};
},
close() {
this.trigger('connectionResult', { cancelled: true });
this.remove();
},
onKeyup(event) {
switch (event.key) {
case 'Escape':
case 'Esc':
this.close();
break;
default:
break;
}
},
});
})();

@ -41,6 +41,7 @@ window.isBehindProxy = () => Boolean(config.proxyUrl);
window.JobQueue = JobQueue; window.JobQueue = JobQueue;
window.getStoragePubKey = key => window.getStoragePubKey = key =>
window.isDev() ? key.substring(0, key.length - 2) : key; window.isDev() ? key.substring(0, key.length - 2) : key;
window.getDefaultFileServer = () => config.defaultFileServer;
window.isBeforeVersion = (toCheck, baseVersion) => { window.isBeforeVersion = (toCheck, baseVersion) => {
try { try {
@ -328,10 +329,7 @@ window.LokiMessageAPI = require('./js/modules/loki_message_api');
window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api'); window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api');
const LokiFileServerAPIWrapper = require('./js/modules/loki_file_server_api'); window.LokiFileServerAPI = require('./js/modules/loki_file_server_api');
// bind first argument as we have it here already
window.LokiFileServerAPI = LokiFileServerAPIWrapper(config.defaultFileServer);
window.LokiRssAPI = require('./js/modules/loki_rss_api'); window.LokiRssAPI = require('./js/modules/loki_rss_api');

@ -567,7 +567,8 @@
<script type='text/javascript' src='../js/views/nickname_dialog_view.js' data-cover></script> <script type='text/javascript' src='../js/views/nickname_dialog_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/password_dialog_view.js' data-cover></script> <script type='text/javascript' src='../js/views/password_dialog_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/seed_dialog_view.js' data-cover></script> <script type='text/javascript' src='../js/views/seed_dialog_view.js' data-cover></script>
<script type='text/javascript' src='js/views/qr_dialog_view.js'></script> <script type='text/javascript' src='../js/views/qr_dialog_view.js'></script>
<script type='text/javascript' src='../js/views/add_server_dialog_view.js'></script>
<script type='text/javascript' src='../js/views/identicon_svg_view.js' data-cover></script> <script type='text/javascript' src='../js/views/identicon_svg_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/last_seen_indicator_view.js' data-cover></script> <script type='text/javascript' src='../js/views/last_seen_indicator_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/scroll_down_button_view.js' data-cover></script> <script type='text/javascript' src='../js/views/scroll_down_button_view.js' data-cover></script>

@ -334,6 +334,13 @@ export class MainHeader extends React.Component<Props, any> {
trigger('showQRDialog'); trigger('showQRDialog');
}, },
}, },
{
id: 'showAddServer',
name: i18n('showAddServer'),
onClick: () => {
trigger('showAddServerDialog');
},
},
]; ];
const passItem = (type: string) => ({ const passItem = (type: string) => ({

Loading…
Cancel
Save