diff options
-rw-r--r-- | src/index.html | 3 | ||||
-rw-r--r-- | src/js/index.js | 157 |
2 files changed, 145 insertions, 15 deletions
diff --git a/src/index.html b/src/index.html index fc3a6d1..56d73e6 100644 --- a/src/index.html +++ b/src/index.html | |||
@@ -40,6 +40,9 @@ | |||
40 | box-shadow: inset 0 1px 1px rgba(0,0,0,.0); | 40 | box-shadow: inset 0 1px 1px rgba(0,0,0,.0); |
41 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.0); | 41 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.0); |
42 | } | 42 | } |
43 | .phrase { | ||
44 | word-break: keep-all; | ||
45 | } | ||
43 | .strength { | 46 | .strength { |
44 | /* override mobile width from bootstrap */ | 47 | /* override mobile width from bootstrap */ |
45 | width: auto!important; | 48 | width: auto!important; |
diff --git a/src/js/index.js b/src/js/index.js index 69f5eab..c5f6c11 100644 --- a/src/js/index.js +++ b/src/js/index.js | |||
@@ -1,6 +1,8 @@ | |||
1 | (function() { | 1 | (function() { |
2 | 2 | ||
3 | var mnemonic = new Mnemonic("english"); | 3 | // mnemonics is populated as required by getLanguage |
4 | var mnemonics = { "english": new Mnemonic("english") }; | ||
5 | var mnemonic = mnemonics["english"]; | ||
4 | var seed = null | 6 | var seed = null |
5 | var bip32RootKey = null; | 7 | var bip32RootKey = null; |
6 | var bip32ExtendedKey = null; | 8 | var bip32ExtendedKey = null; |
@@ -44,6 +46,7 @@ | |||
44 | DOM.indexToggle = $(".index-toggle"); | 46 | DOM.indexToggle = $(".index-toggle"); |
45 | DOM.addressToggle = $(".address-toggle"); | 47 | DOM.addressToggle = $(".address-toggle"); |
46 | DOM.privateKeyToggle = $(".private-key-toggle"); | 48 | DOM.privateKeyToggle = $(".private-key-toggle"); |
49 | DOM.languages = $(".languages a"); | ||
47 | 50 | ||
48 | function init() { | 51 | function init() { |
49 | // Events | 52 | // Events |
@@ -63,6 +66,7 @@ | |||
63 | DOM.indexToggle.on("click", toggleIndexes); | 66 | DOM.indexToggle.on("click", toggleIndexes); |
64 | DOM.addressToggle.on("click", toggleAddresses); | 67 | DOM.addressToggle.on("click", toggleAddresses); |
65 | DOM.privateKeyToggle.on("click", togglePrivateKeys); | 68 | DOM.privateKeyToggle.on("click", togglePrivateKeys); |
69 | DOM.languages.on("click", languageChanged); | ||
66 | disableForms(); | 70 | disableForms(); |
67 | hidePending(); | 71 | hidePending(); |
68 | hideValidationError(); | 72 | hideValidationError(); |
@@ -94,6 +98,7 @@ | |||
94 | function phraseChanged() { | 98 | function phraseChanged() { |
95 | showPending(); | 99 | showPending(); |
96 | hideValidationError(); | 100 | hideValidationError(); |
101 | setMnemonicLanguage(); | ||
97 | // Get the mnemonic phrase | 102 | // Get the mnemonic phrase |
98 | var phrase = DOM.phrase.val(); | 103 | var phrase = DOM.phrase.val(); |
99 | var errorText = findPhraseErrors(phrase); | 104 | var errorText = findPhraseErrors(phrase); |
@@ -163,6 +168,7 @@ | |||
163 | clearDisplay(); | 168 | clearDisplay(); |
164 | showPending(); | 169 | showPending(); |
165 | setTimeout(function() { | 170 | setTimeout(function() { |
171 | setMnemonicLanguage(); | ||
166 | var phrase = generateRandomPhrase(); | 172 | var phrase = generateRandomPhrase(); |
167 | if (!phrase) { | 173 | if (!phrase) { |
168 | return; | 174 | return; |
@@ -171,6 +177,20 @@ | |||
171 | }, 50); | 177 | }, 50); |
172 | } | 178 | } |
173 | 179 | ||
180 | function languageChanged() { | ||
181 | setTimeout(function() { | ||
182 | setMnemonicLanguage(); | ||
183 | if (DOM.phrase.val().length > 0) { | ||
184 | var newPhrase = convertPhraseToNewLanguage(); | ||
185 | DOM.phrase.val(newPhrase); | ||
186 | phraseChanged(); | ||
187 | } | ||
188 | else { | ||
189 | DOM.generate.trigger("click"); | ||
190 | } | ||
191 | }, 50); | ||
192 | } | ||
193 | |||
174 | function toggleIndexes() { | 194 | function toggleIndexes() { |
175 | showIndex = !showIndex; | 195 | showIndex = !showIndex; |
176 | $("td.index span").toggleClass("invisible"); | 196 | $("td.index span").toggleClass("invisible"); |
@@ -246,26 +266,19 @@ | |||
246 | // TODO make this right | 266 | // TODO make this right |
247 | // Preprocess the words | 267 | // Preprocess the words |
248 | phrase = mnemonic.normalizeString(phrase); | 268 | phrase = mnemonic.normalizeString(phrase); |
249 | var parts = phrase.split(" "); | 269 | var words = phraseToWordArray(phrase); |
250 | var proper = []; | ||
251 | for (var i=0; i<parts.length; i++) { | ||
252 | var part = parts[i]; | ||
253 | if (part.length > 0) { | ||
254 | // TODO check that lowercasing is always valid to do | ||
255 | proper.push(part.toLowerCase()); | ||
256 | } | ||
257 | } | ||
258 | var properPhrase = proper.join(' '); | ||
259 | // Check each word | 270 | // Check each word |
260 | for (var i=0; i<proper.length; i++) { | 271 | for (var i=0; i<words.length; i++) { |
261 | var word = proper[i]; | 272 | var word = words[i]; |
262 | if (WORDLISTS["english"].indexOf(word) == -1) { | 273 | var language = getLanguage(); |
274 | if (WORDLISTS[language].indexOf(word) == -1) { | ||
263 | console.log("Finding closest match to " + word); | 275 | console.log("Finding closest match to " + word); |
264 | var nearestWord = findNearestWord(word); | 276 | var nearestWord = findNearestWord(word); |
265 | return word + " not in wordlist, did you mean " + nearestWord + "?"; | 277 | return word + " not in wordlist, did you mean " + nearestWord + "?"; |
266 | } | 278 | } |
267 | } | 279 | } |
268 | // Check the words are valid | 280 | // Check the words are valid |
281 | var properPhrase = wordArrayToPhrase(words); | ||
269 | var isValid = mnemonic.check(properPhrase); | 282 | var isValid = mnemonic.check(properPhrase); |
270 | if (!isValid) { | 283 | if (!isValid) { |
271 | return "Invalid mnemonic"; | 284 | return "Invalid mnemonic"; |
@@ -479,7 +492,8 @@ | |||
479 | } | 492 | } |
480 | 493 | ||
481 | function findNearestWord(word) { | 494 | function findNearestWord(word) { |
482 | var words = WORDLISTS["english"]; | 495 | var language = getLanguage(); |
496 | var words = WORDLISTS[language]; | ||
483 | var minDistance = 99; | 497 | var minDistance = 99; |
484 | var closestWord = words[0]; | 498 | var closestWord = words[0]; |
485 | for (var i=0; i<words.length; i++) { | 499 | for (var i=0; i<words.length; i++) { |
@@ -509,6 +523,119 @@ | |||
509 | } | 523 | } |
510 | } | 524 | } |
511 | 525 | ||
526 | function getLanguage() { | ||
527 | var defaultLanguage = "english"; | ||
528 | // Try to get from existing phrase | ||
529 | var language = getLanguageFromPhrase(); | ||
530 | // Try to get from url if not from phrase | ||
531 | if (language.length == 0) { | ||
532 | language = getLanguageFromUrl(); | ||
533 | } | ||
534 | // Default to English if no other option | ||
535 | if (language.length == 0) { | ||
536 | language = defaultLanguage; | ||
537 | } | ||
538 | return language; | ||
539 | } | ||
540 | |||
541 | function getLanguageFromPhrase(phrase) { | ||
542 | // Check if how many words from existing phrase match a language. | ||
543 | var language = ""; | ||
544 | if (!phrase) { | ||
545 | phrase = DOM.phrase.val(); | ||
546 | } | ||
547 | if (phrase.length > 0) { | ||
548 | var words = phraseToWordArray(phrase); | ||
549 | var languageMatches = {}; | ||
550 | for (l in WORDLISTS) { | ||
551 | // Track how many words match in this language | ||
552 | languageMatches[l] = 0; | ||
553 | for (var i=0; i<words.length; i++) { | ||
554 | var wordInLanguage = WORDLISTS[l].indexOf(words[i]) > -1; | ||
555 | if (wordInLanguage) { | ||
556 | languageMatches[l]++; | ||
557 | } | ||
558 | } | ||
559 | // Find languages with most word matches. | ||
560 | // This is made difficult due to commonalities between Chinese | ||
561 | // simplified vs traditional. | ||
562 | var mostMatches = 0; | ||
563 | var mostMatchedLanguages = []; | ||
564 | for (var l in languageMatches) { | ||
565 | var numMatches = languageMatches[l]; | ||
566 | if (numMatches > mostMatches) { | ||
567 | mostMatches = numMatches; | ||
568 | mostMatchedLanguages = [l]; | ||
569 | } | ||
570 | else if (numMatches == mostMatches) { | ||
571 | mostMatchedLanguages.push(l); | ||
572 | } | ||
573 | } | ||
574 | } | ||
575 | if (mostMatchedLanguages.length > 0) { | ||
576 | // Use first language and warn if multiple detected | ||
577 | language = mostMatchedLanguages[0]; | ||
578 | if (mostMatchedLanguages.length > 1) { | ||
579 | console.warn("Multiple possible languages"); | ||
580 | console.warn(mostMatchedLanguages); | ||
581 | } | ||
582 | } | ||
583 | } | ||
584 | return language; | ||
585 | } | ||
586 | |||
587 | function getLanguageFromUrl() { | ||
588 | return window.location.hash.substring(1); | ||
589 | } | ||
590 | |||
591 | function setMnemonicLanguage() { | ||
592 | var language = getLanguage(); | ||
593 | // Load the bip39 mnemonic generator for this language if required | ||
594 | if (!(language in mnemonics)) { | ||
595 | mnemonics[language] = new Mnemonic(language); | ||
596 | } | ||
597 | mnemonic = mnemonics[language]; | ||
598 | } | ||
599 | |||
600 | function convertPhraseToNewLanguage() { | ||
601 | var oldLanguage = getLanguageFromPhrase(); | ||
602 | var newLanguage = getLanguageFromUrl(); | ||
603 | var oldPhrase = DOM.phrase.val(); | ||
604 | var oldWords = phraseToWordArray(oldPhrase); | ||
605 | var newWords = []; | ||
606 | for (var i=0; i<oldWords.length; i++) { | ||
607 | var oldWord = oldWords[i]; | ||
608 | var index = WORDLISTS[oldLanguage].indexOf(oldWord); | ||
609 | var newWord = WORDLISTS[newLanguage][index]; | ||
610 | newWords.push(newWord); | ||
611 | } | ||
612 | newPhrase = wordArrayToPhrase(newWords); | ||
613 | return newPhrase; | ||
614 | } | ||
615 | |||
616 | // TODO look at jsbip39 - mnemonic.splitWords | ||
617 | function phraseToWordArray(phrase) { | ||
618 | var words = phrase.split(/\s/g); | ||
619 | var noBlanks = []; | ||
620 | for (var i=0; i<words.length; i++) { | ||
621 | var word = words[i]; | ||
622 | if (word.length > 0) { | ||
623 | noBlanks.push(word); | ||
624 | } | ||
625 | } | ||
626 | return noBlanks; | ||
627 | } | ||
628 | |||
629 | // TODO look at jsbip39 - mnemonic.joinWords | ||
630 | function wordArrayToPhrase(words) { | ||
631 | var phrase = words.join(" "); | ||
632 | var language = getLanguageFromPhrase(phrase); | ||
633 | if (language == "japanese") { | ||
634 | phrase = words.join("\u3000"); | ||
635 | } | ||
636 | return phrase; | ||
637 | } | ||
638 | |||
512 | var networks = [ | 639 | var networks = [ |
513 | { | 640 | { |
514 | name: "Bitcoin", | 641 | name: "Bitcoin", |