Merge pull request #557 from BeaudanBrown/add-server

Add server
This commit is contained in:
Beaudan Campbell-Brown 2019-10-22 13:16:23 +11:00 committed by GitHub
commit ac80ef0d4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 315 additions and 38 deletions

View file

@ -166,6 +166,11 @@
"description":
"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": {
"message": "Loading...",
"description":
@ -1940,6 +1945,16 @@
"message": "Show 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": {
"message":
@ -1991,6 +2006,15 @@
"passwordsDoNotMatch": {
"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": {
"message": "Failed to set password"
},

View file

@ -282,6 +282,29 @@
</div>
</div>
</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'>
<div class="content">
<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/seed_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/identicon_svg_view.js'></script>
<script type='text/javascript' src='js/views/install_view.js'></script>

View file

@ -233,6 +233,9 @@
window.lokiPublicChatAPI = new window.LokiPublicChatAPI(ourKey);
// singleton to interface the File server
window.lokiFileServerAPI = new window.LokiFileServerAPI(ourKey);
await window.lokiFileServerAPI.establishConnection(
window.getDefaultFileServer()
);
// are there limits on tracking, is this unneeded?
// window.mixpanel.track("Desktop boot");
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 () => {
if (appView) {
const ourNumber = textsecure.storage.user.getNumber();

View file

@ -1421,7 +1421,7 @@
options.messageType = message.get('type');
options.isPublic = this.isPublic();
if (options.isPublic) {
options.publicSendData = this.getPublicSendData();
options.publicSendData = await this.getPublicSendData();
}
const groupNumbers = this.getRecipients();
@ -2122,6 +2122,21 @@
};
},
// 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() {
if (!this.isPublic()) {
return null;
@ -2132,10 +2147,18 @@
conversationId: this.get('id'),
};
},
getPublicSendData() {
const serverAPI = lokiPublicChatAPI.findOrCreateServer(
async getPublicSendData() {
const serverAPI = await lokiPublicChatAPI.findOrCreateServer(
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(
this.get('channelId'),
this.id
@ -2397,6 +2420,9 @@
async deletePublicMessage(message) {
const channelAPI = this.getPublicSendData();
if (!channelAPI) {
return false;
}
const success = await channelAPI.deleteMessage(message.getServerId());
if (success) {
this.removeMessage(message.id);

View file

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

View file

@ -1,19 +1,23 @@
/* global log */
const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
/* global log */
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 {
constructor(serverUrl, ourKey) {
constructor(ourKey) {
this.ourKey = 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) {
@ -59,4 +63,4 @@ class LokiFileServerAPI {
}
}
module.exports = LokiFileServerAPIWrapper;
module.exports = LokiFileServerAPI;

View file

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

View file

@ -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;
}
},
});
})();

View file

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

View file

@ -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;
}
},
});
})();

View file

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

View file

@ -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/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/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/last_seen_indicator_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/scroll_down_button_view.js' data-cover></script>

View file

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