diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index f562ef89b..cc5a3c110 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -784,6 +784,9 @@
"clear": {
"message": "Clear"
},
+ "copySeed": {
+ "message": "Copy Seed"
+ },
"failedToSend": {
"message":
"Failed to send to some recipients. Check your network connection."
@@ -1682,8 +1685,8 @@
},
"copiedMnemonic": {
- "message": "Copied mnemonic to clipboard",
- "description": "A toast message telling the user that the mnemonic was copied"
+ "message": "Copied seed to clipboard",
+ "description": "A toast message telling the user that the mnemonic seed was copied"
},
"passwordViewTitle": {
diff --git a/background.html b/background.html
index 295eaef6e..eaca125b9 100644
--- a/background.html
+++ b/background.html
@@ -229,6 +229,20 @@
+
+
diff --git a/js/background.js b/js/background.js
index 831ad3044..f04ce680e 100644
--- a/js/background.js
+++ b/js/background.js
@@ -608,6 +608,14 @@
}
});
+ Whisper.events.on('showSeedDialog', async () => {
+ const manager = await getAccountManager();
+ if (appView && manager) {
+ const seed = manager.getCurrentMnemonic();
+ appView.showSeedDialog(seed);
+ }
+ });
+
Whisper.events.on('calculatingPoW', ({ pubKey, timestamp }) => {
try {
const conversation = ConversationController.get(pubKey);
diff --git a/js/views/app_view.js b/js/views/app_view.js
index fcf865a39..5156ac5a8 100644
--- a/js/views/app_view.js
+++ b/js/views/app_view.js
@@ -191,5 +191,9 @@
const dialog = Whisper.getPasswordDialogView(type, resolve, reject);
this.el.append(dialog.el);
},
+ showSeedDialog(seed) {
+ const dialog = new Whisper.SeedDialogView({ seed });
+ this.el.append(dialog.el);
+ },
});
})();
diff --git a/js/views/seed_dialog_view.js b/js/views/seed_dialog_view.js
new file mode 100644
index 000000000..1d5b394ec
--- /dev/null
+++ b/js/views/seed_dialog_view.js
@@ -0,0 +1,56 @@
+/* global Whisper, i18n */
+
+// eslint-disable-next-line func-names
+(function() {
+ 'use strict';
+
+ window.Whisper = window.Whisper || {};
+
+ Whisper.SeedDialogView = Whisper.View.extend({
+ className: 'loki-dialog seed-dialog modal',
+ templateName: 'seed-dialog',
+ initialize(options = {}) {
+ this.okText = options.okText || i18n('ok');
+ this.copyText = options.copyText || i18n('copySeed');
+ this.seed = options.seed || '-';
+
+ this.render();
+ },
+ events: {
+ keyup: 'onKeyup',
+ 'click .ok': 'ok',
+ 'click .copy-seed': 'copySeed',
+ },
+ render_attributes() {
+ return {
+ seed: this.seed,
+ ok: this.okText,
+ copyText: this.copyText,
+ };
+ },
+ ok() {
+ this.remove();
+ },
+ copySeed() {
+ window.clipboard.writeText(this.seed);
+
+ const toast = new Whisper.MessageToastView({
+ message: i18n('copiedMnemonic'),
+ });
+ toast.$el.appendTo(this.$el);
+ toast.render();
+ },
+ onKeyup(event) {
+ switch (event.key) {
+ case 'Enter':
+ case 'Escape':
+ case 'Esc':
+ this.ok();
+ break;
+ default:
+ return;
+ }
+ event.preventDefault();
+ },
+ });
+})();
diff --git a/js/views/standalone_registration_view.js b/js/views/standalone_registration_view.js
index 4b93aac15..5a9599aac 100644
--- a/js/views/standalone_registration_view.js
+++ b/js/views/standalone_registration_view.js
@@ -58,7 +58,7 @@
'keyup #password': 'onPasswordChange',
'keyup #password-confirmation': 'onValidatePassword',
},
- async register(mnemonic) {
+ async register(mnemonic, language) {
// Make sure the password is valid
if (this.validatePassword()) {
this.showToast('Invalid password');
@@ -71,7 +71,7 @@
await window.setPassword(input);
await this.accountManager.registerSingleDevice(
mnemonic,
- this.$('#mnemonic-language').val(),
+ language,
this.$('#display-name').val()
);
this.$el.trigger('openInbox');
@@ -84,14 +84,16 @@
},
registerWithoutMnemonic() {
const mnemonic = this.$('#mnemonic-display').text();
- this.register(mnemonic);
+ const language = this.$('#mnemonic-display-language').val();
+ this.register(mnemonic, language);
},
registerWithMnemonic() {
const mnemonic = this.$('#mnemonic').val();
+ const language = this.$('#mnemonic-language').val();
if (!mnemonic) {
this.log('Please provide a mnemonic word list');
} else {
- this.register(mnemonic);
+ this.register(mnemonic, language);
}
},
onChangeMnemonic() {
diff --git a/libloki/mnemonic.js b/libloki/mnemonic.js
index 33bfd3511..05bbb2f69 100644
--- a/libloki/mnemonic.js
+++ b/libloki/mnemonic.js
@@ -123,7 +123,7 @@ function mn_decode(str, wordset_name) {
);
}
if (w1 === -1 || w2 === -1 || w3 === -1) {
- throw MnemonicError('invalid word in mnemonic');
+ throw new MnemonicError('invalid word in mnemonic');
}
var x = w1 + n * ((n - w1 + w2) % n) + n * n * ((n - w2 + w3) % n);
if (x % n != w1)
diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js
index 7795a4a61..99bad2969 100644
--- a/libtextsecure/account_manager.js
+++ b/libtextsecure/account_manager.js
@@ -72,6 +72,7 @@
return this.queueTask(() =>
generateKeypair().then(async identityKeyPair => {
return createAccount(identityKeyPair)
+ .then(() => this.saveMnemonic(mnemonic))
.then(clearSessionsAndPreKeys)
.then(generateKeys)
.then(confirmKeys)
@@ -451,6 +452,12 @@
const hex = StringView.arrayBufferToHex(keys.privKey);
return mnemonic.mn_encode(hex, language);
},
+ getCurrentMnemonic() {
+ return textsecure.storage.get('mnemonic');
+ },
+ saveMnemonic(mnemonic) {
+ return textsecure.storage.put('mnemonic', mnemonic);
+ },
async registrationDone(number, profileName) {
window.log.info('registration done');
diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss
index 4c23e6bf5..b44290680 100644
--- a/stylesheets/_conversation.scss
+++ b/stylesheets/_conversation.scss
@@ -425,6 +425,17 @@
}
}
+.seed-dialog {
+ .title {
+ font-weight: bold;
+ }
+
+ .seed {
+ padding: 20px 0;
+ font-style: oblique;
+ }
+}
+
.permissions-popup,
.debug-log-window {
.modal {
diff --git a/test/index.html b/test/index.html
index 49c05c888..0152ccafe 100644
--- a/test/index.html
+++ b/test/index.html
@@ -404,6 +404,7 @@
+