diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index e58951412..9360c23fe 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -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"
},
diff --git a/background.html b/background.html
index acad1030c..84bebabbd 100644
--- a/background.html
+++ b/background.html
@@ -282,6 +282,29 @@
+
+
+
+
diff --git a/js/background.js b/js/background.js
index 710471086..b002669dc 100644
--- a/js/background.js
+++ b/js/background.js
@@ -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();
diff --git a/js/models/conversations.js b/js/models/conversations.js
index de9c2216f..35ecae10c 100644
--- a/js/models/conversations.js
+++ b/js/models/conversations.js
@@ -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);
diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js
index 909caf5a3..eb113d860 100644
--- a/js/modules/loki_app_dot_net_api.js
+++ b/js/modules/loki_app_dot_net_api.js
@@ -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 = {};
diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js
index 61caaffd3..b70984ccc 100644
--- a/js/modules/loki_file_server_api.js
+++ b/js/modules/loki_file_server_api.js
@@ -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;
diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js
index ba794adca..2c52ea4a3 100644
--- a/js/modules/loki_message_api.js
+++ b/js/modules/loki_message_api.js
@@ -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,
diff --git a/js/views/add_server_dialog_view.js b/js/views/add_server_dialog_view.js
new file mode 100644
index 000000000..270912825
--- /dev/null
+++ b/js/views/add_server_dialog_view.js
@@ -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;
+ }
+ },
+ });
+})();
diff --git a/js/views/app_view.js b/js/views/app_view.js
index 31235bf9f..885e61ced 100644
--- a/js/views/app_view.js
+++ b/js/views/app_view.js
@@ -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);
+ },
});
})();
diff --git a/js/views/connecting_to_server_dialog_view.js b/js/views/connecting_to_server_dialog_view.js
new file mode 100644
index 000000000..5c45a1ef1
--- /dev/null
+++ b/js/views/connecting_to_server_dialog_view.js
@@ -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;
+ }
+ },
+ });
+})();
diff --git a/preload.js b/preload.js
index 20df787e8..9df58dadd 100644
--- a/preload.js
+++ b/preload.js
@@ -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');
diff --git a/test/index.html b/test/index.html
index ef36a342e..4528bef6b 100644
--- a/test/index.html
+++ b/test/index.html
@@ -567,7 +567,8 @@
-
+
+
diff --git a/ts/components/MainHeader.tsx b/ts/components/MainHeader.tsx
index 8f099dfd3..21396b616 100644
--- a/ts/components/MainHeader.tsx
+++ b/ts/components/MainHeader.tsx
@@ -334,6 +334,13 @@ export class MainHeader extends React.Component {
trigger('showQRDialog');
},
},
+ {
+ id: 'showAddServer',
+ name: i18n('showAddServer'),
+ onClick: () => {
+ trigger('showAddServerDialog');
+ },
+ },
];
const passItem = (type: string) => ({