From 8b1f9d8ce3d58848ec2a291d9380fa7805f42ed6 Mon Sep 17 00:00:00 2001 From: mymindstorm Date: Tue, 31 Jul 2018 12:55:26 -0500 Subject: [PATCH 01/53] Update translations --- _locales/id/messages.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/_locales/id/messages.json b/_locales/id/messages.json index 2f5a96f0e..8a077c9d0 100644 --- a/_locales/id/messages.json +++ b/_locales/id/messages.json @@ -12,7 +12,7 @@ "description": "Extension Description." }, "added": { - "message": " has been added.", + "message": " telah ditambahkan.", "description": "Added Account." }, "errorqr": { @@ -176,7 +176,7 @@ "description": "Counter Based" }, "resize_popup_page": { - "message": "Popup Page", + "message": "Halaman popup", "description": "Popup Page Settings" }, "scale": { @@ -244,27 +244,27 @@ "description": "Remove password." }, "download_enc_backup": { - "message": "Download Password-Protected Backup", + "message": "Download cadangan dilindungi sandi", "description": "Download Encrypted Backup" }, "search": { - "message": "Search", + "message": "Cari", "description": "Search" }, "popout": { - "message": "Popup mode", + "message": "Mode popup", "description": "Make window turn into persistent popup" }, "lock": { - "message": "Lock", + "message": "Kunci", "description": "Lock accounts" }, "dropbox_tooltip": { - "message": "Dropbox sync enabled", + "message": "Dropbox sync diaktifkan", "description": "Dropbox sync enabled" }, "edit": { - "message": "Edit", + "message": "Ubah", "description": "Edit" }, "manual_dropbox": { @@ -272,7 +272,7 @@ "description": "Manual Dropbox sync" }, "use_autofill": { - "message": "Use Autofill", + "message": "IsiOtomatis", "description": "Use Autofill" } } \ No newline at end of file From 8a03be97885546c1804aebf18b367dd54e5b0eb1 Mon Sep 17 00:00:00 2001 From: mymindstorm Date: Tue, 31 Jul 2018 13:01:46 -0500 Subject: [PATCH 02/53] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5a845760c..80e577e80 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -11,5 +11,5 @@ about: Report a bug in Authenticator **Platform:** - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + - Browser: [Chrome, Firefox, Edge] + - Browser Version: From 6af35d04a389ed832c1475aad014e96ef52f044e Mon Sep 17 00:00:00 2001 From: mymindstorm Date: Tue, 31 Jul 2018 15:14:39 -0500 Subject: [PATCH 03/53] i18n.sh now directly commits to branch --- .gitignore | 2 +- .travis.yml | 3 +-- {ci => scripts}/authenticator-build-key.enc | Bin {ci => scripts}/i18n.js | 0 {ci => scripts}/i18n.sh | 24 ++++++++++++-------- 5 files changed, 16 insertions(+), 13 deletions(-) rename {ci => scripts}/authenticator-build-key.enc (100%) rename {ci => scripts}/i18n.js (100%) rename {ci => scripts}/i18n.sh (56%) diff --git a/.gitignore b/.gitignore index 06abdbcfb..2aea3ef46 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ firefox edge .vscode .atom-build.yml -ci/authenticator-build-key.enc +scripts/authenticator-build-key.enc diff --git a/.travis.yml b/.travis.yml index d18a6b5ef..a10cd934c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,7 @@ jobs: script: "npm run chrome" - stage: test # new i18n strings - script: bash ci/i18n.sh - if: branch = dev AND type != pull_request + script: bash scripts/i18n.sh - stage: deploy # release tagging script: bash ci/tag.sh diff --git a/ci/authenticator-build-key.enc b/scripts/authenticator-build-key.enc similarity index 100% rename from ci/authenticator-build-key.enc rename to scripts/authenticator-build-key.enc diff --git a/ci/i18n.js b/scripts/i18n.js similarity index 100% rename from ci/i18n.js rename to scripts/i18n.js diff --git a/ci/i18n.sh b/scripts/i18n.sh similarity index 56% rename from ci/i18n.sh rename to scripts/i18n.sh index bb68171d7..1fe109c29 100644 --- a/ci/i18n.sh +++ b/scripts/i18n.sh @@ -4,31 +4,35 @@ # Define colors RED='\033[0;31m' GREEN='\033[0;32m' -BOLD='\033[1m' NC='\033[0m' +if [[ -n $TRAVIS_PULL_REQUEST_BRANCH ]]; then + BRANCH=$TRAVIS_PULL_REQUEST_BRANCH +else + BRANCH=$TRAVIS_BRANCH +fi + # Configure git git config --global user.email "deploy@travis-ci.org" git config --global user.name "Travis CI" git remote set-url origin git@github.com:Authenticator-Extension/Authenticator.git -openssl aes-256-cbc -K $encrypted_2b3e3bd93233_key -iv $encrypted_2b3e3bd93233_iv -in $TRAVIS_BUILD_DIR/ci/authenticator-build-key.enc -out $TRAVIS_BUILD_DIR/ci/authenticator-build-key -d -chmod 600 $TRAVIS_BUILD_DIR/ci/authenticator-build-key +openssl aes-256-cbc -K $encrypted_2b3e3bd93233_key -iv $encrypted_2b3e3bd93233_iv -in $TRAVIS_BUILD_DIR/scripts/authenticator-build-key.enc -out $TRAVIS_BUILD_DIR/scripts/authenticator-build-key -d +chmod 600 $TRAVIS_BUILD_DIR/scripts/authenticator-build-key eval `ssh-agent -s` &> /dev/null -ssh-add $TRAVIS_BUILD_DIR/ci/authenticator-build-key &> /dev/null +ssh-add $TRAVIS_BUILD_DIR/scripts/authenticator-build-key &> /dev/null # Fix i18n issues cd $TRAVIS_BUILD_DIR -node ./ci/i18n.js +node ./scripts/i18n.js # Branch changes and error with details on how to fix i18n if branched if [[ `git diff _locales` ]]; then - git checkout -b i18n-$TRAVIS_BUILD_NUMBER &> /dev/null + git checkout $BRANCH &> /dev/null git add ./_locales/*/messages.json git commit -m "Add new strings" -m "This commit was automatically made by TravisCI build $TRAVIS_JOB_NUMBER" --quiet - git push -u origin i18n-$TRAVIS_BUILD_NUMBER --quiet - git checkout $TRAVIS_BRANCH --quiet - printf "${RED}You added new strings to _locales/en/messages.json, but not some of the other translation files. A branch has been created at ${BOLD}i18n-$TRAVIS_BUILD_NUMBER ${NC}${RED}with the required changes already made. \n\n${RED}Please ${BOLD}merge i18n-$TRAVIS_BUILD_NUMBER into $TRAVIS_BRANCH ${NC}${RED}to resolve this issue.${NC}\n" - exit 1 + git push --quiet + printf "${RED}You added new strings to _locales/en/messages.json, but not some of the other translation files. A commit has been created on the current branch with the required changes already made. ${NC}\n" + exit 0 else printf "${GREEN}No new translation strings detected.${NC}" fi From e19345e3c79947587da37278765634d9c17e91a6 Mon Sep 17 00:00:00 2001 From: mymindstorm Date: Tue, 31 Jul 2018 15:57:46 -0500 Subject: [PATCH 04/53] Move build tasks to dedicated script Move dev scripts to 'scripts' folder. --- .travis.yml | 2 +- package.json | 9 +++----- scripts/build.sh | 34 ++++++++++++++++++++++++++++ ensureDir.js => scripts/ensureDir.js | 0 {ci => scripts}/tag.sh | 6 ++--- 5 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 scripts/build.sh rename ensureDir.js => scripts/ensureDir.js (100%) rename {ci => scripts}/tag.sh (71%) diff --git a/.travis.yml b/.travis.yml index a10cd934c..5745bf982 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,5 +19,5 @@ jobs: script: bash scripts/i18n.sh - stage: deploy # release tagging - script: bash ci/tag.sh + script: bash scripts/tag.sh if: branch = release AND type != pull_request diff --git a/package.json b/package.json index b7041f046..a12be47fa 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,6 @@ "test": "echo \"Error: no test specified\" && exit 1", "check": "gts check", "clean": "gts clean", - "copyChrome": "cp -r src build css images js _locales LICENSE view chrome", - "copyFirefox": "cp -r src build css images js _locales LICENSE view firefox", - "copyEdge": "cp -r src build css images js _locales LICENSE view edge-files edge/Extension && mv edge/Extension/edge-files/AppXManifest.xml edge && mv edge/Extension/edge-files/icon*.png edge/Assets", "packageEdge": "cmd /C \"\"C:\\Program Files (x86)\\Windows Kits\\10\\App Certification Kit\\makeappx.exe\" pack /h SHA256 /d edge /p edge/Authenticator.appx\"", "installEdge": "rm -rf edge && npm run edge && powershell -Command \"Add-AppxPackage -Path edge\\AppxManifest.xml -Register\"", "compile": "gts clean && tsc -p .", @@ -16,9 +13,9 @@ "prepare": "npm run compile", "pretest": "npm run compile", "posttest": "npm run check", - "chrome": "node ensureDir.js chrome && npm run compile && npm run copyChrome && cp manifest-chrome.json chrome/manifest.json", - "firefox": "node ensureDir.js firefox && npm run compile && npm run copyFirefox && cp manifest-firefox.json firefox/manifest.json", - "edge": "node ensureDir.js edge && node ensureDir.js edge/Extension && node ensureDir.js edge/Assets && npm run compile && npm run copyEdge && cp manifest-edge.json edge/Extension/manifest.json" + "chrome": "bash scripts/build.sh chrome", + "firefox": "bash scripts/build.sh firefox", + "edge": "bash scripts/build.sh edge" }, "repository": { "type": "git", diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100644 index 000000000..ed94de940 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Extension build script +# Syntax: +# build.sh +# Platforms: +# 'chrome', 'firefox', and 'edge' + +PLATFORM=$1 + +if [[ $PLATFORM != "chrome" ]] && [[ $PLATFORM != "firefox" ]] && [[ $PLATFORM != "edge" ]]; then + echo "Invalid platform type. Supported platforms are 'chrome', 'firefox', and 'edge'" + exit 1 +fi + +echo "Removing old build files..." +rm -rf build +rm -rf $PLATFORM +echo "Checking code style..." +gts check +echo "Compiling..." +npm run compile +mkdir $PLATFORM +if [[ $PLATFORM = "edge" ]]; then + mkdir $PLATFORM/Extension + mkdir $PLATFORM/Assets + cp -r build css images js _locales LICENSE view edge-files $PLATFORM/Extension + mv $PLATFORM/Extension/edge-files/AppXManifest.xml $PLATFORM + mv $PLATFORM/Extension/edge-files/icon*.png $PLATFORM/Assets + cp manifest-$PLATFORM.json $PLATFORM/Extension/manifest.json +else + cp -r build css images js _locales LICENSE view $PLATFORM + cp manifest-$PLATFORM.json $PLATFORM/manifest.json +fi diff --git a/ensureDir.js b/scripts/ensureDir.js similarity index 100% rename from ensureDir.js rename to scripts/ensureDir.js diff --git a/ci/tag.sh b/scripts/tag.sh similarity index 71% rename from ci/tag.sh rename to scripts/tag.sh index f4476924c..aef6d5bec 100644 --- a/ci/tag.sh +++ b/scripts/tag.sh @@ -5,10 +5,10 @@ git config --global user.email "deploy@travis-ci.org" git config --global user.name "Travis CI" git remote set-url origin git@github.com:Authenticator-Extension/Authenticator.git -openssl aes-256-cbc -K $encrypted_2b3e3bd93233_key -iv $encrypted_2b3e3bd93233_iv -in $TRAVIS_BUILD_DIR/ci/authenticator-build-key.enc -out $TRAVIS_BUILD_DIR/ci/authenticator-build-key -d -chmod 600 $TRAVIS_BUILD_DIR/ci/authenticator-build-key +openssl aes-256-cbc -K $encrypted_2b3e3bd93233_key -iv $encrypted_2b3e3bd93233_iv -in $TRAVIS_BUILD_DIR/scripts/authenticator-build-key.enc -out $TRAVIS_BUILD_DIR/scripts/authenticator-build-key -d +chmod 600 $TRAVIS_BUILD_DIR/scripts/authenticator-build-key eval `ssh-agent -s` -ssh-add $TRAVIS_BUILD_DIR/ci/authenticator-build-key +ssh-add $TRAVIS_BUILD_DIR/scripts/authenticator-build-key # Create and push tag export GIT_TAG=v$(grep -m 1 "\"version\"" $TRAVIS_BUILD_DIR/manifest-chrome.json | sed -r 's/^ *//;s/.*: *"//;s/",?//') From 655b4bf3a5393a8e70fcf6f0d3aafc5d08fd84ca Mon Sep 17 00:00:00 2001 From: Zhe Li Date: Wed, 1 Aug 2018 13:35:56 +0800 Subject: [PATCH 05/53] add new logo --- images/icon.svg | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 images/icon.svg diff --git a/images/icon.svg b/images/icon.svg new file mode 100644 index 000000000..4c81a93b3 --- /dev/null +++ b/images/icon.svg @@ -0,0 +1,21 @@ + + + +]> + + + + + + + + + + + + + + From 72058532470c93728fdf53951133fa199b907222 Mon Sep 17 00:00:00 2001 From: mymindstorm <27789806+mymindstorm@users.noreply.github.com> Date: Sun, 5 Aug 2018 21:46:04 -0500 Subject: [PATCH 06/53] update logo svg (#184) --- edge-files/icon_150.png | Bin 15971 -> 3598 bytes edge-files/icon_44.png | Bin 5467 -> 1468 bytes edge-files/icon_50.png | Bin 5622 -> 1619 bytes images/icon.svg | 45 +++++++++++++++++++++------------------- images/icon128.png | Bin 68456 -> 3098 bytes images/icon16.png | Bin 3827 -> 729 bytes images/icon19.png | Bin 4250 -> 853 bytes images/icon38.png | Bin 8601 -> 1356 bytes images/icon48.png | Bin 12051 -> 1615 bytes 9 files changed, 24 insertions(+), 21 deletions(-) diff --git a/edge-files/icon_150.png b/edge-files/icon_150.png index 177f3ea80350a93bc6c981dbafdcdf0ad7361482..7487ea57ff5489e87be7dbdc6e589603a81c57a4 100644 GIT binary patch literal 3598 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 diff --git a/edge-files/icon_44.png b/edge-files/icon_44.png index 50e53f41e0e6ab7ce28499051bcd975b852add5a..ceb40b8628bf184145abf0a09811751cc13bbdf5 100644 GIT binary patch delta 1449 zcmV;a1y=goD!dDjC4X~5NmK|32nc)#WQYI&010qNS#tmY0UrPW0UrT!3d#2X000?u zMObuGZ)S9NVRB^vcXxL#X>MzCV_|S*E^l&Yo9;Xs000F!Nkl*PFOnoF)BJQ1i^UKO2LD)FTdSl1esa6r z#}6u50FY%F8ytHjO6j)_hr`BsV#x*qfwYpl-EJH{eAqND7K`Ei`}axx<>loGw!`7D z0r*ZLgxI$RGsbZ9=1uEt0X%&85cT!-rg4jli-u!(y?gp2qi4!N#-QB&Dl*F`fPoF-GhK2^i7{A|-iHV7A?W(F`a&l7G#l^+qxuBo`!^6V~ z@h@M##G^-#497AheCpIG+`fI=FlJ~=fOoH>)w_V(>t(|(quLDMwEPtHsz zi=`2pn}3_}@#9BfKY#ul`T6;~PRa-p0Bn`Cc$;&+&2Q%0Y%}udL6&79iOF&wsMVse zu@Q}pjfOFn2rG&Lr_(8Hn=Soq2j?7qzhBt0EW_jRnD(U%2MZ6s4C*>X%Rkp^k`cBQbp2| zP!wfP2Tm9N50&7Y#A)YKF>=V)zh zwSP>Q`2GGrJswYCd?RBFRaI5ORum;^P1n}eA{L7QSpQ>FQ&YNev$M14@9!7>l9Ce3 z>JtnG!~3$Ac>Ve{Iy*bDva+({Tzg)_;cyuD?%mrJvOp3Y27o(v?uh3@hYpFD;o)I? z{rXk-+S}W~7z41rf!Uht!Gi}_T3XT_JbxX%*DVP`NCM$_3=It-5{U?VdU_gm`?iBO z0AO`>HKh%W73^tA1Ofp>qtRW>nRL=Ud-eb%=<4c1Utgc;A9p&P z7#J8ZJlvWR_IkbWdc6s4w{G1^nSU?<+_-TAd3ky0@9$60@OV^JRiUS+2Ze=&x_&{J zbFO=PSyon-w3FUK5g(tMo4d__@#00))zx8qd>qr$(^yzoKz4REPM$o83l}b+rl!V_ z=mrRR^XARp9*-xr1676*3{rHEXLWV;UjXng)s7uV$LyB+00000NkvXXu0mjf DdXdht literal 5467 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 RTkik>002ovPDHLkV1n^GiOK)~ diff --git a/edge-files/icon_50.png b/edge-files/icon_50.png index c23c7d0ea46456460c560ee24fa0b45bd18e199d..a83fc650fbb0ac07bcde46a96cbf150cabde5a68 100644 GIT binary patch delta 1601 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 QF8}}l07*qoM6N<$g1}AQP5=M^ diff --git a/images/icon.svg b/images/icon.svg index 4c81a93b3..6678b02b0 100644 --- a/images/icon.svg +++ b/images/icon.svg @@ -1,21 +1,24 @@ - - - -]> - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/icon128.png b/images/icon128.png index 6a4a18e36642677740fd8232c11b2924031a07ff..b96b52c5762109e7588f0e6d853dad600b7cb90c 100644 GIT binary patch literal 3098 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 NX83csCs_Au{{b90nFRm< literal 68456 zcmeHQ31AM#7vCI65Y!cOXU)0@-BdV$=t(K^(?z2^G=s^W_ zwWVl?q7-r8B*c|OBKW`Go0x&pWL+lt$z0?+W8I+?leTx@)g0)Lz_9fgr?A__IQY#$y67G^gG0j+2v$*FTo@TEPwt4_x_SdjEH|pz;wu+X{7BSm<-D z`v-*zH`wywxV#;T7JFcJaQn^g?aLcHXWYs853TO~+$ZB+9^bxm?aO5kFZt>1k@1%< z-TM31x^uJ7rACMCDmdnW!j&eSUD#K9I^@H;bqet@6 zwrJWFudorLL%)AE)#JNCnx{5+U)2hQx1PBB{@ON`UeW?5X~%;KpUdYF9-?il`r@{t z+O`tf_w82vFRwO5)B1;vY8a$_lXjVU$c3|n`!~WWVg%2+E(LVFgW<1oif@g!D zwO8A}8&bt{)34h6hz3Dby*DlQ7}IH0ji#Ps*5}fOP3Rb2u)(X^m>CVL?9c*hduol! z6xrz&*uramwLKe;FYPw?!_~na+NP+fJEy;}=ZT`Lr~X-L__Zs?PnJ9RYyTe_cK3g{ zP%q!pL9?gNDqke{oL7tJ{F+uOdG{}OYCju(?(FFkXM3INAAe)Z!aM!03<|1W?&`5S z>pu1L)(W-S6@RGPnKS#n25 zYFsTl?Y8ga;eJ}gN3A!tT9xOgsQ#NqU-s}EU!dcjVOoRH*B|?|RK7`Cq24uXge7U( z(1HlHHh%heh8K#vIrf}(x=XH4)8>K-SMem#R4|X?H=z_!E^kWB5^)lKPdET|M$Yfn|5r`vC-ERL> z-}gVL{%hr+$zQi#S#f0H=u<`!v}0XdfMm5F%f~&Cq;%A3~&AUt?j>^jx1Vd z%ea#*d>2*-Sv;k1^IESxd&c|Ac{Li&nEy@NolEEb<@3$BI`dv`Hemj+HjlR(yR=Bn zp))J)oK?J0)Z)h$?fY})>%Cu3>ht)8(6zot-Y?T+`k&MH^gEyD!nq6Y-x!|X?<2qW zw+8oksARvAZR+rHqpT|2z})${FFv^llZ_pjpL?ps>A{m)CETpk@V zJY-^a%vz=01ydH(OMBb*0N=d!MQ`!oPm6h(Qnbh^Tq=wZr}DcJJ|e zjhMxo7jHSe;o+TA`pxXMcK6D?p*wbLt8zHvK*aXA2~Y2v-EVI94F}i7q<;3{mrZ7G zX+HkR@mr@Inl>sb*R+N~tIkrmdfc-D^Zxis+to*a;%!*p| z^|D#ZHV>OM;8@XCYQE$uJ-`;9jbymRkF2B`_f9}SWq>d-w*l}vk zl37cBy&it<@vg(VcItX-eVNuPW>=cKzuJIpF`HjGzCP-$SL@WT_gYHy1+V(Q*Y}(^ za^BbtUu_6FR&PW3(fdcw7@a3|?yYB1qHa&Tx$#8Uwc=M><$BfQ7mqU@>)$Q)?h3Dd zUS+*!cz>CvR<6%-{ha&p{Pl}9%hsqCAR2W|4d7LeTYiNn3C%-mh<51%>3UJgj9 zwxd#uGW#Ex@X;=xV}(W!`YO4_g&nJW#vO0kf86S_ua}(RpZs>whaai&P1TESSGEg% zBQ&YX{)p3^ZuMS2;p)VtpRRc(rthjZhxLx=+drg#&$wBu$E^Nl^}8d+-fVJpdg0I_ z6?`v!e0uYN&GkM$HN^8=|CvwBK0WirX`wxrpElRQ~0RbAAV7I z`j5|^c|WjWo5xGEKC@(C%hE-zeK708)=f4n{A^*{A0k&bulxDgIUS}Z);{vg+l^BC zeAN2+p}#-j+dAaYut_C9UsL{i%MOkAAC1^`@Qs6yJw0Xil-Y|y{9d27^3lJmMLrbS zy4RzRPWC$;c6PuY0h7o6k#xFz@{sE>#rl8PV)vqzA?-d`lKK%POtG{1yVD*SjSC0p6Z1QvQ^5fh253gFiOUT;)jd=aa zp0}g=MEyAJcAJwOL)u^K8+Q2Ggpl|5eEDUgN8fv?=G3KAwnq(Ie&gjeJ&Sdp`oH$^ zeW&kxrNcXq_)qN?F*xGui(G|aC0arge6VPoy^P`~)M)pmZuyf_kKX!W6>R#*8D?3(2#9UkOc&Xp|CT}MU+;jQ8BNI#Y z2w1!E?C2w_n+|WiW$whczI-cBVw3pTzM+HqAO3FR#vgqLggp6KnczBu&&(P#=+LZ7 zGl%~)_~l0m{QBIPUn(E_YSde&+MHhy{oCQ+b{?DYPGo{lY;f44d+KZ)8u8eo0rTrE zSRa4>(?xSicP-s?L*(7j@zqcD9J;mE#FysIpYu??tI4O|n|x}?|CY?1)9TrJf6X`? zmeeA3M!zm?yR7fsXxH3l{X;Lei0qcM{rb^!6T4hlH~2$MsL9wqTMO}KS?()zZEn}bA9l!UTl)jtx4f*@#-&MQJ3tJ!mdt5-|z)D9RTwQT( z!p&icm;8t4z1-q(myr`ke3!c6(Cv$PQVL#tXXTwAUfVR__{~>0y}IkSP6gu&_>P@> zXUg5ZV^8d9x})j7^3Rw5Y~q}34Oumfu=-U36Hzt6!xqi;j6aes%oXV_Vmko$*j= zgF9`m?@sBtb;+Ntlj@%vbtm=lqz|s&s<>y~6ExZk>=_)=Qqx9Oq*3VmnwET*_7^m5 zXf;i{_^PJW`9jnD2Yudc%k!FtHnmyf=UyD)q1`1UkGt6Br2ni1JpZ#T&uKVYASVF+ zv>@8RL0rKF^2w{G3i^5n@=IA6YejeUK6t9p5Pm7r4yc-+yEb23?J z0Z;PH9rD5DB*EcSTPO$ zv^U_ldl&;5j!#WZ^?&4%M-En~P$9?pS?I`&5Jf@IAT%`eBKa@)+_`h?eE8Km*#v+( z@~P`DS*1#q-PGk*vL?zY-yasBF`Cw+M~}#A)vC3Njg7Udv`qluoR6cQj{3-g1q=Gy zrOE-`R~G2frAq{j-=4gD`Lbp`b~6P4j{c;iB&|e=5}y_?Ui|UosVJIw2-WC{S9 zJ*rES2<22|&i>zL0Xn@mp;<${VWt3}>D@3zwDH7X^ZhqynyUg6iSUDN8+se{Y( zdtyc7JAb+wC`H6q!N3#%R5tYpKyWuwHH&l$BrG-CQX`j&jCOR($f3!&%EF3)s#trsvo)|^ph|G>-9;1$~@;L!u$3i&AZrHE^Q{r}NEud4UPHFx7 z_t#prYGnc5XSpm3031>F?nh^&y>NcSFTHYb=j{0LytUDGn-e z1#;=qCAV-9ESQS{Z=8bC8_ceh%k%(1gRWk^s&(()ov&Zx;^JKCl;(OE3fK2bmoC+Q z`spW)?qsH!M`jWX95_(x+_|$`0f6A6RXp0XY14QqUS{8y$@Bm~1mij!*Wfe{&A-et zHr?~)UM92n-z#6VJuRtJsZ!hvq_-z?M`os92IA;MFMuG(?35KUGP@1n569!@>MA57 zbjkDp$Ot8Kcx0XhasnXpY|z?*Gy9Tp4euK9^jrd{}&TsT!GUfrqF4TuHI4vCRSeIo=Qfg zoB+ruOJ*P+tf;D0s}`?fqJ_FTX9PgmvSrh~vPfr0nVbMHS|gj}aaVKPxN%vcP0kn~ zOU#^oh2#W4_VrDcXqGKb0hk5EY@n-272y1rM(9})0oft|)QXwBdGY3L#|eu`GI9j$ zNK#sY%pF_=Hh!+=48RL>5Lx+jR<;O$-o1NkAt50y*X_gkSYLm?C9G1KGG&TJ&*eE) z{rvg!TEm77wIM^Y?&{t4r1VA=)N>-|r698Fh?QAG3(*rxSZ5$Ixb(sE>_7kfGd~{Y`Y5$x;ARoe1Qah;Tq|6pu;XVCR0L443e`n> z1^6x*+t7YDi(SjmWT?|CD09Bf8k*>Dvt65&I6gD+K$A~6Smf@1GM1_2@Gv7xdQ z$}my_26L$kQDO=gFD%9{vy-!jLyYdF=8;`^V{@4C)U=$vbEa=<>TPPj<#p`fFKlD> zkKpHes{1za1FlN+ITzN=7@8Ei9Hu-234aI_(FTbxi8#oS2b%DtuAvfPbvqagR6>^V zDI+ z3b6Df0O0MbH?_FfIIUm-=MS64U5eueNiAZ;0mfzaHR;P;7x4va)*nt1d9;ROS^z|z zyw6$)Rk+5uao7U@UmV^7onr&SnVpOcQFR|~sdaqxi1t{FCYsWVrl#A`)N~&8!c1vA zYa1qFYPwty6M+X3cBWt;5zjy0gx={>Ui0_EVgdsLwR!XAWos)!&-5JK*}8G}s9dJc1m2@wnMP*n zjU9HeF2vs}oeQ@AM78}MfEp;yUgCmAv{wv0j3YjVV?$#S9+nuaE5Q!hM71=kl>=L* z1tRlfc8Xf%vKKIEzGa2#}&;fAf%X319{+xl7fke zE>u|s7w`m<;G^gOm#_F#&&g;GI4#1zIV$p<(JjJ>5%l3EHD zIEJkv0=4*O0%aiPBOsCyb_RMONx%%e^sIr3v$_Ec6%^1o@Dx{TS_KCM^g%MS5yT45 zqO-si1eUyszaSNrIIs+r#y|jA$W|H%66ZaDMJDGAy!9G;Ful@l#ytx6u*?E<8a<<& zLU;z411d8(EhXaZkiFs~5$xhw0j-M6!5&Pp*7t59>DZk4&s_zWo1arM^#I21Bdw-M z%QN*Ebv=%Orn*Dvj=sh_ZSLS3ad2>%J5OV+%G0U9!_!75w3oi{>;}E!lX-jVPQnxb zd?HJ$Y_f_cH5%{My>jJ>%a29ido}oQ0^a87%ppoqWpo&koh>Uoy*&BO*|DR?G%s&2 z8>1VWfrm0MJ6WN81v=sx$i_U1RK_S%B~vvDmShe9)l4u@OexRwlAnrW9zj!ur>FS# z!1U?UwUsMZYIs|xvxN2WqehL=x_0fVB(Ny`G737dj*ujT@}M7C*)TP@IdtfdhKEY9 z+}hdGG+399H-9f$v`EXJE59z6#XwnZAlXa;EEgFDfKrQ))qoGZ9-NN^htsFqwY_`) zqJ!a>cJbmxEsov~e1qN-Jagtu?cs+XmhFN($yYi_mY5IfRJ(;j#9xLv z6o;llY2{oY!=R9;=7upYQQ(bXSPBN2&H?~Ejp?y75-I{&ZP5tTCX#6o%T|;ghn8GL zmi3bF213vG-FM$dmj%eMH8Hm610f=gbcL(~z?!?z z%Rbbib6Rssd`9z4n;zEEt;@FDRFL88XyeC^=WZTv>?m2Xq&}`t&NouK=4`}vo=-b3 zGX+Y@{*aV&8R*>JT#ExrIX^r+TpRM{5I)KudgviOojDuKy21V*MKO;7vPczE4`oND zb(Gpc$smw(NvK7a>rGb~NMT`NJkZ61tQg0*dY=Ik-Rp*h6S47dy;ojN=ZxASGWg>US!N}z zD_W-7DJhxtU42Y5@~yY{e2>vQ&hu^^^{PPzWrSR1S3`xgG()IeEMCzw5!r+HVz6hV zRJOsD6fYV@rNI}>%Ym)z1_0lbASzrAi2)!LpbT@u4rNitFeS`#{bbb7oCmJtBRSDd zh8WREPQqDNY zb0!OQ$}9>``2gbdkvZDVMuvQ@Av$3s8qY`r=d~ONbQ#Mg4 zV%Xaa05ITWVlJRIb%s`|fK}u!_XcK$tY`oc>kUJm5d@Ypq~m5ekgN!ajW81nk;sxa zmqZ>}d_8Qzf(87g&a^mRGPT+%zT_O0g(jTW0o;BN>NwKI7BtFP(1EGTQ90E<@G!VY z$5R=*0iY_yUa>Sot?RJn4ihe}zG{h=AL8-|$sT7l*b@AmZ_y274L>f;oLznb2Xh6w zL7lVZTJTFqNaSz#;I(ad#^0@O-wodOTdoN0ScO4$;EF`w+s83Njd_r{5db=OyBRe_ ziwPNgxCvj|a+cr>XCFesQRZ%flhlhBoCOZ>Fk9#@Yo)`21@pDThYxetpQ!^rcN?4m zc-Gc61%P#QxFb)!{Aj>{0a~9vefVfJ(}Alvn?X2^A_Rc-(F7+Y(sU|b9gkkZ*$Un} zx9?~-lauI6TwSzPt0J}dEAf_YPKeW0;5N~rkDD}*>(r6b0Jyhsfu?ftKr7bsTkEPb zcQ+&XIzl(AY%Vd4qPO;*J%)@+8wb&@SSeCd02uiLk;htM!`&y>(?*c~;d9qnOIJO>9Q)qoDTw`lqHnl)?qeYnnEr-EhP@$vCm%a*OUorPWWI!X`F-MJ%7 zAlhPW4uW!prmb1MT0498EWf)by#b(~kI8bstUsgsFK!0dAC6 z=QArutJVRRxhPf&-@=u1rT|cNJex%Xx{ky|7cL~Q<9Lw9j6K9xuE2m0#f=JysF)h} zV8#q(!vu5U>A|yV=+2#uC2$ny&+n(@lAo!+l}cYEQw;@c%3Gv^xv1oRNXlZh6^#X1 z%SAmA(zb2e_(PQGbv}4Wk)(zgnH!X&S}EM13w+8@%SPZaJvbr=C~?y`925jlwj9fP*bM;Yf{lgBl9iQ$k3vx5gz;rFbv8B`MaC5f0vin10P?ba6exP^mGN{A zmP*d4A{&p8RGINb0q-%ux!=`zMi=2CV-aD=SqT;#WVPu=oPjsouGD6*!Z$ZU+S4M- zk~1)q*$kfwhA?LWZB@8O2nn$($1c#03@3Ph|NS?QL;krZoE$aIL7Q!wXKb7?Z-aOr z-wVK@kikib}Qu;&s18sLy1bu z9)S)%n5RJ7GUChWa6V==vFb>l)6m;b?Ik((?-NMPWt&wyOg(@Cn)Z^p31(IBq|3I0 zv_5a>(4jozrn*v~;jI@a<34&|V4!w8g#sW0lF|P*;mJDdR!N&QGvl<;T4Cde$M3UG zCvdN4qo)6Vh~6M z{jG}RbWiSk)CPRHrCQYbYoFrMi|HXZkyy2y#Y6b?&iu!6chj<7HPBDpcS*j@d;;^hCI% ztX{o3tK__Aa^E|)0RUR}=`+bbX;*;NmPWz=(%l~#U^FJi6iBse*XAQGBjy8`VLCQA z`tjT)y8ox1dMX{9Y)MW1o9=t=-*NsxhR!Qu$8mvq75%)7u2a@AqB|QRVHQMZWQ78s z!uMPI3I2Ty-50y17J?Na0`;ewZG$8DxwwWf*)UHDCJQhgNj8U&w)sKP!>f7 zNf=6M$b(fsF)=Y(zkdC-LU>>_mlw&A%!r!lq*~W#X<7*y?a!S%hb~`zwba|GvPN8y z;9=6>;9zamtXVuB&}ZFrtgOkv`B?md$IjZeZOcO04s1ZtHp|SPv4wCEl?jibY)o67 zO{W0CP9cH9no5=pV3k#SMeS^lpoA>3N5Mc%K>P`YTnorA@ zAJe6@&zD#Js7zC+m`24fzno4Lf|w1{W23GHx^(HH1(grdE?l@^GsUeVXF&$?J9g~I za{!oGMG|eMEdoI|05ZlT%GOY}uS7EifH=mC4AP%RZ-VovS_tZs$A`|(^rQ88e4h5f z`PxSV+=tHH_yLYGQd+xS95LwHF?x6BXRn0_Ow4LxRZ`!+{j{qz`sVotRAga-{f;1`CFDm2kc5PhIc}&6{)ZU;-qo6MUU| zD^+VmA9CzQ?=rxXz_>g_TQbB6E*b>qpE+}e-yS|{)Z2`mn3Tvb<8VQmrfQo2U}`?U zP{)CP8TEXg)kk7N2D%nw?`=;q0sv{rk|p|$A$xd681Bc{tXWfQ)v_fI=2EfdkIo4! zPa=xEgcvJ~jNHhB6bVk1O+}+wR?4juexu01fdl#e#Hv-Rw91t$XL=C8`RA$I$CNid zBk%8DoDUqjwSlcP&g9R{&NA0q%b)c1iAnThMXs! zDnJxN>%BT!@CdcL3FqiiB}=hWR4gi5K24FQ)IEHq2O%>`T2Eub)gYxDSg^Ikz=;kw ze#HxJy^b9_mK}nn;+P}I$QwqixZL3C1z!+_GT{@$wW?LC@`0mpls!3ENnaiS>IJld z68wTaZBQ2DWrk2e?om(45K;o5wm{Pd(8zNIZNhx_DU*S@?jf^+fO8!Seh zNmn0F)O>=+9+o?kQaKWeCMz9<>Y^Qhu{k8Fj^jX4hXKI&l8FOa0ak)oBXlF zb3&~n3`cOCwJD>HC=<}CfFQs-fRI8%L$$HgE#s0C&cbOTJ(ES?jUPT7edNdyty!~X z{465I8yNB9@)PyWlQVlbr?fu?a88qTwN8AADpzL77)6C7I5{yff#>0y(fmLSnv$J6 zcP>B3f>*@gt!H>V4$g8nsBo_V6Fdm6Dpjgz-MV$-uZKf#HCT4-{PeZbCIE!;%JOOt zA=2CgVw1=Lh{8am;${U5mENuqd=lxwOeIoEB)K>@qw5!Y+Hn&4h&=RwL!g5*Jz#YF z>^qA&T6nV{aipe@)9_RiZW-f+5$)Qw(^jllp>5y3ofos9yH+oW!T?zH4v+)ga5;%n z3U21$hEt6iHMAy8nrKZLH>R1)JbVKRb>i(~u(@n4sibSgV1lFf5Y0AJnzB@|!c+Ki zZxa9vha_oTKA#9ZxJn5GT;)-L>C}>R>E=g^DV!OenaGWStr5)E@A&>7-;zZGP%8y; zN$ST#HCBS43==P*!Z{R+I-Kc&k+yhXRJ6jvSE!<%*=NzJrG$#!_zwLcc;Mhll`3gN zh78f->7`LScJ9>n@87STIDSHljf>?cm*H^e23;`YSds>}xSfl;dbqK}ua6@k2ploi zps_yQDyFwrgggr-K1xsFdeLYxWTmaN0qYE1xol{kXqstj69AA4I%QDCW=6b`VIg?1 zGL%ianM4h$q|Ko&F(bMx94&bN;B7wIXbwP#w(Ny?+ERt8qY}=Ua0t3)bp)YW4Hu0e z3mG_%=b?!x?rsfyz+SXp0%1vHbsW(fV15GKxEBS}1N3=#K9cEZ25-DY3{$w!2}|9) zX~i@qmhe?DH7N!;soX2vBprpLAVe2~ocK2i2bsXJs zi=d)YAauaeE&S{ybn(W?EsOK5uq|S&Do}JoH_30@)b&M5bXL`U~>KL;kbMAa=WM^eL093b>ouOuV9nM{ptf#b1mw`AM&}HHL zs3#d{*on?bMV{IVOiPz3;3z8$W2dI`wUQ#+nBU1pN z2fzp%M;(xL87xbbS%$0=tOc4n&oYea5pR6zWCGyOA6W1YSd@?#J0VZP1RNX`o^^8t zA?AD(UrYdTUI>+>lAbt;nL|k@FbPN0a~I6KAQCX>3T~oRtTRfS0T0E;(yK5hK?-tk zAUi5P0-xbJ4q%3Qrcg~*NZ>E@04?J)3Te#kWl{PDC<2FbjLCMPB6@XP9pNB%7yzii z;3bj1hYJFTR)4Gj1W;0jfkBi}q<&Nh$B6`5PQp`^Rf<7R!5IqkPaqP8f(vZRy>3av zMnj#T6VkvJWupkf5GY~~{)}S5<90|S>}<{v3>1WjE0GgoL;*5{9K{=YF)GB+kM%=d z{&7$!93>D0{F7L+EIBbFQ3E_?#^OcZKqJ|Jm(mNh80=!4gAaQm8BeRG9sq+EoFgt= zIL`sV*mgJ2Q0i<`uj<5JR6EL>iH%r-yAWcAJEO6cX2iDTBOp3l&lspA3_D}B?i>TE zy{wT2^?FPpZ{TQ+r+C?i3#Tg_XL|&Kudfd~AIr+<3Is8!AEl-Mpocmx;$A!g;1Yew zIxl_f7z}dK{b>RC4wszh_VL6OdUZ-SsW-swRZgY=pu129F^h#R42&Com5~-Y$%BxLqI7Yx)2kR#APy}2L_AyV=_7L zWQb%V`LkYx1U$wSEKFNC9_M(>I^yZ30|yTBj3k_oxd)n@I!5(kFhG>10Jt1~`R~}+ z*husO7!M!_4jw$Doji4tCHm4Sfx8iD1*xw|8KoRIi1CEBDB~X>&U2$>9#AktVg}fY zI>uFm3`7M%bwS9J(ZCOEP>nqEBe((*EldkKRB@iml7?ktBWf6lH(&@HP=Qwd!5gSR znd&eY1`2+BRe?O!V#9FYv0i{m9#kQ85_k$O2?Q-w0f!4P9)7Yc+9Kowib1?DeYnFr zw>Q1YK32o4Zt!3#oR1%_@MwKuH07@VAIWgRFFt#6^5n^Rv;wswm?H=fHCUj4<<>=M zX3MXDAFtWN!=S7UDFjX7G-N6O(L}}*lw!+NU?~_74qtIl z5oQxiQHc`KGhe}rH4sb~jrl_>#^H@{7$`|T^Hca#%?<@;fvRWNSp)}|X|NRqhFb8c z2zfFwiq6Xb({h98T_0&l*vi=ZUHzQ2c&X5IKk1F)y2_I?>#&~uj&1o z?tsQQ3;kq zB06}JT?PP}hNB#dC1PR@PowL`Q?+WY%)A{<<(P(fTatXRAD=gPE#Wh`ACyh5*M_f%s6L|RVz z=Pe*k%i@+iM*lR}i^qVEf?z52=8;sNV9)u`+-?Ad216m_+X7Ap;T`8KvX)WB# z6a;l?tPn_p4nM*H4>71!ggN`HvH%^Yd_|K&J@Zw%k$0NLZ-0{Gf1}I4Ep+}r4j(#l zK9qDC06+jSMv@pz(B(!n%?U)GIB~*{V3jDc0!V?pgoY7fP7uIgS(|8TgdLnh^EL70 zi}U38Gvuo)t{kt_WCp5`v;Xf|KwR7X_u8FpTKujy`k(JPd9JBZ?&Bpx8t38H5H@Sl Ls`2uMufF$xwX823 diff --git a/images/icon16.png b/images/icon16.png index fe84e4249e58ed6f3d59d791b7fed0feefc24e54..2e5104503148c93e175d21adde9ea8149aa54fea 100644 GIT binary patch delta 705 zcmV;y0zUoo9oYqtB!2{RLP=Bz2nYy#2xN!=000SaNLh0L003wJ003wKqvm=%0000P zbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}WiD@WXPfRk8UO$SR!KxbR5*>5kv&KwQ51#0 znTck|U?Lg84XH-4i$$!&DwSa87Ghx`h)M#2AQpnKfsK;N-haj-gcV=|ewo5jUNPESv%)oN~d?U3CJCMG5@41>+hO)f7l+27v>;OOXxmzNg+?(Xi8 zWf@gfF@Fr>W6-j!r$(b8CMPFFxm*^G{~G|Kq+xa(Qhbru#D{#t0YS~5T)k>L6HnLr=_z}VOr z!C(+cl1Qi1Oixc!sZi2rRU;4uL_jiiLV$manNIg6} nq?^sA?<)cxkLS0dC_n!IG)x)8gQ>!100000NkvXXu0mjf(nm+O literal 3827 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 IM6N<$g1}6U+5i9m literal 4250 zcmbtXcQ}>*`+pzvAbXRnXps>mA(@91kz-`bCbEwrB@ME&G80O2D5XeNM#eFs$ljb} z9Q$N{pXd4f>i50Ae|@j(dtcY{9`}0R_x*m|&-FYH4K8Xk((};+02p;J8ivr!4nJrb zX!JHX*$Pc`UKleU02m+v13L<&rt<(mf5Amv-N3-v)7R6-+0#o%M_paW%iGh@#oYmb z!2S%Q0MmFfm&yp)ff=jTyI7p;ejA90;?YekX4x!{f$d!uHr0mk2lQADPS$jcas8cc zdQ8TH@vOsTPUj*Xm`560C@csL=DiONBF)cl{n)A?rW3c@aUIN|tZd?O)7h@zv|+fC zVy|F~KBm68GL(W7GfE}IGEJy;qHX|SkrEde;``?8HtMw}pa5Ro#}BeKj_rk&8;F|# zp*S!q!bW63#TtU9{Q!F#rH~$FmULJ3Xuh>qco_x-Y6yvK$>+OHaFiu}b36!fN!Jo^RB|#SmN~L*t8?jowVAJz3aBSJF3X($YtTj{PGMX1K;|=pCMALW^b8F1 zVs_6er6(d)_D+m>jz)&8L(<)Wpha*!RW+rsCMRs?y6rXpBEx7Rfl5v08ks1%+ZcXp zC;CX|xLmP?1ZTvO>BBMukqeEukA`s?!p(`}Y%{lHmjsh`=;8wy0sfwT&BYJ2?+NZT zAqyyqNG8)Rf1nz&BKe4qAr7!O$;sl#0K`{s#Hb!afwtt_2>>d7@>sk~;MV(s20$b0 z_R%-zX!m{Qz}GOPe-@~y;ij;q(NNuYutteRjb<`*Km9YFq;tHNYZQB_-oK&b%VJF( zV=4J8qrgZUWwW?F>%~oD!;@LdQ*n=u>Rc0=-Bwuf0jphfuSh$ZJ0+S*#1

I5byi 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 H=FYzW^|)OI diff --git a/images/icon38.png b/images/icon38.png index 7d5174b4b3a329173758253db66a14b67240f117..079ee29361b6af0793d2b0cf26563dde8c1dabc4 100644 GIT binary patch delta 1337 zcmV-91;+ZBL(B@0B!2{RLP=Bz2nYy#2xN!=000SaNLh0L008&^008&_&%x$p0000P zbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}WiD@WXPfRk8UO$U$w@>(R9J<@m`zL*X&A@< zZ`%o2!66M}g^<((jmDVTVq!`YE|L(M6ioC)FGP*miyzyAhJOPQvYU<3gS&xlwCn}5 zG$@H)U{~USn0Np`5~nDsCblUCE8Rk++mvwJCEEQ5D5{XA5kqa7zVa{_Lw~JD`-An)gj^o(H#l;^r zpL;4NdG6dfghHXBi*#H8kI_=&L8~l z-8;N}`?hGXcswqC_WS)99UT?D%jE*YFi53RvNaiok$=PpAsWTiXS3OG;#o*vz&I??Jf;pow$1;-Z!X1CkX+S)4m zZ|N_d=W*uD8PSi8jiIitZgXIS5ae&drIsSt@bECk$Hxoi=jP_VJJ1&?g2iGn#A30p zDrmJ@aqHGC(U+H(16XVJzP>&&UR_^1(iQ_yCW`BYLaVDgg_J!|3kr#`EXT zMSu8BgKbvfa2TDPomf~{_~sh2XC=pRNTpH$fXd2BP)dtki-TC1~4=<1OPA?47h*)zHC9i-;bwHpB4>JNZ)-`K6&!w zyVQ}Lv4;;IB9Ta-v9WQ(9Xely`f7G|7Jv8d-IK&wmPK=Ob8%p9w;RD=5FU?51Uqr! z1guso06?qN76fBiRyOYQ`QY>UB=N?^#^S-`WH1x*xBgyVj2apmWaElA*o{`w zeZ_ap~}k2;BYv;{s#N<U0Lj&~zP?_(N3B)^qI zxO?|59z1w}si`SMqft~=R^rg1Lw|5M9MI`>O7BL<%*@O`Mx&8a8nxAW^X5%dwYs`m zzP^vPTOU7u#LCJ_xr$QyZy*p5&&KUkGMPj$7z80Ct|5f{A_&6ofk1$0wOZ`hv7_Wa z|MQj2X0g1yjC48;wOXA8@G}8`>FMde1VLyO1Yuimv}(0FO9=VfY&N%vD>9Pj`5ypu v1E`{uO8zq|r3fJcfF(-lPZo>i4*>WN3`1EIiYLo>00000NkvXXu0mjf)L@~Y literal 8601 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 diff --git a/images/icon48.png b/images/icon48.png index 059a7ccc2bf22368bfeea6fd0dfb3c600a131539..d6fedbad6e29735714ff06315b4310f0036f05aa 100644 GIT binary patch delta 1598 zcmV-E2EqB0Ue64WB!2{RLP=Bz2nYy#2xN!=000SaNLh0L00B7w00B7xU?Hsd0000P zbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}WiD@WXPfRk8UO$V%t=H+RA_$R-6K%=l&EW*;#QrU+OAN~?4rI)>4uZ?rQ-nvY| zHiLtMxO?|*-UIaX^uXzK8sffs^$Is`+|cwNKYkpC4u2g|wMmi$&UvLs2oYB6B+D{} zhlfo_RwJEGtL)j?S*)zAD7M?}Mrmm&03aTZ>yEW|@7`702_b@*w?dO>X=y<$7E|ob z&Q3HnH30xhOG~kB+cp({d3hQ0^Yc058cN{w>C-rJ#=y=$!{FBEMOhe9FM23S*5 zqx#?7-L2wVEXJ-UqSkutbULwX*RDL}0-`9Y;?~vGWpRG^Z8jSk8yhwKTHu_6yng+9 zVSo4T-LAZqt|P|B$76K8Mf_j<@(E~8;`i_0V`5@LvF&y{_U+rZDtCH%8q?F$itTp0 z(a_MKJ22-QlarHp^X3gIDk`vl|9-RD0q7*~^yyQCLLtSjt*zBeARG>(udh$B1A%~U z0^x8Np->1PKYj!N96o%w@CmF17-Q(_>VLw(z(B!b7IhBXym_;r;sKrefW=~g%k}T- z>Toy|UNV_f9UDYZRB`QgdsfW1Z{On1ojaOB@_m=mETQJ+W;8c9uj*@UZN=2olwx1F zZ~@_PSa+b?w{L?nmX}C96s3eRnGBvkf4(NMi;_Sj5Cz=^-MTgJK^7Mmb^R}2zQna_*EIc2O--81vhGIq@Zm!Mz?m~=R0&+aejQ6oONyQA zR_1oQb^Wp|LzZPt|7r)lAINJT@cDeE_%4?V)z#I?#$^VC5Cj4NO@Be>Kq{5O?Ch-a zo12?cdh$)2C0GJV}yp<;oRfuhWDS_iL)(?|;Yr`}Z+0 zFo2<O5Z%1>+SP3PB@PD6|yI%$0HFFAJJ{pZemgT&Uw8aGH9I`AU8jXTdIwvy5 zIt4-Ki$o$ckx0Pha=~J;KoA5JdxC3$bB>jj6{J!r#N%;fG8s@x8GsJLIj18dBV&xQ z-_~2G8Bj{cgTbJm5<(auq@GebDF{O0ZyW^%rIb-h#~EV{gb=RO2sh`P4i68X5(ME- w##jXax>4rD_flq*(m4Pffk2?25W)fAAHtMAl2o5Jb^rhX07*qoM6N<$f&p6tWdHyG literal 12051 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 From 4ff48bc3ddc4909809a992de9d1977ac82c498ba Mon Sep 17 00:00:00 2001 From: Brendan Date: Tue, 7 Aug 2018 11:55:13 -0500 Subject: [PATCH 07/53] use mozAnon when syncing time with google --- src/ui/menu.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/menu.ts b/src/ui/menu.ts index c98200ef1..5cd28ee62 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -11,7 +11,8 @@ async function syncTimeWithGoogle() { return new Promise( (resolve: (value: string) => void, reject: (reason: Error) => void) => { try { - const xhr = new XMLHttpRequest(); + // @ts-ignore + const xhr = new XMLHttpRequest({'mozAnon': true}); xhr.open('HEAD', 'https://www.google.com/generate_204'); const xhrAbort = setTimeout(() => { xhr.abort(); From 37cb6f46045c4ccec6ba9c62e67ee927e8f5345b Mon Sep 17 00:00:00 2001 From: Brendan Date: Fri, 10 Aug 2018 23:49:50 -0500 Subject: [PATCH 08/53] Make BrowserStorage handle all storage requests --- .gitignore | 1 + src/models/storage.ts | 343 +++++++++++++++++++++++++----------------- 2 files changed, 207 insertions(+), 137 deletions(-) diff --git a/.gitignore b/.gitignore index 2aea3ef46..604763e14 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ edge .vscode .atom-build.yml scripts/authenticator-build-key.enc +cert.pfx diff --git a/src/models/storage.ts b/src/models/storage.ts index 346824670..ce8340817 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -3,6 +3,80 @@ /// /// +class BrowserStorage { + private static getStorageLocation() { + if (localStorage.storageLocation !== 'sync' && localStorage.storageLocation !== 'local') { + return new Promise((resolve, reject) => { + let sync: number; + let local: number; + chrome.storage.local.get((l) => { + local = Object.keys(l).length; + try { + chrome.storage.sync.get((s) => { + sync = Object.keys(s).length; + // If storage location can't be found try to auto-detect storage location + if (local > sync) { + localStorage.storageLocation = 'local'; + } else if (local < sync) { + localStorage.storageLocation = 'sync'; + } else { + // No or duplicate user data, use defualt + if (navigator.userAgent.indexOf('Edge') !== -1) { + localStorage.storageLocation = 'local'; + } else { + localStorage.storageLocation = 'sync'; + } + } + resolve(localStorage.storageLocation); + }) + } catch (error) { + reject(error); + } + }) + }) + } else { + return new Promise((resolve) => { + resolve(localStorage.storageLocation); + }) + } + } + + /* tslint:disable-next-line:no-any */ + static async get(callback: (items: {[key: string]: any;}) => void) { + const storageLocation = await this.getStorageLocation(); + if (storageLocation === 'local') { + chrome.storage.local.get(callback); + } else if (storageLocation === 'sync') { + chrome.storage.sync.get(callback); + } + return; + } + + static async set(data: object, callback?: (() => void) | undefined) { + const storageLocation = await this.getStorageLocation(); + if (storageLocation === 'local') { + chrome.storage.local.set(data, callback); + } else if (storageLocation === 'sync') { + chrome.storage.sync.set(data, callback); + } else { + console.log("No storage location set") + } + return; + } + + static async remove(data: string | string[], callback?: (() => void) | undefined) { + const storageLocation = await this.getStorageLocation(); + if (storageLocation === 'local') { + chrome.storage.local.remove(data, callback); + } else if (storageLocation === 'sync') { + chrome.storage.sync.remove(data, callback); + } else { + console.log("No storage location set") + } + return; + } +} + class EntryStorage { private static getOTPStorageFromEntry( encryption: Encryption, entry: OTPEntry): OTPStorage { @@ -69,7 +143,7 @@ class EntryStorage { return new Promise( (resolve: (value: boolean) => void, reject: (reason: Error) => void) => { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { for (const hash of Object.keys(_data)) { if (!this.isValidEntry(_data, hash)) { continue; @@ -89,7 +163,7 @@ class EntryStorage { (resolve: (value: {[hash: string]: OTPStorage}) => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { for (const hash of Object.keys(_data)) { if (!this.isValidEntry(_data, hash)) { delete _data[hash]; @@ -127,7 +201,7 @@ class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { for (const hash of Object.keys(data)) { // never trust data import from user // we do not support encrypted data import any longer @@ -197,7 +271,7 @@ class EntryStorage { } } _data = this.ensureUniqueIndex(_data); - chrome.storage.sync.set(_data, resolve); + BrowserStorage.set(_data, resolve); }); return; } catch (error) { @@ -210,7 +284,7 @@ class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { if (_data.hasOwnProperty(entry.hash)) { throw new Error('The specific entry has already existed.'); } @@ -218,7 +292,7 @@ class EntryStorage { this.getOTPStorageFromEntry(encryption, entry); _data[entry.hash] = storageItem; _data = this.ensureUniqueIndex(_data); - chrome.storage.sync.set(_data, resolve); + BrowserStorage.set(_data, resolve); }); return; } catch (error) { @@ -231,7 +305,7 @@ class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { if (!_data.hasOwnProperty(entry.hash)) { throw new Error('The specific entry is not existing.'); } @@ -239,7 +313,7 @@ class EntryStorage { this.getOTPStorageFromEntry(encryption, entry); _data[entry.hash] = storageItem; _data = this.ensureUniqueIndex(_data); - chrome.storage.sync.set(_data, resolve); + BrowserStorage.set(_data, resolve); }); return; } catch (error) { @@ -252,14 +326,14 @@ class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { entries.forEach(entry => { const storageItem = this.getOTPStorageFromEntry(encryption, entry); _data[entry.hash] = storageItem; }); _data = this.ensureUniqueIndex(_data); - chrome.storage.sync.set(_data, resolve); + BrowserStorage.set(_data, resolve); }); return; } catch (error) { @@ -273,143 +347,138 @@ class EntryStorage { (resolve: (value: OTPEntry[]) => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get( - async (_data: {[hash: string]: OTPStorage}) => { - const data: OTPEntry[] = []; - for (const hash of Object.keys(_data)) { - if (!this.isValidEntry(_data, hash)) { - continue; - } - const entryData = _data[hash]; - let needMigrate = false; + BrowserStorage.get(async (_data: {[hash: string]: OTPStorage}) => { + const data: OTPEntry[] = []; + for (const hash of Object.keys(_data)) { + if (!this.isValidEntry(_data, hash)) { + continue; + } + const entryData = _data[hash]; + let needMigrate = false; - if (!entryData.hash) { - entryData.hash = hash; - needMigrate = true; - } + if (!entryData.hash) { + entryData.hash = hash; + needMigrate = true; + } - if (!entryData.type) { - entryData.type = OTPType[OTPType.totp]; - needMigrate = true; - } + if (!entryData.type) { + entryData.type = OTPType[OTPType.totp]; + needMigrate = true; + } - let type: OTPType; - switch (entryData.type) { - case 'totp': - case 'hotp': - case 'battle': - case 'steam': - case 'hex': - case 'hhex': - type = OTPType[entryData.type]; - break; - default: - // we need correct the type here - // and save it - type = OTPType.totp; - entryData.type = OTPType[OTPType.totp]; - needMigrate = true; - } + let type: OTPType; + switch (entryData.type) { + case 'totp': + case 'hotp': + case 'battle': + case 'steam': + case 'hex': + case 'hhex': + type = OTPType[entryData.type]; + break; + default: + // we need correct the type here + // and save it + type = OTPType.totp; + entryData.type = OTPType[OTPType.totp]; + needMigrate = true; + } - let period = 30; - if (entryData.type === OTPType[OTPType.totp] && - entryData.period && entryData.period > 0) { - period = entryData.period; - } + let period = 30; + if (entryData.type === OTPType[OTPType.totp] && + entryData.period && entryData.period > 0) { + period = entryData.period; + } - entryData.secret = entryData.encrypted ? - encryption.getDecryptedSecret(entryData.secret, hash) : - entryData.secret; - - // we need migrate secret in old format here - if (/^(blz\-|bliz\-)/.test(entryData.secret)) { - const secretMatches = - entryData.secret.match(/^(blz\-|bliz\-)(.*)/); - if (secretMatches && secretMatches.length >= 3) { - entryData.secret = secretMatches[2]; - entryData.type = OTPType[OTPType.battle]; - entryData.hash = - CryptoJS.MD5(entryData.secret).toString(); - await this.remove(hash); - needMigrate = true; - } - } + entryData.secret = entryData.encrypted ? + encryption.getDecryptedSecret(entryData.secret, hash) : + entryData.secret; - if (/^stm\-/.test(entryData.secret)) { - const secretMatches = - entryData.secret.match(/^stm\-(.*)/); - if (secretMatches && secretMatches.length >= 2) { - entryData.secret = secretMatches[1]; - entryData.type = OTPType[OTPType.steam]; - entryData.hash = - CryptoJS.MD5(entryData.secret).toString(); - await this.remove(hash); - needMigrate = true; - } - } + // we need migrate secret in old format here + if (/^(blz\-|bliz\-)/.test(entryData.secret)) { + const secretMatches = + entryData.secret.match(/^(blz\-|bliz\-)(.*)/); + if (secretMatches && secretMatches.length >= 3) { + entryData.secret = secretMatches[2]; + entryData.type = OTPType[OTPType.battle]; + entryData.hash = CryptoJS.MD5(entryData.secret).toString(); + await this.remove(hash); + needMigrate = true; + } + } - if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && - /^[0-9a-f]+$/i.test(entryData.secret) && - entryData.type === OTPType[OTPType.totp]) { - entryData.type = OTPType[OTPType.hex]; - needMigrate = true; - } + if (/^stm\-/.test(entryData.secret)) { + const secretMatches = entryData.secret.match(/^stm\-(.*)/); + if (secretMatches && secretMatches.length >= 2) { + entryData.secret = secretMatches[1]; + entryData.type = OTPType[OTPType.steam]; + entryData.hash = CryptoJS.MD5(entryData.secret).toString(); + await this.remove(hash); + needMigrate = true; + } + } - if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && - /^[0-9a-f]+$/i.test(entryData.secret) && - entryData.type === OTPType[OTPType.hotp]) { - entryData.type = OTPType[OTPType.hhex]; - needMigrate = true; - } + if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && + /^[0-9a-f]+$/i.test(entryData.secret) && + entryData.type === OTPType[OTPType.totp]) { + entryData.type = OTPType[OTPType.hex]; + needMigrate = true; + } - const entry = new OTPEntry( - type, entryData.issuer, entryData.secret, - entryData.account, entryData.index, entryData.counter, - period, entryData.hash); - data.push(entry); - - // we need correct the hash - - // Do not correct hash, wrong password - // may not only 'Encrypted', but also - // other wrong secret. We cannot know - // if the hash doesn't match the correct - // secret - - // Only correct invalid hash here - - if (entry.secret !== 'Encrypted' && - !/^[0-9a-f]{32}$/.test(hash)) { - const _hash = CryptoJS.MD5(entryData.secret).toString(); - if (hash !== _hash) { - await this.remove(hash); - entryData.hash = _hash; - needMigrate = true; - } - } + if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && + /^[0-9a-f]+$/i.test(entryData.secret) && + entryData.type === OTPType[OTPType.hotp]) { + entryData.type = OTPType[OTPType.hhex]; + needMigrate = true; + } - if (needMigrate) { - const _entry: {[hash: string]: OTPStorage} = {}; - _entry[entryData.hash] = entryData; - _entry[entryData.hash].encrypted = false; - this.import(encryption, _entry); - } - } + const entry = new OTPEntry( + type, entryData.issuer, entryData.secret, entryData.account, + entryData.index, entryData.counter, period, entryData.hash); + data.push(entry); - data.sort((a, b) => { - return a.index - b.index; - }); + // we need correct the hash - for (let i = 0; i < data.length; i++) { - if (data[i].index !== i) { - const exportData = await this.getExport(encryption); - await this.import(encryption, exportData); - break; - } + // Do not correct hash, wrong password + // may not only 'Encrypted', but also + // other wrong secret. We cannot know + // if the hash doesn't match the correct + // secret + + // Only correct invalid hash here + + if (entry.secret !== 'Encrypted' && + !/^[0-9a-f]{32}$/.test(hash)) { + const _hash = CryptoJS.MD5(entryData.secret).toString(); + if (hash !== _hash) { + await this.remove(hash); + entryData.hash = _hash; + needMigrate = true; } + } + + if (needMigrate) { + const _entry: {[hash: string]: OTPStorage} = {}; + _entry[entryData.hash] = entryData; + _entry[entryData.hash].encrypted = false; + this.import(encryption, _entry); + } + } + + data.sort((a, b) => { + return a.index - b.index; + }); - return resolve(data); - }); + for (let i = 0; i < data.length; i++) { + if (data[i].index !== i) { + const exportData = await this.getExport(encryption); + await this.import(encryption, exportData); + break; + } + } + + return resolve(data); + }); return; } catch (error) { return reject(error); @@ -420,7 +489,7 @@ class EntryStorage { static async remove(hash: string) { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { - return chrome.storage.sync.remove(hash, resolve); + return BrowserStorage.remove(hash, resolve); }); } @@ -428,13 +497,13 @@ class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { if (_data.hasOwnProperty(entry.hash)) { delete _data[entry.hash]; } _data = this.ensureUniqueIndex(_data); - chrome.storage.sync.remove(entry.hash, () => { - chrome.storage.sync.set(_data, resolve); + BrowserStorage.remove(entry.hash, () => { + BrowserStorage.set(_data, resolve); }); return; }); From 6a2e2d354d43c99ac39de9021299f273b24c3429 Mon Sep 17 00:00:00 2001 From: Brendan Date: Sat, 11 Aug 2018 00:15:07 -0500 Subject: [PATCH 09/53] Fix the Windows height issue and an exception that only happens in edge --- src/ui/menu.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ui/menu.ts b/src/ui/menu.ts index c98200ef1..a83f3cb8c 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -173,10 +173,14 @@ async function menu(_ui: UI) { const correctWidth = 320 * zoom; if (window.innerHeight !== correctHeight || window.innerWidth !== correctWidth) { - chrome.windows.getCurrent((currentWindow) => { - chrome.windows.update( - currentWindow.id, {height: correctHeight, width: correctWidth}); - }); + // window update to correct size + const adjustedHeight = + correctHeight + (window.outerHeight - window.innerHeight); + const adjustedWidth = + correctWidth + (window.outerWidth - window.innerWidth); + chrome.windows.update( + chrome.windows.WINDOW_ID_CURRENT, + {height: adjustedHeight, width: adjustedWidth}); } }, dropboxUpload: async () => { From 1bd394a9ed2a66de4148fbd2972c317eb551df5f Mon Sep 17 00:00:00 2001 From: Brendan Date: Sat, 11 Aug 2018 00:19:36 -0500 Subject: [PATCH 10/53] gts fix --- src/models/storage.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/models/storage.ts b/src/models/storage.ts index ce8340817..5e62d7bc2 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -5,7 +5,8 @@ class BrowserStorage { private static getStorageLocation() { - if (localStorage.storageLocation !== 'sync' && localStorage.storageLocation !== 'local') { + if (localStorage.storageLocation !== 'sync' && + localStorage.storageLocation !== 'local') { return new Promise((resolve, reject) => { let sync: number; let local: number; @@ -14,7 +15,8 @@ class BrowserStorage { try { chrome.storage.sync.get((s) => { sync = Object.keys(s).length; - // If storage location can't be found try to auto-detect storage location + // If storage location can't be found try to auto-detect storage + // location if (local > sync) { localStorage.storageLocation = 'local'; } else if (local < sync) { @@ -28,16 +30,16 @@ class BrowserStorage { } } resolve(localStorage.storageLocation); - }) + }); } catch (error) { reject(error); } - }) - }) + }); + }); } else { return new Promise((resolve) => { resolve(localStorage.storageLocation); - }) + }); } } @@ -48,30 +50,27 @@ class BrowserStorage { chrome.storage.local.get(callback); } else if (storageLocation === 'sync') { chrome.storage.sync.get(callback); - } + } return; } - static async set(data: object, callback?: (() => void) | undefined) { + static async set(data: object, callback?: (() => void)|undefined) { const storageLocation = await this.getStorageLocation(); if (storageLocation === 'local') { chrome.storage.local.set(data, callback); } else if (storageLocation === 'sync') { chrome.storage.sync.set(data, callback); - } else { - console.log("No storage location set") } return; } - static async remove(data: string | string[], callback?: (() => void) | undefined) { + static async remove( + data: string|string[], callback?: (() => void)|undefined) { const storageLocation = await this.getStorageLocation(); if (storageLocation === 'local') { chrome.storage.local.remove(data, callback); } else if (storageLocation === 'sync') { chrome.storage.sync.remove(data, callback); - } else { - console.log("No storage location set") } return; } From d039de55eef58562cb17c4ae431dd71d85287519 Mon Sep 17 00:00:00 2001 From: Brendan Date: Sat, 11 Aug 2018 00:27:34 -0500 Subject: [PATCH 11/53] small change --- src/models/storage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/storage.ts b/src/models/storage.ts index 5e62d7bc2..2b458b553 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -17,9 +17,9 @@ class BrowserStorage { sync = Object.keys(s).length; // If storage location can't be found try to auto-detect storage // location - if (local > sync) { + if (local > sync && sync === 0) { localStorage.storageLocation = 'local'; - } else if (local < sync) { + } else if (local < sync && local === 0) { localStorage.storageLocation = 'sync'; } else { // No or duplicate user data, use defualt From 247c07af9c5d6cde7864f97e25bb652b4e378811 Mon Sep 17 00:00:00 2001 From: mymindstorm Date: Sat, 11 Aug 2018 13:18:02 -0500 Subject: [PATCH 12/53] review fixes --- src/models/storage.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/models/storage.ts b/src/models/storage.ts index 2b458b553..5f82966e4 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -8,21 +8,21 @@ class BrowserStorage { if (localStorage.storageLocation !== 'sync' && localStorage.storageLocation !== 'local') { return new Promise((resolve, reject) => { - let sync: number; - let local: number; - chrome.storage.local.get((l) => { - local = Object.keys(l).length; + let amountSync: number; + let amountLocal: number; + chrome.storage.local.get((local) => { + amountLocal = Object.keys(local).length; try { - chrome.storage.sync.get((s) => { - sync = Object.keys(s).length; + chrome.storage.sync.get((sync) => { + amountSync = Object.keys(sync).length; // If storage location can't be found try to auto-detect storage // location - if (local > sync && sync === 0) { + if (amountLocal > amountSync && amountSync === 0) { localStorage.storageLocation = 'local'; - } else if (local < sync && local === 0) { + } else if (amountLocal < amountSync && amountLocal === 0) { localStorage.storageLocation = 'sync'; } else { - // No or duplicate user data, use defualt + // Use default if (navigator.userAgent.indexOf('Edge') !== -1) { localStorage.storageLocation = 'local'; } else { @@ -30,15 +30,18 @@ class BrowserStorage { } } resolve(localStorage.storageLocation); + return; }); } catch (error) { reject(error); + return; } }); }); } else { return new Promise((resolve) => { resolve(localStorage.storageLocation); + return; }); } } From 97fbd7b633f88b9e31f3676fac88c2b2e525337d Mon Sep 17 00:00:00 2001 From: mymindstorm Date: Mon, 20 Aug 2018 18:02:54 -0500 Subject: [PATCH 13/53] UI for new storage management menu --- README.md | 4 +--- _locales/en/messages.json | 20 ++++++++++++++++++-- css/popup.css | 14 +++++++++++++- src/ui/menu.ts | 2 +- view/popup.html | 16 +++++++++++++++- 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 18c04a358..74584e1ba 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ [](https://chrome.google.com/webstore/detail/authenticator/bhghoamapcdpbohphigoooaddinpkbai) [](https://addons.mozilla.org/en-US/firefox/addon/auth-helper?src=external-github) [](https://www.microsoft.com/store/apps/9P0FD39WFFMK?ocid=badge) -Authenticator is one of the [featured extensions on Firefox Add-ons this month](https://blog.mozilla.org/addons/2018/07/02/julys-featured-extensions-2/)! - ## Build Setup ``` bash @@ -23,4 +21,4 @@ gts fix npm run [chrome, firefox, edge] ``` -Note that Windows users should download [Cygwin](http://cygwin.com/) to build. Building for Edge requires the [Windows App Certification Kit](https://developer.microsoft.com/en-us/windows/develop/app-certification-kit) +Note that Windows users should download a tool like [Git Bash](https://git-scm.com/download/win) or [Cygwin](http://cygwin.com/) to build. Building for Edge requires the [Windows App Certification Kit](https://developer.microsoft.com/en-us/windows/develop/app-certification-kit) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b0508bb4f..2a1ec74b4 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -204,8 +204,8 @@ "description": "Import backup code." }, "dropbox_backup": { - "message": "Auto Backup to Dropbox", - "description": "Auto backup to Dropbox." + "message": "Dropbox", + "description": "Auto backup to Dropbox menu" }, "dropbox_code": { "message": "Dropbox Code", @@ -274,5 +274,21 @@ "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } diff --git a/css/popup.css b/css/popup.css index 65d51099f..60cc37764 100644 --- a/css/popup.css +++ b/css/popup.css @@ -401,6 +401,7 @@ body { #add_button, #dropbox_get_code, #dropbox_logout, +#dropbox_menu, #download_backup, #import_backup, #upload_backup, @@ -746,6 +747,7 @@ body { #security_remove:hover, #dropbox_get_code:hover, #dropbox_logout:hover, +#dropbox_menu:hover, #export:hover, #confirm_cancel:hover, #confirm_ok:hover, @@ -860,6 +862,8 @@ body { #passphrase label, #security_warning, #dropbox_risk, +#storage_location_info, +#storage_sync_info, #export_info, #passphrase_info { display: block; @@ -869,17 +873,25 @@ body { #security_warning, #export_info, #dropbox_risk, +#storage_location_info, +#storage_sync_info, #passphrase_info { color: gray; } #resize_list_label, +#storage_location_label, #use_autofill_label, -#resize_list { +#resize_list, +#storage_location { margin: 20px; font-size: 16px; } +#storage_location { + margin: 20px 20px 20px 0; +} + #message, #confirm { position: absolute; diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 0b4889cde..ee884717e 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -75,7 +75,7 @@ async function menu(_ui: UI) { let useAutofill = (localStorage.autofill === 'true'); const ui: UIConfig = { - data: {version, zoom, useAutofill}, + data: {version, zoom, useAutofill, currentStorageLocation: localStorage.storageLocation}, methods: { openLink: (url: string) => { window.open(url, '_blank'); diff --git a/view/popup.html b/view/popup.html index 314ee1215..45a1a41a3 100644 --- a/view/popup.html +++ b/view/popup.html @@ -77,7 +77,7 @@

+ +
+ +
{{ i18n.storage_location_info }}
+ + + +
{{ i18n.storage_sync_info }}
+

+
{{ i18n.dropbox_backup }}
+
From c22239fa2897594355af74c5bd00e32001b66cd4 Mon Sep 17 00:00:00 2001 From: Brendan Date: Sat, 25 Aug 2018 14:59:55 -0500 Subject: [PATCH 14/53] Add info about opening an issue via #AuthenticatorFeedback --- .github/ISSUE_TEMPLATE.md | 1 + .github/ISSUE_TEMPLATE/bug_report.md | 1 + .github/ISSUE_TEMPLATE/feature_request.md | 1 + README.md | 4 +--- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index dd6efb364..033860369 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,4 @@ + diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 80e577e80..9525d876a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,6 +6,7 @@ about: Report a bug in Authenticator + **Describe the bug:** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 5134fe5a2..91a81d4d8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -3,6 +3,7 @@ name: Feature request about: Suggest an idea for this project --- + **Describe the feature you want:** diff --git a/README.md b/README.md index 18c04a358..74584e1ba 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ [](https://chrome.google.com/webstore/detail/authenticator/bhghoamapcdpbohphigoooaddinpkbai) [](https://addons.mozilla.org/en-US/firefox/addon/auth-helper?src=external-github) [](https://www.microsoft.com/store/apps/9P0FD39WFFMK?ocid=badge) -Authenticator is one of the [featured extensions on Firefox Add-ons this month](https://blog.mozilla.org/addons/2018/07/02/julys-featured-extensions-2/)! - ## Build Setup ``` bash @@ -23,4 +21,4 @@ gts fix npm run [chrome, firefox, edge] ``` -Note that Windows users should download [Cygwin](http://cygwin.com/) to build. Building for Edge requires the [Windows App Certification Kit](https://developer.microsoft.com/en-us/windows/develop/app-certification-kit) +Note that Windows users should download a tool like [Git Bash](https://git-scm.com/download/win) or [Cygwin](http://cygwin.com/) to build. Building for Edge requires the [Windows App Certification Kit](https://developer.microsoft.com/en-us/windows/develop/app-certification-kit) From 1e354dbccb6881d1d855c22e94362951163ee614 Mon Sep 17 00:00:00 2001 From: mymindstorm Date: Sat, 25 Aug 2018 17:18:10 -0500 Subject: [PATCH 15/53] Add 'Storage & Backup' menu + add user option for storage space --- scripts/build.sh | 8 +++++- src/ui/menu.ts | 67 +++++++++++++++++++++++++++++++++++++++++++++++- view/popup.html | 2 +- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index ed94de940..60457658a 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -7,6 +7,7 @@ # 'chrome', 'firefox', and 'edge' PLATFORM=$1 +set -e if [[ $PLATFORM != "chrome" ]] && [[ $PLATFORM != "firefox" ]] && [[ $PLATFORM != "edge" ]]; then echo "Invalid platform type. Supported platforms are 'chrome', 'firefox', and 'edge'" @@ -17,7 +18,12 @@ echo "Removing old build files..." rm -rf build rm -rf $PLATFORM echo "Checking code style..." -gts check +if gts check ; then + echo +else + echo "Fixing code style..." + gts fix +fi echo "Compiling..." npm run compile mkdir $PLATFORM diff --git a/src/ui/menu.ts b/src/ui/menu.ts index ee884717e..a43186c82 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -75,7 +75,12 @@ async function menu(_ui: UI) { let useAutofill = (localStorage.autofill === 'true'); const ui: UIConfig = { - data: {version, zoom, useAutofill, currentStorageLocation: localStorage.storageLocation}, + data: { + version, + zoom, + useAutofill, + newStorageLocation: localStorage.storageLocation + }, methods: { openLink: (url: string) => { window.open(url, '_blank'); @@ -192,6 +197,66 @@ async function menu(_ui: UI) { } else { _ui.instance.alert(_ui.instance.i18n.updateFailure); } + }, + migrateStorage: async () => { + // sync => local + if (localStorage.storageLocation === 'sync' && + _ui.instance.newStorageLocation === 'local') { + return new Promise((resolve, reject) => { + chrome.storage.sync.get(syncData => { + chrome.storage.local.set(syncData, () => { + chrome.storage.local.get((localData) => { + // Double check if data was set + if (Object.keys(syncData).every( + (value) => + Object.keys(localData).indexOf(value) >= 0)) { + localStorage.storageLocation = 'local'; + chrome.storage.sync.clear(); + _ui.instance.alert(_ui.instance.i18n.updateSuccess); + resolve(); + return; + } else { + _ui.instance.alert( + _ui.instance.i18n.updateFailure + + ' All data not transferred successfully.'); + reject('Transfer failure'); + return; + } + }); + }); + }); + }); + // local => sync + } else if ( + localStorage.storageLocation === 'local' && + _ui.instance.newStorageLocation === 'sync') { + return new Promise((resolve, reject) => { + chrome.storage.local.get(localData => { + chrome.storage.sync.set(localData, () => { + chrome.storage.sync.get((syncData) => { + // Double check if data was set + if (Object.keys(localData).every( + (value) => + Object.keys(syncData).indexOf(value) >= 0)) { + localStorage.storageLocation = 'sync'; + chrome.storage.local.clear(); + _ui.instance.alert(_ui.instance.i18n.updateSuccess); + resolve(); + return; + } else { + _ui.instance.alert( + _ui.instance.i18n.updateFailure + + ' All data not transferred successfully.'); + reject('Transfer failure'); + return; + } + }); + }); + }); + }); + } else { + return; + } } } }; diff --git a/view/popup.html b/view/popup.html index 45a1a41a3..6aef4b9a8 100644 --- a/view/popup.html +++ b/view/popup.html @@ -182,7 +182,7 @@
{{ i18n.storage_location_info }}
- From ae28c4bb2733f9f0a97225838c8887cb97c23295 Mon Sep 17 00:00:00 2001 From: Travis CI Date: Sat, 25 Aug 2018 22:19:07 +0000 Subject: [PATCH 16/53] Add new strings This commit was automatically made by TravisCI build 561.5 --- _locales/cs/messages.json | 18 +++++++++++++++++- _locales/de/messages.json | 18 +++++++++++++++++- _locales/es/messages.json | 18 +++++++++++++++++- _locales/fr/messages.json | 18 +++++++++++++++++- _locales/id/messages.json | 18 +++++++++++++++++- _locales/it/messages.json | 18 +++++++++++++++++- _locales/ja/messages.json | 18 +++++++++++++++++- _locales/pl/messages.json | 18 +++++++++++++++++- _locales/ru/messages.json | 16 ++++++++++++++++ _locales/sv/messages.json | 18 +++++++++++++++++- _locales/tr/messages.json | 18 +++++++++++++++++- _locales/vi/messages.json | 18 +++++++++++++++++- _locales/zh_CN/messages.json | 16 ++++++++++++++++ _locales/zh_TW/messages.json | 18 +++++++++++++++++- 14 files changed, 236 insertions(+), 12 deletions(-) diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json index 1443f7b87..a19ae0453 100644 --- a/_locales/cs/messages.json +++ b/_locales/cs/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Exportovat \/ Importovat", + "message": "Exportovat / Importovat", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "Použití automatického vyplňování", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 2a56ed656..62fbc35f6 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Exportieren \/ Importieren", + "message": "Exportieren / Importieren", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 45e91247b..a8e6833db 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Exportación \/ importación", + "message": "Exportación / importación", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index f6ab6fa9c..026880c17 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Exporter \/ Importer", + "message": "Exporter / Importer", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "Utiliser Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/id/messages.json b/_locales/id/messages.json index 8a077c9d0..193a55364 100644 --- a/_locales/id/messages.json +++ b/_locales/id/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Ekspor \/ Impor", + "message": "Ekspor / Impor", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "IsiOtomatis", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 1cf205359..1e2fbe060 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Esporta \/ Importa", + "message": "Esporta / Importa", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index eb599c289..777a9a81f 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "エクスポート\/インポート", + "message": "エクスポート/インポート", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index b3f5af9d3..67803e766 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Eksport \/ Import", + "message": "Eksport / Import", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index bd193cd6a..936ca5d5b 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -274,5 +274,21 @@ "use_autofill": { "message": "Использование функции автозаполнения", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index db1796b9b..c86a2163a 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Exportera \/ Importera", + "message": "Exportera / Importera", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/tr/messages.json b/_locales/tr/messages.json index 63dd00346..38e200b12 100644 --- a/_locales/tr/messages.json +++ b/_locales/tr/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "İçe\/Dışa aktar", + "message": "İçe/Dışa aktar", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/vi/messages.json b/_locales/vi/messages.json index 2db402bec..538787a5e 100644 --- a/_locales/vi/messages.json +++ b/_locales/vi/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Xuất \/ Nhập dữ liệu", + "message": "Xuất / Nhập dữ liệu", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "Chế độ Điền tự động", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index a1f3851c6..8824d3431 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -274,5 +274,21 @@ "search": { "message": "Search", "description": "Search" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index 5f700c34a..982f02eaf 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "匯出 \/ 匯入", + "message": "匯出 / 匯入", "description": "Export and Import." }, "settings": { @@ -274,5 +274,21 @@ "use_autofill": { "message": "啟用自動填充", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" } } \ No newline at end of file From 2b4d26083d85f29098330bd94f3146f35dd9ceda Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Mon, 27 Aug 2018 19:58:24 -0500 Subject: [PATCH 17/53] fix bug --- src/ui/info.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ui/info.ts b/src/ui/info.ts index 232183d10..616b74f09 100644 --- a/src/ui/info.ts +++ b/src/ui/info.ts @@ -38,6 +38,11 @@ async function info(_ui: UI) { return; }); return; + } else if (tab === 'storage') { + if (_ui.instance.newStorageLocation !== 'sync' && + _ui.instance.newStorageLocation !== 'local') { + _ui.instance.newStorageLocation = localStorage.storageLocation; + } } _ui.instance.class.fadein = true; From 6ba9c476b11d1d94ec0ae59f6aa3bb26df53c562 Mon Sep 17 00:00:00 2001 From: mymindstorm <27789806+mymindstorm@users.noreply.github.com> Date: Mon, 27 Aug 2018 20:08:53 -0500 Subject: [PATCH 18/53] Update .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 2e3916624..6e3982439 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ *.js linguist-vendored css/font-awesome.css linguist-vendored +js/jsqrcode/index.d.ts linguist-vendored From 5ec5091ff70573deb0ea092da84f244f7149d8d2 Mon Sep 17 00:00:00 2001 From: mymindstorm <27789806+mymindstorm@users.noreply.github.com> Date: Mon, 27 Aug 2018 22:34:05 -0500 Subject: [PATCH 19/53] Update build.sh --- scripts/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index 60457658a..7ab72d043 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -19,7 +19,7 @@ rm -rf build rm -rf $PLATFORM echo "Checking code style..." if gts check ; then - echo + true else echo "Fixing code style..." gts fix From dd770624919f243a9a85ff26f21d8d0b346b0486 Mon Sep 17 00:00:00 2001 From: Zhe Li Date: Wed, 29 Aug 2018 17:24:36 +0800 Subject: [PATCH 20/53] update chines --- _locales/zh_CN/messages.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index 8824d3431..a7a8ba55d 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -272,23 +272,23 @@ "description": "Use Autofill" }, "search": { - "message": "Search", + "message": "搜索", "description": "Search" }, "storage_menu": { - "message": "Storage & Backup", + "message": "存储及备份", "description": "Storage and sync menu title" }, "storage_location_info": { - "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "message": "选择数据存储位置。local为仅在本地存储,sync为云端同步存储。", "description": "Message explaning the diffrences between sync and local storage spaces." }, "storage_sync_info": { - "message": "Automatically backup your data to 3rd party storgae services.", + "message": "自动备份您的数据至第三方服务。", "description": "3rd party backup info" }, "storage_location": { - "message": "Storage Location", + "message": "存储位置", "description": "Storage location" } } \ No newline at end of file From a768204a5e52c8b615c9c61082bcbde2bb021177 Mon Sep 17 00:00:00 2001 From: Brendan Date: Thu, 30 Aug 2018 23:01:24 -0500 Subject: [PATCH 21/53] The last popup mode related bugfix --- src/popup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/popup.ts b/src/popup.ts index a3fadf618..e80ccd6da 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -109,7 +109,7 @@ async function init() { } }); - if (ui.instance.isPopup()) { + if (ui.instance.isPopup() && !ui.instance.isEdge()) { ui.instance.fixPopupSize(); } From bb0dd041a10a24b2658672aa3cbc231f489a0e2f Mon Sep 17 00:00:00 2001 From: Brendan Date: Tue, 4 Sep 2018 18:45:46 -0500 Subject: [PATCH 22/53] Edge icon changes --- edge-files/{ => Assets}/icon_150.png | Bin edge-files/{ => Assets}/icon_44.png | Bin edge-files/{ => Assets}/icon_50.png | Bin edge-files/images/icon128.png | Bin 0 -> 3098 bytes edge-files/images/icon176.png | Bin 0 -> 4087 bytes edge-files/images/icon20.png | Bin 0 -> 868 bytes edge-files/images/icon25.png | Bin 0 -> 1047 bytes edge-files/images/icon30.png | Bin 0 -> 1147 bytes edge-files/images/icon40.png | Bin 0 -> 1377 bytes edge-files/images/icon48.png | Bin 0 -> 1615 bytes edge-files/store-icon.png | Bin 0 -> 361029 bytes manifest-edge.json | 10 ++++++---- scripts/build.sh | 5 +++-- 13 files changed, 9 insertions(+), 6 deletions(-) rename edge-files/{ => Assets}/icon_150.png (100%) rename edge-files/{ => Assets}/icon_44.png (100%) rename edge-files/{ => Assets}/icon_50.png (100%) create mode 100644 edge-files/images/icon128.png create mode 100644 edge-files/images/icon176.png create mode 100644 edge-files/images/icon20.png create mode 100644 edge-files/images/icon25.png create mode 100644 edge-files/images/icon30.png create mode 100644 edge-files/images/icon40.png create mode 100644 edge-files/images/icon48.png create mode 100644 edge-files/store-icon.png diff --git a/edge-files/icon_150.png b/edge-files/Assets/icon_150.png similarity index 100% rename from edge-files/icon_150.png rename to edge-files/Assets/icon_150.png diff --git a/edge-files/icon_44.png b/edge-files/Assets/icon_44.png similarity index 100% rename from edge-files/icon_44.png rename to edge-files/Assets/icon_44.png diff --git a/edge-files/icon_50.png b/edge-files/Assets/icon_50.png similarity index 100% rename from edge-files/icon_50.png rename to edge-files/Assets/icon_50.png diff --git a/edge-files/images/icon128.png b/edge-files/images/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..b96b52c5762109e7588f0e6d853dad600b7cb90c GIT binary patch literal 3098 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 NX83csCs_Au{{b90nFRm< literal 0 HcmV?d00001 diff --git a/edge-files/images/icon176.png b/edge-files/images/icon176.png new file mode 100644 index 0000000000000000000000000000000000000000..bba0f98c975dcc1d6e57edee3e536b7d0470b0ba GIT binary patch literal 4087 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 literal 0 HcmV?d00001 diff --git a/edge-files/images/icon25.png b/edge-files/images/icon25.png new file mode 100644 index 0000000000000000000000000000000000000000..7e59f8e1248a747da24fad72d6bd912dcffb0e73 GIT binary patch literal 1047 zcmV+y1nB#TP)P000>X1^@s6#OZ}&00004b3#c}2nYxW zdYFiZk&SB?K)KJup{l1V+gKAi_z*(dO(T@}~ zXg;4R&6aBt^%uG+ni zcl%(&UO#((Ywf+)+6<+XIX*uAL#NX@2_Y7SVeo%JDaFgnOFEa!eQIxS{|oVW{7*@e zp6NS4lB6#uCnxhntJOL=jw6&(B$G*qqF6P;rluw|H#e8*Q>hd#E-qx5=Xuy{HgFtQ zqtR#v2qDD$YTVk|LMRlfN-{My1&_yrL?VH`y*(I>MmQV}M59qGEiK7@qobpko0|gw z5JJp^QmVKFold7%@CvuvEzb`Q4zRem2tg3wa5ykNK8}Hb0RVv4>s73ylrrr1n-N0L z)6)aBT8;brd+7Cgba!`G%?O0SH`LbF!fZA}r_;Te1pqueJRqG;L#0w-ety2pUX{e}_rve^%hA4t zXf%q2g$1;=wN-pLWh75ePneyZ{bBU~1qApvQnT5N#>Pe@lgTp2*49=SjYd#PF)}iO zp`jr#3m<#Kp_enwqg9Vn%Ee0&7(wQZE%#o^(hVx!yJTg2mWSvD99 ziaSz}tgWr#^z;;)o0~A1Ojuc2kr$OhqtPhjNF;(tBqGahw;MAvGnGk7!EqdVdwXSh ze}5mDOa_7=z;3r=eSN)hO)w1XcDq8ye%~B#Zf+0=1P}-W5R1iR+w$@e1o?a(`FtKKl`6zgN|{(J_BVjhVzH?I(ckeiU>F80%Vq(5=<4eF{U2k@?{Ed8 R?JEEP002ovPDHLkV1grD&NKi3 literal 0 HcmV?d00001 diff --git a/edge-files/images/icon30.png b/edge-files/images/icon30.png new file mode 100644 index 0000000000000000000000000000000000000000..2a1907f489b94563a156116663494a976a0c24ad GIT binary patch literal 1147 zcmV->1cdvEP)t*95fLVcc2=T|S|nDt_0hNrqM$BZloVEpBtq85NQy*uSF=P(V$99tF=vZQW;1st z@xgWf)%hOh`<dJ&JuD}q~FTZQqLFJBN022oX2 zg|)Rc0Ko0-Ez;>U03el0L9f^USVF6mD{M3xvAeq~@e@MO-`_9tU0q$_?CcB*g+lNh zA0G?j$;nAvUtddVDI^{M3WWk@vl%Lt3b9xWu~-aRtroSlwQ}hnk}SBW)6-Mf?RMDh zb_4=}p9Td0CGg+DD%ESg;#kgCs)dCGWV2aGOm>fpYHx4H^z<|Ups}&BQmRO9w;PE> z0v3w}6B84%ITgV+n+-ObO|W^Me-rrV=m^nh6dfHM?*k_i35<@8z6tVC$$|@o0$7%P zQvsLD1wsf)+wIQI4tSo2Qd$0mN)Fe_$;q35#W@s33DBa?U@%DXWWl8>Z3Lew33zpN z73p*u4Gj$#A0HR|nM?-D%gX?OuC6W&3=D|oC6h_)@9*RC@)Ao+OQ@={4(b5WvjLjL_p37Z)o+0YFIzDZ3BlnnUTFXfz6jVMI4trJBXK z2%`a;sOSP0UnPB0H9W@h5W6ptyiy6IZ41jUSTgUFGwbn0D$W1YDt0f z^YeE@Wx))?z-qN(c6JuP_wGee6m&YBY=KNBBk?&L4$*gQHk;vcxhjK$!60(EoM0Ch zP);o^Er>)SBHzu;jmS2cOpQ zHZ}kNTCG+DmXp)zL|a=M*4Nh&3WWdw&CShlI2;%p9F*h}LMV!5*}t2bntmxdpPrsj zC=`Swg+d|Pqj)@yd_E5VvcEzc$AJ(6jYd;;q2X}&UYX0~=zKmee77|WS@bCZtQOf7@b*R;9UoMv` z|89PwR4P>_o6Yu60O0faI%t~y4SrGTIVI1 z$(Z;-+CL1;x#!H8-`u(P+_|H|zp~ryp9mqp5JHj&Ap{W+&+{D5^IyGQ?~i$Td4GzI zot>SZ9S+AO$8kcW#c`bAa5yfvx3_x%KxMbvKgGw#|B;%SYKTOy1(UHGizfMFPxRl2EKtwvf}nj+Z2!GWah=;#RT?d_sHJUonwiV6TgQ&STjA0I{E z%E}5H4oA@Zihu@#0n^jdLH(3ciS>1MTN?kH>@C+gkv@-Q67oK~TIdP{Di&2?cVUyWNh_(NWQ^s;ZIzdc9tZjErDgwv7 z^01*S`f@RH@{JiEAD4v-HP>^jY!6zkRUVT-fxJ! zMY4cetrl9XR?>cTbtRrZK0YEbF;O<@+S;1mXZ^baQcA(|JP09RnD8r+)9DQA^LRW^ zt0nt1L`6j*Dk>^8z~SLxOifL}Y&N5>uTO0EeZ?k|DJX=}EDiScVzpXfu~>~=d&PEH^Q z0*po@a&vPf-;9A01c9iwx3{k|Gc)yykrC42;h~$Ml(IKBH<4zM=;r1Ilv2V1`08{z zKYjzd`_jVHl2O+23+!* jz!yrXB|ks^F97%(SHh4^F&SJq00000NkvXXu0mjf$RmT| literal 0 HcmV?d00001 diff --git a/edge-files/images/icon48.png b/edge-files/images/icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..d6fedbad6e29735714ff06315b4310f0036f05aa GIT binary patch literal 1615 zcmV-V2C(^wP)*Atd z)rC+j*XKeiO2MM8E))tb3hgE$Sc-1CDI&U%LN#vGji7CnK2<8nrPG=;kE;!hY0tR} zb*^*Mnat#-Y5gFO|NP(i&-cTBoQ{m{*qo?e4dyCiWwXC;5qp(;k!qU=G*@q7w z{t_vrm%U!EjdQ-*Q7KaY3s-YIrlTN}=vJzG#wW*`E9kv%|c<}-NuxZmKP2Pr0fDi&nl5+A)C(zy9jjvz7D)yEwTg-;Y z!r0gt&Ye4_YJ2kJ39MFY&c3D-sIIOy+a)I$V_7@5(X*MM1Qr(;5s5@{@|KsE4GmgI z7)qeGw->#=y=$!{FBEMOhe9FM23S*5qx#?7-L2wVEXJ-UqSkutbULwX*RDL}0-`9Y z;?~vGWpRG^Z8jSk8yhwKTHu_6yng+9VfXIcuDq45BgV(aV|2Ym{9pX?3208@_wV0h zVq!wE?RGo%?c291cY1mn)6>(6?RLA-(9obeFy|bTlaqM!<_#(;DzJb5ezV#E=p^v; z=~IM4A;qq(t<_8*91f$euTQZ9fq-rT;cytCPzWDCegpsU$}4q;c!@Ypxd`^gE5wuNIev#gff{7o zKqL}DDwSH3*qSBu?AbFse*72!aOu(|Y~8vw??Dz97j^wFU%te(Yu7aWO-)Uj%d+l9 z_VD3D0Kl0uXH*GXzkVG{OG}EK>sID=yLJ7tEJK!MP5){Ky&uSHAMp8nruZ(G3)R)t z%En~|gb)M*0Zo5F=Rhiz!tCs<@|&BRQ+x*x9?UD|o;`cu@pv@-n)|@dZARXBfNw{H zj*bq+cDvnp^ym=);KYd&xOeX!qS2^f@sv_rym(P}Y(pjV;K2hp91d06fddCjUGS|| zE4sS6(AwIXb%EDHTU#6ae!niqPy+q^{aI}_H8rMRC<1{1Zr!?tt5>h$)2C0GJV}yp z<;oRfuhWDS_iL)(@5lZ7_c1UqfT5uw)iJ!Hq5_SLjW~JoB+ARnbH?JF6EZqFny9O* zv;WtbNF)%C$2B{bmzQU~N`LwCMfLn5iXy72s&qx9(`m$FG1zQ2)34kU6B9|1F;>DD z1EuN($>DHh)qO5ltyV*?X+J}fBzV1EQ+ZGu3Sd4OjY5{? zypOcS1m_&GEF&6?f>JssGR8UuLFkJ_A~camz~yqmVzEFF1QdIMYk_l)m6a8wQYpma zabz+XP)Zqq4#GL7BO@bYjIrO=Tc{aOO2>o2pq~;#7$KydQaULJLg8;51qP**QA)=d zV-185uG9$UoDL5UpArP&PsUgU0J>4;#P?EWl+rl>9f3fgpAfIMjCDTp9dbFL4VS2 zA9X{4sN;Vxyu0K{+S20QkwyP4#MPJck}`Ag4}MM?)2A1XxjwzT#YG)^4?3~(XI*L2 zrPHsUFnz?dyYHTI&vY^3zA67XeaaoV_m@n+EBA)NF+UprQ0tCDl3U>gYHm5C8!X(4Rmv{Y`|0AOHd&@C^Y77S03^00CnNK(LGn6$}Ld5P)Fe8~_0j zFopmG%a~BXP!IqC2o}x(5U`5C?&a$i*N+9T%CSbyK)f0u!3Gcj0bL0|uyl;eH0 zXhZ;l)d&eTfB*>SN&teTYh+*-2!KE%0uZc5NU#9}KtNXl5G-9I1G_)~1R4>5U^PO5 z4FMsL{`SnfgE1Ds(ttv|EDbE&0s$KdK(K6#DtrO~O9?=*EDbE&0s$KdK(K6#DtrO~ zO9?=*EDbE&0s$KdK(K6#DtrO~O9?=*EDfxeZvC*=w$CvZz)PYi%L@V!FE1#f3=jYT zB?KT?N-(1m5C8!$2tcsBpolU+00fi}xJXGY5-VA=X3a%y+O)~1Z!iw1t*z~yl9JMs zs>`LN6)l;xw5A0K1cE}Kf*h@+c#>Z577j+QKABLKreKplZoRMp=|#NJF#Pk*Iv-@eDyRqahpMhVu|ty@PB z>=Ig7sM5VbfKospkOZp9>o@3HT9}uYx5g9#CJ0u_mMvRG5!hJ-^%||gUi;Osr5FpK zb%835~*l;unh!2 zAT|LOudl!US{yxkG-k_-8VnogTAM(kRUW$}^?S4uENWT(kPxs~wQY!kTTdN2bPydo zb`;H<$82;pJPf}<00d$XID7W2IDGi9ICA7jEa5sv*WIoC`}gNtMJJkth)yVORk2XAe4BXSzyZO1UJX($-5!<<7%<@32HTbYR4-WPx22AV zF&1F+=FLR}<|U#KAzb|QuY33IXkjToJy2ng3Gma!J$v><>!fD^DYe27VC0Rg^65E{IHxJQMh{54rai7%{S6l(H3WtJq5b%)zi`M0rUmmR=c@Xt=mHJ_t@>{|W zKm4#Ky+X5{oc;FAW(L!{ckgIm{9uAQK>!582yjRIXPS^-oTd`sfG2TjgoKXP?Bus$se!6t&((w2+>VSVTY~wA*dOQ(hEI?Q;4_B3n z7f;VXwnx0++`4sZjKA_QpePCiWDww2ky=Mn z@$f8I2LTY!gg{PCj^JxHJkf)++2OxsuPYNQQsUv{%=g0`y5y2e(1#Tc1nVH6F#(>^ ziQn@LPb6U1ZrZfz2jSaft}7EPa^h#$`m)B+@Ba(iCS_Gg3yUm|XkGCPqCCPK2?T;j;Es{o zXf}jgQCKi+tm*#qsCd4f!xJ?`cr<|YBwf7tV}sSH7j00DCeWM?DH~=Ko6^kT9kCbqA4sQoZzPo zxHXI#3v2`dV+gQdMR{_VC))uP9q|7W?gS72=XVNKrnLw0=NJK+LO7+Ea7L}?{t(M`|L$$(bU>yX^B@pFWmOfNm zg49^g5`GvP(KKOfMWvE2WC081Q`QKpr)oq z3?3Zur6tceOq(`MOq@8;v+_`o{{$dd{>LCW3nqL%XQ@SF0nVK}mmH56N7LLWG4^1y zsRVF)XlmqOHwXlf00b+5L5IsAU@8F!mZ_10-5?M^0_e{QV9?>Ra{`xLcA5C?Z-49j zfFyEOR8)vx{_>Yelwkj%5G)5V=81uLvU(d5KcPE(_^=JGDDi<~KYaLbC3W$%5lBrF zfM7MvC$x9iuwmknM;>vIQ~xX{@sfC+oyI{l|r>5ncx=)fPlsXAXpj)2d04l2t*QqU`6u4FAx9$jR`=oG!70-0|5|- zB;d=V!n(S;NGlZlN~TUeHj(2KImQhN+2u#GB~cd9%HnOZeQ;g=OZP2%T7$6wuI;`6 z*z5SjiklAG(c01>3a|kLK)?|J2$myE$OHl)paTI2mJU&X4IlsljtKbDpXJChWCDSJ z5y;BQ60>H_a_x2J&YfK!W9k7AEK?%~yR9RTk&z+BjT>j3UtT(dTS6~sqAU;q0jUHa zSW@xYTzKDqfBqH?#sb)k7``=00OHjo9_$1G5YUML1WTuAz!neyfhGw+u$sh!oge@L zIuU?i=@bpv0sFMcS7i)ES5HE@0ZQi`OxZ#Eyq|}g`n=7SU6xx_TX=$lg zwQ5!49Z7EK)~%cP;~)Q+WGTTq9D*e=-u%Dk+_`fl)_|fxAXo&@pB1cl!&?wAng9gL z=yI{l|r@bh$XxLfj|&|Upgmrawf)^w+31|?%*1sBlJH`TNcKY~n zO{2A?*+~F%KmY_{6M$gFW`jK-00Np3fM97B5SRl3AP}2?^8T#YrpVoM=bd+ov17-| ztt0@YXJustz)QHOD}lfWmW)Awx&{??fq<<9a7$=wWZ@SGSV#bZWnob3U3%j35z8?a zpq>W)+C~84Wm`ny4+vOH0D@(4aN!yV*hTN}j zmSq8jJ0M^)0SK1O(S>gyU>SkH3s%dv#|C07fMsVj+zAAMz>8NPq6{ZNz!CxwEK33j zH$Wf=1Rz*Jh%$Tx0ZRx#uq+8A+yH?f5P)C>A2+Y)^HU|;DB~PHTvQm_nmy0jI{8Cg@REVmo zs?gH2XHR@8I(fXFl@J)gdiv?7#rEynJp)%?P`7T~#Ecm;>VNwBmoKmGXHO^ zOX6073?JFQqM{;Q+)T)hQ>RXezx?GdV&%$}A+ci7LNbp)U6k!Eby2>ZEdYszka=V_St8}C!c)cap~3-H$p7#WaL-? zh?jNeVz3>0@4ffLf&~l2o;`bl4Z>3IAy}3MHfU}gJa|ydn>SCaTemJ~t_R9r2v(rN zZJ)DzEBO20|6V-v%roe(vM;=iJc3{~LV^vZ5IBDPxF{_x6(4-?fhi`#&Ljyyu#yCb z!~G$^;3szE8L58|Z=G^$CSz*A2>6&iDud`T$5R06m?G&OPo zvwP{%r4Xxt#XOq35UglauuUrh+<(R6t&l(ff`xNHYXX}#Z4$F*&-SJNN^3AdHLiHh zqEe_Dc??NAxF0|yQW7Azf-O22jR z*NqJ_&$n21ptTi?47U45fFBWJRzBbGYh5t}OY86i&$v~qR*CoDe?NGR!(>AXttt zA(ILM-08k>;X)Nvp$WxLmi1KZ7H~mJ=sVh<^S0xi!a8{x)se zILenyrnlaDOK|@c5(va15E#L__uhMBF;Kq!@y8z*YuB#zyx!iudyB=37kgf=iUPR< zmuKli0)eIo;Fho{Ix_b?)cMz4ydDc6(_V#T^QUlj@7}GjMpR`Y0f?80QM1ry{(ct{ z2*e`*!HP!+`=t{&cI+7DLzYh2jtU@HcEr(WFL?IBaOi6)2GGGojawKkAm$Y0Ku{=p2m4oR#t|Iu#AhEe+C;MSi7cvdJo0| z_;-q^tC?r=LjnP(1Pl-_r>-EMi3C3S=pz%&hRr?^fMEFq9@TzBVE_L8Kmh`d2|%zM zqe4cn3Gj@g+yRdS0!|4)u$+=YKFx>rX(q#7 zp9utJlE3Qe>V#gr&CJXcDJdyFhsnbYg?0w&YLghhAgMnDOX7)yqMIfVqzkQS60!I8 zs{F$cH)8>U^q)XrI<;b-9_sg$l$40^cfTXmw&H|TalfeZIH#unF_&5jvtOrl0e_SeUmI9*oOy= z9|SAd=ezIT=jG-3ZiC4N1dSgA%jC1w0^9TR^S$w6=8u=sbJtu^STJm?HzgWZif1eh z@f_l1NT6&rli!KLlf%TK%l%kaTyaHW2OD}61k2FC*<^M>L4i%~DD!3b@Zo|VVgxA# z%iADD)*xTUj2VOJG7^Z_=+UDSILhopAXsL{&kF0ibm=0ly6P$`d{N~Dk7w)Gub-;w zw5bPzrA;V;ZOBbG-4tv9F1=@7L7c=5g5~l=L3TL=`1S^qk~w4BqD2dF>#ess%WPRL z2$p35jqlFNYwkFx_gDaab*HGPD84JmA;yj!D|qC45R)%2Mc38U8N-@wHOXE#U>|tk zfrRFlj#mdi$-4gf>*Hl>w4YlLFTVJqIC0`cqkY)$4FR6cwYa$0+H*2qoP80jct&Br z8UpXU^Ntk3;tush1dDHbCQX_oe)qfIsR3YMEfXe85Eoy3v4NI*V=`_Jz2S&bJRF1>+ZxdevnatWlSriz&}X9n_VSUj@Rr%xBAP2L#K zC%5|{SZ;|SzjOlq`}Y?&-gu+*dQ7Td@ZiA$ZwCkg!7}Nr4Unze%EJ4G-vqpU`}Sh? z?AZZ|er#7ESh3ms_?>~pdXEK&ZM#)__~Q$6=gtlMtfl-8;lmFPv{gMHgKp=FgumY@hAnd*Az>Sg>G$;5Pt}q7W#av#1oR6$K17 z=}CY`ney{~TRXN23k$`QPd+I!Gc)xJfCg42dtX?&tNULu7C?hQ2GIn*Uz|2=nz-bW zOT-gTJR#*pN!46=ECaufbLX9Rq7y3)e6+UW0K#rP3Gh5M&p-dX_|A8}qo;umt>QUk z_)YM;?z#(N<$xIk%K;>0(UZWHS6(Szdg&#>bKRJjxC3j+k|l!Q$w6{PAlchPXSE;~ z2uLE(x^-*u*kg~0b?erNdGqFpFTea!Qcd0!%X7!@*bRP}GKljbCqoh~tYqlnSZxRl z88SpHTeeKxe*5j(zTd--K6$dMS6_Wqj2blxVwJ$z1HnoF8V=EuKt@J}m^yW;Sg~S7 zr~_M67$h;r7 zihS$GU*X_Zm2qF_xC_ZW#JO#zjp+iG`Rs8Nv&_3rJ6^e%&Ay^*5L{WMY;7)FyJ&+fE|L4in%LZt?W#(GQy zX9NonzemGQC%Fa1pK8v@$q~7^xuR32PUuMY{0xd+V2bRwam&ieB0m1rwQE!5g65w0G81H5#{=Sn#lT`@Tsc5zwJa|yl)=C%T zXjNmIKLkb;y*v+N0sLVvrB=Rv`8tk~t00R0F;A^yrTfut6KdWDn^;f!J(LHaIg2ng5d-v|eum@QYM!6u6 zBmo}Iz}KjQ{^!q=tSau|(glm=CE6U%igV}AiQT(*L$u<7!hR4?On}EvvRLso8ZVKc zZHkvC+5OT5iynotU`6eC4>z8H?Ao;pLm$GSU>yW}B)~JYazn!5Omt%`Kj@ zs8sswe(VL!@b?D&8STU)p6ixB``fNvJ15y~%Tu_%dKAV2*cMg8{W*H{Xy|JnE@lG# z6V38>i)^>0H@VmWa{5iDjxZqZ?c=#^owQ4xmWzBK00Qv|oIihF?BBmXG^ex+v2t~d z$G0MO4kfLv9XockrNJEh9p8-DrZnEelWz0m+B#1r-q>nv0Rb}!@R%bWc$GjyfuH+d zN-eCDvOQNKSnLGdA}*spV-sPDrpA$M$1e*WIdUZFfsu3@ z`E+7dxIao+3yV#oR@OTD^RMpBj68yG6 zFQ@lVw_^cPQ&W?z7RQ5tF$BD5LAh{t6?M9gbdf*ltV&y3Y%Sf|l@Qb&w5sIW$ch7) zbU;u52pB@(47IeTskT+gn5=N8zWd$penGz<3*Uxy5C8$Q2yjY|NV#CK z6Q@p{;(OrtWjKT~K>!4#6WB;yJin4&OOy(f_h;4HKu-_5(mKDG)V_Wj{(=AqXiwk> zb?fx+*RS6pKP>g7g~g6iXI(j|^sTg7v@69hoOHGyh+5;@K)9xU2KxGG}7}d=)Q-Kmzs(30MiOLcrvn6(}79 z5+%SRPw%1*_g51wz^mhY>(6TFz?LmrZXq@Q18sNp;&j7u@EZg`z*7P>d`H9$3r|Z; zurO@|i|yaKb?XSaUtUV9=#)<|pg0T!JS1?0`>*8R5<0io_glhnQ~USt|1SL=K#KlB z_;##=00<~2u%2Fx8=#X|F#)Xviw)u@iF9i?lvW?1CFYmXXde;?fPnV|YUuiVh{jD0 z8#HLpLGO!Cu0*YF2^+dX?<4l27n>Ilz=DSR@EZg`z;goc(0jQ5b>Ynv&kOgWK&=I< z-a!(sLb^?yMZc9zZ&6=2{(^v}1oj^6G{MPUqpzrzjT<*E`Kd^$K);glg5?)D z)C&S2pq_wsLmbp2K#d>(0;UjvV3`sr*a-q4px+=;!~_JqCQ$kDu#aSn1@PKVl;@5> z%E$lF=S4KR+(AQb5C8!m2|%!XgpaC000i6-fMB`fh1?(j0zMLeVEG6iRf7NsxFZ0; za>om~K>!4NBmlwk5k9I00T6IU0D|R?m$kWP6qmNZSO9CoYo%ilFDt_er$E3?0uU@a z;|i}pz)AuTEGxqbr$E3?0uU@a;|i}pz)AuTEGxqbr$E3?0uU@a;|i}pz)AuTEVp5u zy!tu%Y(~z~!h&IA-5!hlAOHfM5rBAkh7tvV00_t?0Kt+?80CWi2zW*Sg5?=X6a)ew zAe#ULOEzJY4+0?I8372EXDCq+2!McW0uU_Ogi$^SfPiNNZk_-9pGJ3?Fq#W2y#A(w K*M{Hr@c#oiT587t literal 0 HcmV?d00001 diff --git a/manifest-edge.json b/manifest-edge.json index b7f6b445f..23399754a 100644 --- a/manifest-edge.json +++ b/manifest-edge.json @@ -33,8 +33,10 @@ }, "browser_action": { "default_icon": { - "19": "images/icon19.png", - "38": "images/icon38.png" + "20": "images/icon20.png", + "25": "images/icon25.png", + "30": "images/icon30.png", + "40": "images/icon40.png" }, "default_title": "__MSG_extShortName__", "default_popup": "view/popup.html" @@ -43,9 +45,9 @@ "default_locale": "en", "description": "__MSG_extDesc__", "icons": { - "16": "images/icon16.png", "48": "images/icon48.png", - "128": "images/icon128.png" + "128": "images/icon128.png", + "176": "images/icon176.png" }, "manifest_version": 2, "name": "__MSG_extName__", diff --git a/scripts/build.sh b/scripts/build.sh index 7ab72d043..d52f68171 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -30,9 +30,10 @@ mkdir $PLATFORM if [[ $PLATFORM = "edge" ]]; then mkdir $PLATFORM/Extension mkdir $PLATFORM/Assets - cp -r build css images js _locales LICENSE view edge-files $PLATFORM/Extension + cp -r build css js _locales LICENSE view edge-files $PLATFORM/Extension mv $PLATFORM/Extension/edge-files/AppXManifest.xml $PLATFORM - mv $PLATFORM/Extension/edge-files/icon*.png $PLATFORM/Assets + mv $PLATFORM/Extension/edge-files/images $PLATFORM/Extension + mv $PLATFORM/Extension/edge-files/Assets/icon*.png $PLATFORM/Assets cp manifest-$PLATFORM.json $PLATFORM/Extension/manifest.json else cp -r build css images js _locales LICENSE view $PLATFORM From f358eaf0d9c7b1b09eff90b4d1c5e4be2c411f60 Mon Sep 17 00:00:00 2001 From: Brendan Date: Tue, 4 Sep 2018 18:59:01 -0500 Subject: [PATCH 23/53] Translation update Added Greek translations --- _locales/de/messages.json | 6 +- _locales/el/messages.json | 294 ++++++++++++++++++++++++++++++++++++ _locales/it/messages.json | 8 +- _locales/ja/messages.json | 38 ++--- _locales/pl/messages.json | 40 ++--- _locales/ru/messages.json | 4 +- edge-files/AppXManifest.xml | 1 + 7 files changed, 343 insertions(+), 48 deletions(-) create mode 100644 _locales/el/messages.json diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 62fbc35f6..331952bc4 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Exportieren / Importieren", + "message": "Exportieren \/ Importieren", "description": "Export and Import." }, "settings": { @@ -204,8 +204,8 @@ "description": "Import backup code." }, "dropbox_backup": { - "message": "Automatische Sicherung in Dropbox", - "description": "Auto backup to Dropbox." + "message": "Dropbox", + "description": "Auto backup to Dropbox menu" }, "dropbox_code": { "message": "Dropbox-Code", diff --git a/_locales/el/messages.json b/_locales/el/messages.json new file mode 100644 index 000000000..70aaaf5b5 --- /dev/null +++ b/_locales/el/messages.json @@ -0,0 +1,294 @@ +{ + "extName": { + "message": "Authenticator", + "description": "Extension Name." + }, + "extShortName": { + "message": "Authenticator", + "description": "Extension Short Name." + }, + "extDesc": { + "message": "Ο Επαληθευτής δημιουργεί κωδικούς επαλήθευσης σε 2 βήματα στο πρόγραμμα περιήγησης.", + "description": "Extension Description." + }, + "added": { + "message": "έχει προστεθεί.", + "description": "Added Account." + }, + "errorqr": { + "message": "Μη αναγνωρισμένος κώδικας QR.", + "description": "QR Error." + }, + "errorsecret": { + "message": "Μυστικό σφάλμα. Υποστηρίζονται μόνο τα Base32 (A-Z, 2-7 και =) και HEX (0-9 και A-F). Ωστόσο, το μυστικό σας είναι:", + "description": "Secret Error." + }, + "add_qr": { + "message": "Σάρωση QR κωδικού", + "description": "Scan QR Code." + }, + "add_secret": { + "message": "Χειροκίνητη εισαγωγή", + "description": "Manual Entry." + }, + "close": { + "message": "Κλείσιμο", + "description": "Close." + }, + "ok": { + "message": "Εντάξει", + "description": "OK." + }, + "yes": { + "message": "Ναι", + "description": "Yes." + }, + "no": { + "message": "Όχι", + "description": "No." + }, + "account": { + "message": "Λογαριασμός", + "description": "Account." + }, + "accountName": { + "message": "Όνομα Λογαριασμού", + "description": "Account Name." + }, + "issuer": { + "message": "Εκδότης", + "description": "Issuer." + }, + "secret": { + "message": "Μυστικό", + "description": "Secret." + }, + "updateSuccess": { + "message": "Επιτυχία", + "description": "Update Success." + }, + "updateFailure": { + "message": "Αποτυχία", + "description": "Update Failure." + }, + "about": { + "message": "Σχετικά", + "description": "About." + }, + "export_import": { + "message": "Εξαγωγή\/εισαγωγή", + "description": "Export and Import." + }, + "settings": { + "message": "Ρυθμίσεις", + "description": "Settings." + }, + "security": { + "message": "Ασφάλεια", + "description": "Security." + }, + "current_phrase": { + "message": "Τρέχον Κωδικός", + "description": "Current Passphrase." + }, + "new_phrase": { + "message": "Νέος κωδικός πρόσβασης", + "description": "New Passphrase." + }, + "phrase": { + "message": "Κωδικός πρόσβασης", + "description": "Passphrase." + }, + "confirm_phrase": { + "message": "Επιβεβαίωση Κωδικού Πρόσβασης", + "description": "Confirm Passphrase." + }, + "confirm_delete": { + "message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αρχείο; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "description": "Remove entry confirmation" + }, + "security_warning": { + "message": "Αυτός ο κωδικός πρόσβασης θα χρησιμοποιηθεί για να κρυπτογραφήσετε τα μυστικά σας. Κανείς δεν μπορεί να σας βοηθήσει εάν ξεχάσετε τον κωδικό πρόσβασης.", + "description": "Passphrase Warning." + }, + "update": { + "message": "Ενημέρωση", + "description": "Update." + }, + "phrase_incorrect": { + "message": "Δεν μπορείτε να προσθέσετε νέο λογαριασμό ή να εξαγάγετε δεδομένα μέχρι να αποκρυπτογραφηθούν όλοι οι λογαριασμοί. Εισαγάγετε τον σωστό κωδικό πρόσβασης πριν συνεχίσετε.", + "description": "Passphrase Incorrect." + }, + "phrase_not_match": { + "message": "Το Συνθηματικό δεν ταιριάζει", + "description": "Passphrase Not Match." + }, + "encrypted": { + "message": "Κρυπτογραφημένο", + "description": "Encrypted." + }, + "copied": { + "message": "Αντιγράφηκε", + "description": "Copied." + }, + "feedback": { + "message": "Αξιολόγηση", + "description": "Feedback." + }, + "translate": { + "message": "Μετάφραση", + "description": "Translate." + }, + "source": { + "message": "Πηγαίος Κώδικας", + "description": "Source Code." + }, + "passphrase_info": { + "message": "Εισάγετε κωδικό πρόσβασης για να αποκρυπτογραφήσει τα δεδομένα του λογαριασμού.", + "description": "Passphrase Info" + }, + "sync_clock": { + "message": "Συγχρονισμός ρολογιού με την Google", + "description": "Sync Clock" + }, + "remember_phrase": { + "message": "Απομνημόνευση κωδικού πρόσβασης", + "description": "Remember Passphrase" + }, + "clock_too_far_off": { + "message": "ΠΡΟΣΟΧΗ! Τοπικό ρολόι σας είναι πάρα πολύ μακριά, παρακαλώ διορθώστε το πριν να συνεχίσετε.", + "description": "Local Time is Too Far Off" + }, + "remind_backup": { + "message": "Έχετε ένα αντίγραφο ασφαλείας για τα μυστικά σας; Μην περιμένετε έως ότου είναι πολύ αργά!", + "description": "Remind Backup" + }, + "capture_failed": { + "message": "Η καταγραφή απέτυχε, επαναλάβετε τη φόρτωση της σελίδας και προσπαθήστε ξανά.", + "description": "Capture Failed" + }, + "based_on_time": { + "message": "Βάσει χρόνου", + "description": "Time Based" + }, + "based_on_counter": { + "message": "Με μετρητή", + "description": "Counter Based" + }, + "resize_popup_page": { + "message": "Αναδυόμενο σελίδα", + "description": "Popup Page Settings" + }, + "scale": { + "message": "Κλίμακα", + "description": "Scale" + }, + "export_info": { + "message": "Προειδοποίηση: όλα τα αντίγραφα ασφαλείας είναι κρυπτογραφημένη. Θέλετε να προσθέσετε ένα λογαριασμό σε μια άλλη εφαρμογή; Καταδείξτε επάνω δεξιά μέρος οποιουδήποτε λογαριασμού και πάτα το κουμπί κρυφό.", + "description": "Export menu info text" + }, + "download_backup": { + "message": "Λήψη αντιγράφου ασφαλείας", + "description": "Download backup file." + }, + "import_backup": { + "message": "Εισαγωγή αντιγράφου ασφαλείας", + "description": "Import backup." + }, + "import_backup_file": { + "message": "Εισαγωγή αντιγράφου ασφαλείας", + "description": "Import backup file." + }, + "import_backup_code": { + "message": "Εισαγωγή αντιγράφων κειμένου", + "description": "Import backup code." + }, + "dropbox_backup": { + "message": "Dropbox", + "description": "Auto backup to Dropbox menu" + }, + "dropbox_code": { + "message": "Κωδικός Dropbox", + "description": "Dropbox code." + }, + "dropbox_token": { + "message": "Διακριτικό Dropbox", + "description": "Dropbox token." + }, + "dropbox_authorization": { + "message": "Εισέλθετε στο Dropbox", + "description": "Dropbox authorization." + }, + "show_all_entries": { + "message": "Δείτε όλες τις καταχωρήσεις", + "description": "Show all entries." + }, + "dropbox_risk": { + "message": "ΠΡΟΣΟΧΗ: τα αντίγραφα ασφαλείας αποθηκεύονται στο Dropbox είναι κρυπτογραφημένη. Χρησιμοποιήστε το με δική σας ευθύνη.", + "description": "Dropbox backup risk warning." + }, + "import_error_password": { + "message": "Πρέπει να παρέχετε το σωστό κωδικό πρόσβασης για να εισαγάγετε τα αντίγραφα ασφαλείας.", + "description": "Error password warning when import backups." + }, + "local_passphrase_warning": { + "message": "Τον κωδικό πρόσβασής σας είναι αποθηκευμένη τοπικά, παρακαλούμε αμέσως να το αλλάξετε από το μενού ασφάλεια.", + "description": "localStorage password warning." + }, + "dropbox_logout": { + "message": "Αποσύνδεση του Dropbox", + "description": "Dropbox logout." + }, + "remove": { + "message": "Αφαίρεση", + "description": "Remove password." + }, + "download_enc_backup": { + "message": "Λήψη αντιγράφων ασφαλείας που προστατεύεται με κωδικό πρόσβασης", + "description": "Download Encrypted Backup" + }, + "search": { + "message": "Αναζήτηση", + "description": "Search" + }, + "popout": { + "message": "Αναδυόμενο παράθυρο λειτουργίας", + "description": "Make window turn into persistent popup" + }, + "lock": { + "message": "Κλείδωμα", + "description": "Lock accounts" + }, + "dropbox_tooltip": { + "message": "Συγχρονισμός Dropbox ενεργοποιημένη", + "description": "Dropbox sync enabled" + }, + "edit": { + "message": "Επεξεργασία", + "description": "Edit" + }, + "manual_dropbox": { + "message": "Χειροκίνητος συγχρονισμός", + "description": "Manual Dropbox sync" + }, + "use_autofill": { + "message": "Χρήση της αυτόματης συμπλήρωσης", + "description": "Use Autofill" + }, + "storage_menu": { + "message": "Αποθήκευση & δημιουργία αντιγράφων ασφαλείας", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Επιλέξτε όπου είναι αποθηκευμένα τα δεδομένα σας. Χρησιμοποιώντας «τοπικό» αποθηκεύει τα δεδομένα σας στον υπολογιστή σας. Χρησιμοποιώντας «συγχρονισμού» σας επιτρέπει να συγχρονίσετε τα δεδομένα σας στο cloud, αν είστε συνδεδεμένοι σε ένα λογαριασμό συγχρονισμού το πρόγραμμα περιήγησής σας.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Δημιουργήστε αυτόματα αντίγραφα ασφαλείας των δεδομένων σας σε υπηρεσίες αποθήκευσης τρίτων.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Θέση αποθήκευσης", + "description": "Storage location" + } +} \ No newline at end of file diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 1e2fbe060..7546ac26b 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Esporta / Importa", + "message": "Esporta \/ Importa", "description": "Export and Import." }, "settings": { @@ -204,8 +204,8 @@ "description": "Import backup code." }, "dropbox_backup": { - "message": "Backup automatico su Dropbox", - "description": "Auto backup to Dropbox." + "message": "Dropbox", + "description": "Auto backup to Dropbox menu" }, "dropbox_code": { "message": "Codice di Dropbox", @@ -244,7 +244,7 @@ "description": "Remove password." }, "download_enc_backup": { - "message": "Download Password-Protected Backup", + "message": "Scarica Backup protetti da Password", "description": "Download Encrypted Backup" }, "search": { diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 777a9a81f..a6cb682ad 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "エクスポート/インポート", + "message": "エクスポート\/インポート", "description": "Export and Import." }, "settings": { @@ -176,7 +176,7 @@ "description": "Counter Based" }, "resize_popup_page": { - "message": "Popup Page", + "message": "ポップアップ表示", "description": "Popup Page Settings" }, "scale": { @@ -204,8 +204,8 @@ "description": "Import backup code." }, "dropbox_backup": { - "message": "Dropbox へ自動バックアップ", - "description": "Auto backup to Dropbox." + "message": "Dropbox", + "description": "Auto backup to Dropbox menu" }, "dropbox_code": { "message": "Dropboxのコード", @@ -216,7 +216,7 @@ "description": "Dropbox token." }, "dropbox_authorization": { - "message": "Sign in to Dropbox", + "message": "Dropbox にログイン", "description": "Dropbox authorization." }, "show_all_entries": { @@ -236,59 +236,59 @@ "description": "localStorage password warning." }, "dropbox_logout": { - "message": "Logout of Dropbox", + "message": "Dropbox をログアウト", "description": "Dropbox logout." }, "remove": { - "message": "Remove", + "message": "削除", "description": "Remove password." }, "download_enc_backup": { - "message": "Download Password-Protected Backup", + "message": "パスワードで保護されたバックアップをダウンロード", "description": "Download Encrypted Backup" }, "search": { - "message": "Search", + "message": "検索", "description": "Search" }, "popout": { - "message": "Popup mode", + "message": "ポップアップ表示", "description": "Make window turn into persistent popup" }, "lock": { - "message": "Lock", + "message": "ロック", "description": "Lock accounts" }, "dropbox_tooltip": { - "message": "Dropbox sync enabled", + "message": "Dropbox との同期が有効", "description": "Dropbox sync enabled" }, "edit": { - "message": "Edit", + "message": "編集", "description": "Edit" }, "manual_dropbox": { - "message": "Manual Sync", + "message": "手動で同期", "description": "Manual Dropbox sync" }, "use_autofill": { - "message": "Use Autofill", + "message": "自動入力を使用する", "description": "Use Autofill" }, "storage_menu": { - "message": "Storage & Backup", + "message": "ストレージとバックアップ", "description": "Storage and sync menu title" }, "storage_location_info": { - "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "message": "データの保存場所を選択します。 「ローカル」はPCにデータを保存します。「同期」は、そのアカウントにログインいればブラウザーを通じてクラウドとデータを同期することができます。", "description": "Message explaning the diffrences between sync and local storage spaces." }, "storage_sync_info": { - "message": "Automatically backup your data to 3rd party storgae services.", + "message": "サードパーティのストレージサービスへ自動的にデータをバックアップします。", "description": "3rd party backup info" }, "storage_location": { - "message": "Storage Location", + "message": "ストレージの場所", "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index 67803e766..7622dd7e2 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Eksport / Import", + "message": "Eksport \/ Import", "description": "Export and Import." }, "settings": { @@ -172,11 +172,11 @@ "description": "Time Based" }, "based_on_counter": { - "message": "Licznik oparty", + "message": "Oparte na liczniku", "description": "Counter Based" }, "resize_popup_page": { - "message": "Popup Page", + "message": "Wyskakująca strona", "description": "Popup Page Settings" }, "scale": { @@ -204,8 +204,8 @@ "description": "Import backup code." }, "dropbox_backup": { - "message": "Automatyczna kopia zapasowa na Dropbox'ie", - "description": "Auto backup to Dropbox." + "message": "Dropbox", + "description": "Auto backup to Dropbox menu" }, "dropbox_code": { "message": "Kod Dropbox", @@ -216,7 +216,7 @@ "description": "Dropbox token." }, "dropbox_authorization": { - "message": "Zaloguj się do Dropbox'a", + "message": "Zaloguj się do Dropboksa", "description": "Dropbox authorization." }, "show_all_entries": { @@ -224,7 +224,7 @@ "description": "Show all entries." }, "dropbox_risk": { - "message": "Ostrzeżenie: kopie zapasowe zapisane w Dropbox'ie są nieszyfrowane. Używasz na własne ryzyko.", + "message": "Ostrzeżenie: kopie zapasowe zapisane w Dropboksie są nieszyfrowane. Używasz na własne ryzyko.", "description": "Dropbox backup risk warning." }, "import_error_password": { @@ -236,7 +236,7 @@ "description": "localStorage password warning." }, "dropbox_logout": { - "message": "Wyloguj się z Dropbox'a", + "message": "Wyloguj się z Dropboksa", "description": "Dropbox logout." }, "remove": { @@ -244,51 +244,51 @@ "description": "Remove password." }, "download_enc_backup": { - "message": "Pobrania kopii zapasowej hasłem", + "message": "Pobierz kopię zapasową chronioną hasłem", "description": "Download Encrypted Backup" }, "search": { - "message": "Search", + "message": "Szukaj", "description": "Search" }, "popout": { - "message": "Popup mode", + "message": "Tryb wyskakujący", "description": "Make window turn into persistent popup" }, "lock": { - "message": "Lock", + "message": "Zablokuj", "description": "Lock accounts" }, "dropbox_tooltip": { - "message": "Dropbox sync enabled", + "message": "Synchronizacja z Dropboksem włączona", "description": "Dropbox sync enabled" }, "edit": { - "message": "Edit", + "message": "Edytuj", "description": "Edit" }, "manual_dropbox": { - "message": "Manual Sync", + "message": "Ręczna synchronizacja", "description": "Manual Dropbox sync" }, "use_autofill": { - "message": "Use Autofill", + "message": "Użyj autowypełniania", "description": "Use Autofill" }, "storage_menu": { - "message": "Storage & Backup", + "message": "Przechowywanie i kopie zapasowe", "description": "Storage and sync menu title" }, "storage_location_info": { - "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "message": "Wybierz miejsce przechowywania danych. Używanie \"local\" zapisuje twoje dane na twoim komputerze. Korzystanie z opcji \"sync\" pozwala przeglądarce synchronizować dane z chmurą, jeśli jesteś zalogowany na koncie synchronizacji.", "description": "Message explaning the diffrences between sync and local storage spaces." }, "storage_sync_info": { - "message": "Automatically backup your data to 3rd party storgae services.", + "message": "Automatycznie twórz kopie zapasowe danych w usługach przechowywania innych firm.", "description": "3rd party backup info" }, "storage_location": { - "message": "Storage Location", + "message": "Miejsce przechowywania", "description": "Storage location" } } \ No newline at end of file diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 936ca5d5b..510097aff 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -204,8 +204,8 @@ "description": "Import backup code." }, "dropbox_backup": { - "message": "Автосохранение резервной копии в Dropbox", - "description": "Auto backup to Dropbox." + "message": "Dropbox", + "description": "Auto backup to Dropbox menu" }, "dropbox_code": { "message": "Dropbox код", diff --git a/edge-files/AppXManifest.xml b/edge-files/AppXManifest.xml index beede6866..782e8e30a 100644 --- a/edge-files/AppXManifest.xml +++ b/edge-files/AppXManifest.xml @@ -27,6 +27,7 @@ + From 684e74bf09237ee9ba0f60f50da91080572d8c39 Mon Sep 17 00:00:00 2001 From: Brendan Date: Sat, 8 Sep 2018 03:17:28 -0500 Subject: [PATCH 24/53] tiny changes --- README.md | 4 +--- manifest-firefox.json | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 74584e1ba..9bb9a25c0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ - - -# Authenticator [![Build Status](https://travis-ci.org/Authenticator-Extension/Authenticator.svg?branch=dev)](https://travis-ci.org/Authenticator-Extension/Authenticator) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/authenticator-firefox/localized.svg)](https://crowdin.com/project/authenticator-firefox) +# Authenticator [![Build Status](https://travis-ci.org/Authenticator-Extension/Authenticator.svg?branch=dev)](https://travis-ci.org/Authenticator-Extension/Authenticator) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/authenticator-firefox/localized.svg)](https://crowdin.com/project/authenticator-firefox) > Authenticator generates 2-Step Verification codes in your browser. diff --git a/manifest-firefox.json b/manifest-firefox.json index 5c3f500d5..06b1baea4 100644 --- a/manifest-firefox.json +++ b/manifest-firefox.json @@ -17,6 +17,7 @@ "128": "images/icon128.png" }, "browser_action": { + "browser_style": false, "default_icon": { "19": "images/icon19.png", "38": "images/icon38.png" From c2a37f0540eb077de3f721cbf98b3f1aa40d8e06 Mon Sep 17 00:00:00 2001 From: Brendan Date: Sat, 8 Sep 2018 04:54:44 -0500 Subject: [PATCH 25/53] Add option to encrypt dropbox backup --- _locales/en/messages.json | 4 - css/popup.css | 3 +- src/models/{dropbox.ts => backup.ts} | 132 +++++++++++++-------------- src/popup.ts | 2 +- src/ui/menu.ts | 9 +- view/import.html | 2 +- view/popup.html | 11 ++- 7 files changed, 86 insertions(+), 77 deletions(-) rename src/models/{dropbox.ts => backup.ts} (87%) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 2a1ec74b4..b67b70a7f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -207,10 +207,6 @@ "message": "Dropbox", "description": "Auto backup to Dropbox menu" }, - "dropbox_code": { - "message": "Dropbox Code", - "description": "Dropbox code." - }, "dropbox_token": { "message": "Dropbox Token", "description": "Dropbox token." diff --git a/css/popup.css b/css/popup.css index 60cc37764..9765b0bc5 100644 --- a/css/popup.css +++ b/css/popup.css @@ -856,7 +856,6 @@ body { padding: 5px; } -#dropbox_box label, #secret_box label, #security label, #passphrase label, @@ -881,6 +880,8 @@ body { #resize_list_label, #storage_location_label, +#dropbox_encryption_label, +#dropbox_encryption, #use_autofill_label, #resize_list, #storage_location { diff --git a/src/models/dropbox.ts b/src/models/backup.ts similarity index 87% rename from src/models/dropbox.ts rename to src/models/backup.ts index c240106d9..a323371fb 100644 --- a/src/models/dropbox.ts +++ b/src/models/backup.ts @@ -1,66 +1,66 @@ -/* tslint:disable:no-reference */ -/// -/// -/// - -class Dropbox { - async getToken() { - return localStorage.dropboxToken || ''; - } - - async upload(encryption: Encryption) { - const exportData = await EntryStorage.getExport(encryption); - for (const hash of Object.keys(exportData)) { - if (exportData[hash].encrypted) { - throw new Error('Error passphrase.'); - } - } - const backup = JSON.stringify(exportData, null, 2); - - const url = 'https://content.dropboxapi.com/2/files/upload'; - const token = await this.getToken(); - return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - if (!token) { - resolve(false); - } - try { - const xhr = new XMLHttpRequest(); - const now = - (new Date()).toISOString().slice(0, 10).replace(/-/g, ''); - const apiArg = { - path: `/${now}.json`, - mode: 'add', - autorename: true - }; - xhr.open('POST', url); - xhr.setRequestHeader('Authorization', 'Bearer ' + token); - xhr.setRequestHeader('Content-type', 'application/octet-stream'); - xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify(apiArg)); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('dropboxToken'); - resolve(false); - } - try { - const res = JSON.parse(xhr.responseText); - if (res.name) { - resolve(true); - } else { - resolve(false); - } - } catch (error) { - reject(error); - } - } - return; - }; - xhr.send(backup); - } catch (error) { - return reject(error); - } - }); - } -} +/* tslint:disable:no-reference */ +/// +/// +/// + +class Dropbox { + async getToken() { + return localStorage.dropboxToken || ''; + } + + async upload(encryption: Encryption) { + if (localStorage.dropboxEncrypted === undefined) { + // Encrypt by default if user hasn't set yet + localStorage.dropboxEncrypted = 'true'; + } + const exportData = await EntryStorage.getExport( + encryption, (localStorage.dropboxEncrypted === 'true') ? true : false); + const backup = JSON.stringify(exportData, null, 2); + + const url = 'https://content.dropboxapi.com/2/files/upload'; + const token = await this.getToken(); + return new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + if (!token) { + resolve(false); + } + try { + const xhr = new XMLHttpRequest(); + const now = + (new Date()).toISOString().slice(0, 10).replace(/-/g, ''); + const apiArg = { + path: `/${now}.json`, + mode: 'add', + autorename: true + }; + xhr.open('POST', url); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Content-type', 'application/octet-stream'); + xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify(apiArg)); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('dropboxToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.name) { + resolve(true); + } else { + resolve(false); + } + } catch (error) { + reject(error); + } + } + return; + }; + xhr.send(backup); + } catch (error) { + return reject(error); + } + }); + } +} diff --git a/src/popup.ts b/src/popup.ts index e80ccd6da..3b11a0dd1 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -9,7 +9,7 @@ /// /// /// -/// +/// async function init() { const ui = new UI({el: '#authenticator'}); diff --git a/src/ui/menu.ts b/src/ui/menu.ts index a43186c82..2cea60ebd 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -1,6 +1,6 @@ /* tslint:disable:no-reference */ /// -/// +/// /// function getVersion() { @@ -79,7 +79,8 @@ async function menu(_ui: UI) { version, zoom, useAutofill, - newStorageLocation: localStorage.storageLocation + newStorageLocation: localStorage.storageLocation, + dropboxEncrypted: localStorage.dropboxEncrypted }, methods: { openLink: (url: string) => { @@ -198,6 +199,10 @@ async function menu(_ui: UI) { _ui.instance.alert(_ui.instance.i18n.updateFailure); } }, + dropboxUpdateEncryption: () => { + localStorage.dropboxEncrypted = _ui.instance.dropboxEncrypted; + return; + }, migrateStorage: async () => { // sync => local if (localStorage.storageLocation === 'sync' && diff --git a/view/import.html b/view/import.html index e359738bd..0c1937334 100644 --- a/view/import.html +++ b/view/import.html @@ -16,7 +16,7 @@ - +
diff --git a/view/popup.html b/view/popup.html index 6aef4b9a8..949c8af49 100644 --- a/view/popup.html +++ b/view/popup.html @@ -15,7 +15,7 @@ - +
@@ -167,7 +167,14 @@
-
{{ i18n.dropbox_risk }}
+
{{ i18n.dropbox_risk }}
+
+ + +
{{ i18n.dropbox_logout }}
{{ i18n.dropbox_authorization }}
{{ i18n.manual_dropbox }}
From 52e11dd3fa6d3ac2b95e748e485a75f6d50f5a1b Mon Sep 17 00:00:00 2001 From: Brendan Date: Sat, 8 Sep 2018 09:46:50 -0500 Subject: [PATCH 26/53] Initial Drive support --- manifest-firefox.json | 2 +- src/background.ts | 44 +++++++++++++++++++++++++++ src/models/backup.ts | 69 +++++++++++++++++++++++++++++++++++++++++++ src/popup.ts | 6 ++++ src/ui/info.ts | 9 +++++- src/ui/menu.ts | 9 ++++++ 6 files changed, 137 insertions(+), 2 deletions(-) diff --git a/manifest-firefox.json b/manifest-firefox.json index 06b1baea4..7843d04d6 100644 --- a/manifest-firefox.json +++ b/manifest-firefox.json @@ -71,5 +71,5 @@ "view/qr.html", "images/scan.gif" ], - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com; default-src 'none'" + "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/upload/drive/v3/files; default-src 'none'" } diff --git a/src/background.ts b/src/background.ts index c1116a4d1..49686b895 100644 --- a/src/background.ts +++ b/src/background.ts @@ -20,6 +20,8 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { sendResponse(cachedPassphrase); } else if (message.action === 'dropbox') { getDropboxToken(); + } else if (message.action === 'drive') { + getDriveToken(); } else if (message.action === 'lock') { cachedPassphrase = ''; } @@ -194,6 +196,48 @@ function getDropboxToken() { }); } +function getDriveToken() { + chrome.identity.launchWebAuthFlow( + { + url: + 'https://accounts.google.com/o/oauth2/v2/auth?response_type=token&client_id=292457304165-ria4acohb2i875o1kmda5a31vkan7rj7.apps.googleusercontent.com&scope=https%3A//www.googleapis.com/auth/drive&prompt=consent&redirect_uri=' + + // encodeURIComponent(chrome.identity.getRedirectURL()), + // encodeURIComponent('https://chrome.authenticatortest.tk'), + encodeURIComponent('https://firefox.authenticatortest.tk'), + interactive: true + }, + (url) => { + if (!url) { + return; + } + const hashMatches = url.split('#'); + if (hashMatches.length < 2) { + return; + } + + const hash = hashMatches[1]; + + const resData = hash.split('&'); + for (let i = 0; i < resData.length; i++) { + const kv = resData[i]; + if (/^(.*?)=(.*?)$/.test(kv)) { + const kvMatches = kv.match(/^(.*?)=(.*?)$/); + if (!kvMatches) { + continue; + } + const key = kvMatches[1]; + const value = kvMatches[2]; + if (key === 'access_token') { + localStorage.driveToken = value; + chrome.runtime.sendMessage({action: 'drivetoken', value}); + return; + } + } + } + return; + }); +} + // Show issue page after first install chrome.runtime.onInstalled.addListener((details) => { if (details.reason !== 'install') { diff --git a/src/models/backup.ts b/src/models/backup.ts index a323371fb..7c07f2d69 100644 --- a/src/models/backup.ts +++ b/src/models/backup.ts @@ -64,3 +64,72 @@ class Dropbox { }); } } + +class Drive { + async getToken() { + return localStorage.driveToken || ''; + } + + async upload(encryption: Encryption) { + if (localStorage.driveEncrypted === undefined) { + localStorage.driveEncrypted = 'true'; + } + const exportData = await EntryStorage.getExport( + encryption, (localStorage.driveEncrypted === 'true') ? true : false); + const backup = JSON.stringify(exportData, null, 2); + + const token = await this.getToken(); + return new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + if (!token) { + resolve(false); + } + try { + const xhr = new XMLHttpRequest(); + const now = + (new Date()).toISOString().slice(0, 10).replace(/-/g, ''); + xhr.open( + 'POST', + 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart'); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader( + 'Content-type', 'multipart/related; boundary=segment_marker'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (!res.error) { + resolve(true); + } else { + console.error(res.error.message); + resolve(false); + } + } catch (error) { + reject(error); + } + } + return; + }; + const requestDataPrototype = [ + '--segment_marker', + 'Content-Type: application/json; charset=UTF-8', '', + JSON.stringify({name: `${now}.json`, mimeType: 'application/json'}), '', '--segment_marker', + 'Content-Type: application/octet-stream', '', backup, + '--segment_marker--' + ]; + let requestData = ''; + requestDataPrototype.forEach((line) => { + requestData = requestData + line + '\n'; + }); + xhr.send(requestData); + } catch (error) { + return reject(error); + } + }); + } +} \ No newline at end of file diff --git a/src/popup.ts b/src/popup.ts index 3b11a0dd1..5b86d9c35 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -106,6 +106,12 @@ async function init() { if (authenticator.info === 'dropbox') { setTimeout(authenticator.closeInfo, 500); } + } else if (message.action === 'drivetoken') { + authenticator.driveToken = message.value; + authenticator.driveUpload(); + if (authenticator.info === 'drive') { + setTimeout(authenticator.closeInfo, 500); + } } }); diff --git a/src/ui/info.ts b/src/ui/info.ts index 616b74f09..c98f5398d 100644 --- a/src/ui/info.ts +++ b/src/ui/info.ts @@ -4,7 +4,11 @@ async function info(_ui: UI) { const ui: UIConfig = { - data: {info: '', dropboxToken: localStorage.dropboxToken || ''}, + data: { + info: '', + dropboxToken: localStorage.dropboxToken || '', + driveToken: localStorage.driveToken || '' + }, methods: { getDropboxToken: () => { chrome.runtime.sendMessage({action: 'dropbox'}); @@ -14,6 +18,9 @@ async function info(_ui: UI) { _ui.instance.dropboxToken = ''; _ui.instance.openLink('https://www.dropbox.com/account/connected_apps'); }, + getDriveToken: () => { + chrome.runtime.sendMessage({action: 'drive'}); + }, showInfo: (tab: string) => { if (tab === 'export' || tab === 'security') { const entries = _ui.instance.entries as OTPEntry[]; diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 2cea60ebd..d527a55f5 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -199,6 +199,15 @@ async function menu(_ui: UI) { _ui.instance.alert(_ui.instance.i18n.updateFailure); } }, + driveUpload: async () => { + const drive = new Drive(); + const response = await drive.upload(_ui.instance.encryption); + if (response === true) { + _ui.instance.alert(_ui.instance.i18n.updateSuccess); + } else { + _ui.instance.alert(_ui.instance.i18n.updateFailure); + } + }, dropboxUpdateEncryption: () => { localStorage.dropboxEncrypted = _ui.instance.dropboxEncrypted; return; From 5bf0e9d99d11056a6837499cdf6e9244c445e811 Mon Sep 17 00:00:00 2001 From: Brendan Date: Sat, 8 Sep 2018 09:52:53 -0500 Subject: [PATCH 27/53] reduce permission scope --- src/background.ts | 2 +- src/models/backup.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/background.ts b/src/background.ts index 49686b895..157f43012 100644 --- a/src/background.ts +++ b/src/background.ts @@ -200,7 +200,7 @@ function getDriveToken() { chrome.identity.launchWebAuthFlow( { url: - 'https://accounts.google.com/o/oauth2/v2/auth?response_type=token&client_id=292457304165-ria4acohb2i875o1kmda5a31vkan7rj7.apps.googleusercontent.com&scope=https%3A//www.googleapis.com/auth/drive&prompt=consent&redirect_uri=' + + 'https://accounts.google.com/o/oauth2/v2/auth?response_type=token&client_id=292457304165-ria4acohb2i875o1kmda5a31vkan7rj7.apps.googleusercontent.com&scope=https%3A//www.googleapis.com/auth/drive.file&prompt=consent&redirect_uri=' + // encodeURIComponent(chrome.identity.getRedirectURL()), // encodeURIComponent('https://chrome.authenticatortest.tk'), encodeURIComponent('https://firefox.authenticatortest.tk'), diff --git a/src/models/backup.ts b/src/models/backup.ts index 7c07f2d69..d2359d6ca 100644 --- a/src/models/backup.ts +++ b/src/models/backup.ts @@ -118,9 +118,10 @@ class Drive { const requestDataPrototype = [ '--segment_marker', 'Content-Type: application/json; charset=UTF-8', '', - JSON.stringify({name: `${now}.json`, mimeType: 'application/json'}), '', '--segment_marker', - 'Content-Type: application/octet-stream', '', backup, - '--segment_marker--' + JSON.stringify( + {name: `${now}.json`}), + '', '--segment_marker', 'Content-Type: application/octet-stream', + '', backup, '--segment_marker--' ]; let requestData = ''; requestDataPrototype.forEach((line) => { From 7f03906074795e60e20c51f890b2a35173fe24de Mon Sep 17 00:00:00 2001 From: Brendan Date: Sat, 8 Sep 2018 12:11:18 -0500 Subject: [PATCH 28/53] Hook the UI to the Drive class Make the strings more generic for future integrations Permissions request to api for chrome --- _locales/en/messages.json | 30 +++++++++++------------------- manifest-chrome.json | 5 +++-- manifest-edge.json | 2 +- manifest-firefox.json | 3 ++- src/background.ts | 6 ++++-- src/models/backup.ts | 7 +++---- src/popup.ts | 19 +++++++++++++++++++ src/ui/info.ts | 17 +++++++++++++++++ src/ui/menu.ts | 6 +++++- view/popup.html | 29 +++++++++++++++++++++-------- 10 files changed, 86 insertions(+), 38 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b67b70a7f..3e83dab4c 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -203,25 +203,13 @@ "message": "Import Text Backup", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Dropbox", - "description": "Auto backup to Dropbox menu" - }, - "dropbox_token": { - "message": "Dropbox Token", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Sign in to Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Show all entries", "description": "Show all entries." }, "dropbox_risk": { - "message": "Warning: backups saved in Dropbox are unencrypted. Use at your own risk.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "You must provide correct password to import backups.", @@ -231,10 +219,6 @@ "message": "Your password is stored locally, please change it in the security menu immediately.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Logout of Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "Remove", "description": "Remove password." @@ -265,7 +249,7 @@ }, "manual_dropbox": { "message": "Manual Sync", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Use Autofill", @@ -286,5 +270,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } diff --git a/manifest-chrome.json b/manifest-chrome.json index 4a84374bc..d88f6c870 100644 --- a/manifest-chrome.json +++ b/manifest-chrome.json @@ -58,12 +58,13 @@ "optional_permissions": [ "clipboardWrite", "https://www.google.com/", - "https://*.dropboxapi.com/*" + "https://*.dropboxapi.com/*", + "https://www.googleapis.com/upload/drive/v3/files" ], "offline_enabled": true, "web_accessible_resources": [ "view/qr.html", "images/scan.gif" ], - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com; default-src 'none'" + "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/upload/drive/v3/files; default-src 'none'" } diff --git a/manifest-edge.json b/manifest-edge.json index 23399754a..9b425ee40 100644 --- a/manifest-edge.json +++ b/manifest-edge.json @@ -41,7 +41,7 @@ "default_title": "__MSG_extShortName__", "default_popup": "view/popup.html" }, - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com; default-src 'none'", + "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/; default-src 'none'", "default_locale": "en", "description": "__MSG_extDesc__", "icons": { diff --git a/manifest-firefox.json b/manifest-firefox.json index 7843d04d6..607ed38ba 100644 --- a/manifest-firefox.json +++ b/manifest-firefox.json @@ -65,7 +65,8 @@ "optional_permissions": [ "clipboardWrite", "https://www.google.com/", - "https://*.dropboxapi.com/*" + "https://*.dropboxapi.com/*", + "https://www.googleapis.com/upload/drive/v3/files" ], "web_accessible_resources": [ "view/qr.html", diff --git a/src/background.ts b/src/background.ts index 157f43012..d85ad9c59 100644 --- a/src/background.ts +++ b/src/background.ts @@ -197,13 +197,15 @@ function getDropboxToken() { } function getDriveToken() { + const redirURL = (navigator.userAgent.indexOf('Chrome') !== -1) ? + 'https://chrome.authenticatortest.tk' : + 'https://firefox.authenticatortest.tk'; chrome.identity.launchWebAuthFlow( { url: 'https://accounts.google.com/o/oauth2/v2/auth?response_type=token&client_id=292457304165-ria4acohb2i875o1kmda5a31vkan7rj7.apps.googleusercontent.com&scope=https%3A//www.googleapis.com/auth/drive.file&prompt=consent&redirect_uri=' + // encodeURIComponent(chrome.identity.getRedirectURL()), - // encodeURIComponent('https://chrome.authenticatortest.tk'), - encodeURIComponent('https://firefox.authenticatortest.tk'), + encodeURIComponent(redirURL), interactive: true }, (url) => { diff --git a/src/models/backup.ts b/src/models/backup.ts index d2359d6ca..ce8b9081e 100644 --- a/src/models/backup.ts +++ b/src/models/backup.ts @@ -118,10 +118,9 @@ class Drive { const requestDataPrototype = [ '--segment_marker', 'Content-Type: application/json; charset=UTF-8', '', - JSON.stringify( - {name: `${now}.json`}), - '', '--segment_marker', 'Content-Type: application/octet-stream', - '', backup, '--segment_marker--' + JSON.stringify({name: `${now}.json`}), '', '--segment_marker', + 'Content-Type: application/octet-stream', '', backup, + '--segment_marker--' ]; let requestData = ''; requestDataPrototype.forEach((line) => { diff --git a/src/popup.ts b/src/popup.ts index 5b86d9c35..7befbc0ba 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -82,6 +82,25 @@ async function init() { authenticator.alert(authenticator.i18n.remind_backup); localStorage.lastRemindingBackupTime = clientTime; }); + } else if (authenticator.driveToken) { + chrome.permissions.contains( + {origins: ['https://www.googleapis.com/upload/drive/v3/files']}, + async (hasPermission) => { + if (hasPermission) { + try { + const drive = new Drive(); + const res = await drive.upload(authenticator.encryption); + if (res) { + localStorage.lastRemindingBackupTime = clientTime; + return; + } + } catch (error) { + // ignore + } + } + authenticator.alert(authenticator.i18n.remind_backup); + localStorage.lastRemindingBackupTime = clientTime; + }); } else { authenticator.alert(authenticator.i18n.remind_backup); localStorage.lastRemindingBackupTime = clientTime; diff --git a/src/ui/info.ts b/src/ui/info.ts index c98f5398d..ac5d19435 100644 --- a/src/ui/info.ts +++ b/src/ui/info.ts @@ -21,6 +21,11 @@ async function info(_ui: UI) { getDriveToken: () => { chrome.runtime.sendMessage({action: 'drive'}); }, + logoutDrive: async () => { + localStorage.removeItem('driveToken'); + _ui.instance.driveToken = ''; + _ui.instance.openLink('https://myaccount.google.com/permissions'); + }, showInfo: (tab: string) => { if (tab === 'export' || tab === 'security') { const entries = _ui.instance.entries as OTPEntry[]; @@ -45,6 +50,18 @@ async function info(_ui: UI) { return; }); return; + } else if (tab === 'drive') { + chrome.permissions.request( + {origins: ['https://www.googleapis.com/upload/drive/v3/files']}, + async (granted) => { + if (granted) { + _ui.instance.class.fadein = true; + _ui.instance.class.fadeout = false; + _ui.instance.info = tab; + } + return; + }); + return; } else if (tab === 'storage') { if (_ui.instance.newStorageLocation !== 'sync' && _ui.instance.newStorageLocation !== 'local') { diff --git a/src/ui/menu.ts b/src/ui/menu.ts index d527a55f5..029e69d0b 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -80,7 +80,8 @@ async function menu(_ui: UI) { zoom, useAutofill, newStorageLocation: localStorage.storageLocation, - dropboxEncrypted: localStorage.dropboxEncrypted + dropboxEncrypted: localStorage.dropboxEncrypted, + driveEncrypted: localStorage.driveEncrypted }, methods: { openLink: (url: string) => { @@ -212,6 +213,9 @@ async function menu(_ui: UI) { localStorage.dropboxEncrypted = _ui.instance.dropboxEncrypted; return; }, + driveUpdateEncryption: () => { + localStorage.driveEncrypted = _ui.instance.driveEncrypted; + }, migrateStorage: async () => { // sync => local if (localStorage.storageLocation === 'sync' && diff --git a/view/popup.html b/view/popup.html index 949c8af49..eb0794f79 100644 --- a/view/popup.html +++ b/view/popup.html @@ -167,7 +167,7 @@
-
{{ i18n.dropbox_risk }}
+
{{ i18n.dropbox_risk }}
-
{{ i18n.dropbox_logout }}
-
{{ i18n.dropbox_authorization }}
+
{{ i18n.log_out }}
+
{{ i18n.sign_in }}
{{ i18n.manual_dropbox }}
- +
+
+ +
+
+
{{ i18n.dropbox_risk }}
+
+ + +
+
{{ i18n.log_out }}
+
{{ i18n.sign_in }}
+
{{ i18n.manual_dropbox }}
@@ -196,7 +208,8 @@
{{ i18n.storage_sync_info }}

-
{{ i18n.dropbox_backup }}
+
Google Drive
+
Dropbox
From 6933fbaf7124cf22092f1e244d221f81ea77b83a Mon Sep 17 00:00:00 2001 From: Travis CI Date: Sat, 8 Sep 2018 17:12:36 +0000 Subject: [PATCH 29/53] Add new strings This commit was automatically made by TravisCI build 580.5 --- _locales/cs/messages.json | 8 ++++++++ _locales/de/messages.json | 10 +++++++++- _locales/el/messages.json | 10 +++++++++- _locales/es/messages.json | 8 ++++++++ _locales/fr/messages.json | 8 ++++++++ _locales/id/messages.json | 8 ++++++++ _locales/it/messages.json | 10 +++++++++- _locales/ja/messages.json | 10 +++++++++- _locales/pl/messages.json | 10 +++++++++- _locales/ru/messages.json | 8 ++++++++ _locales/sv/messages.json | 8 ++++++++ _locales/tr/messages.json | 8 ++++++++ _locales/vi/messages.json | 8 ++++++++ _locales/zh_CN/messages.json | 8 ++++++++ _locales/zh_TW/messages.json | 8 ++++++++ 15 files changed, 125 insertions(+), 5 deletions(-) diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json index a19ae0453..d8dae1ef3 100644 --- a/_locales/cs/messages.json +++ b/_locales/cs/messages.json @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 331952bc4..42513efeb 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Exportieren \/ Importieren", + "message": "Exportieren / Importieren", "description": "Export and Import." }, "settings": { @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/el/messages.json b/_locales/el/messages.json index 70aaaf5b5..cdb4a5614 100644 --- a/_locales/el/messages.json +++ b/_locales/el/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Εξαγωγή\/εισαγωγή", + "message": "Εξαγωγή/εισαγωγή", "description": "Export and Import." }, "settings": { @@ -290,5 +290,13 @@ "storage_location": { "message": "Θέση αποθήκευσης", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/es/messages.json b/_locales/es/messages.json index a8e6833db..30964c071 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 026880c17..79dea15b0 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/id/messages.json b/_locales/id/messages.json index 193a55364..38159f49e 100644 --- a/_locales/id/messages.json +++ b/_locales/id/messages.json @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 7546ac26b..1f14a3d59 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Esporta \/ Importa", + "message": "Esporta / Importa", "description": "Export and Import." }, "settings": { @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index a6cb682ad..0e2733bea 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "エクスポート\/インポート", + "message": "エクスポート/インポート", "description": "Export and Import." }, "settings": { @@ -290,5 +290,13 @@ "storage_location": { "message": "ストレージの場所", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index 7622dd7e2..25c47367f 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -76,7 +76,7 @@ "description": "About." }, "export_import": { - "message": "Eksport \/ Import", + "message": "Eksport / Import", "description": "Export and Import." }, "settings": { @@ -290,5 +290,13 @@ "storage_location": { "message": "Miejsce przechowywania", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 510097aff..1818dc3a5 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index c86a2163a..67e8acbc0 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/tr/messages.json b/_locales/tr/messages.json index 38e200b12..d10dec819 100644 --- a/_locales/tr/messages.json +++ b/_locales/tr/messages.json @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/vi/messages.json b/_locales/vi/messages.json index 538787a5e..ffaadd18b 100644 --- a/_locales/vi/messages.json +++ b/_locales/vi/messages.json @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index a7a8ba55d..0e993d361 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -290,5 +290,13 @@ "storage_location": { "message": "存储位置", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index 982f02eaf..853ff2450 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -290,5 +290,13 @@ "storage_location": { "message": "Storage Location", "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Sign out", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file From 40bac1adea18765554910757d9098a940b0fe07c Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Wed, 12 Sep 2018 21:34:21 -0500 Subject: [PATCH 30/53] Dropbox UI bugfix --- src/ui/info.ts | 6 ++++++ view/popup.html | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ui/info.ts b/src/ui/info.ts index 616b74f09..6c436bc4c 100644 --- a/src/ui/info.ts +++ b/src/ui/info.ts @@ -28,6 +28,12 @@ async function info(_ui: UI) { } } } else if (tab === 'dropbox') { + if (localStorage.dropboxEncrypted !== 'true' && + localStorage.dropboxEncrypted !== 'false') { + localStorage.dropboxEncrypted = 'true'; + _ui.instance.dropboxEncrypted = localStorage.dropboxEncrypted; + } + chrome.permissions.request( {origins: ['https://*.dropboxapi.com/*']}, async (granted) => { if (granted) { diff --git a/view/popup.html b/view/popup.html index 949c8af49..df28dc485 100644 --- a/view/popup.html +++ b/view/popup.html @@ -167,7 +167,7 @@
-
{{ i18n.dropbox_risk }}
+
{{ i18n.dropbox_risk }}
-
{{ i18n.log_out }}
-
{{ i18n.sign_in }}
+
{{ i18n.log_out }}
+
{{ i18n.sign_in }}
{{ i18n.manual_dropbox }}
@@ -191,8 +191,8 @@
-
{{ i18n.log_out }}
-
{{ i18n.sign_in }}
+
{{ i18n.log_out }}
+
{{ i18n.sign_in }}
{{ i18n.manual_dropbox }}
From ab5928f90a548d9de62a9ceb4e4c8dc24ae23608 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 13 Sep 2018 19:58:24 -0500 Subject: [PATCH 32/53] Move backup menu functions to it's own file Last of the refactoring --- src/background.ts | 81 +++++++++++++---------------------------------- src/popup.ts | 22 +++++++------ src/ui/backup.ts | 56 ++++++++++++++++++++++++++++++++ src/ui/menu.ts | 43 +------------------------ view/popup.html | 9 +++--- 5 files changed, 96 insertions(+), 115 deletions(-) create mode 100644 src/ui/backup.ts diff --git a/src/background.ts b/src/background.ts index d85ad9c59..70cdc9309 100644 --- a/src/background.ts +++ b/src/background.ts @@ -18,10 +18,8 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { cachedPassphrase = message.value; } else if (message.action === 'passphrase') { sendResponse(cachedPassphrase); - } else if (message.action === 'dropbox') { - getDropboxToken(); - } else if (message.action === 'drive') { - getDriveToken(); + } else if (['dropbox', 'drive'].indexOf(message.action) > -1) { + getBackupToken(message.action); } else if (message.action === 'lock') { cachedPassphrase = ''; } @@ -156,59 +154,19 @@ async function getTotp(text: string, passphrase: string) { return; } -function getDropboxToken() { - chrome.identity.launchWebAuthFlow( - { - url: - 'https://www.dropbox.com/oauth2/authorize?response_type=token&client_id=mmx38seexw3tvps&redirect_uri=' + - encodeURIComponent(chrome.identity.getRedirectURL()), - interactive: true - }, - (url) => { - if (!url) { - return; - } - const hashMatches = url.split('#'); - if (hashMatches.length < 2) { - return; - } - - const hash = hashMatches[1]; - - const resData = hash.split('&'); - for (let i = 0; i < resData.length; i++) { - const kv = resData[i]; - if (/^(.*?)=(.*?)$/.test(kv)) { - const kvMatches = kv.match(/^(.*?)=(.*?)$/); - if (!kvMatches) { - continue; - } - const key = kvMatches[1]; - const value = kvMatches[2]; - if (key === 'access_token') { - localStorage.dropboxToken = value; - chrome.runtime.sendMessage({action: 'dropboxtoken', value}); - return; - } - } - } - return; - }); -} - -function getDriveToken() { - const redirURL = (navigator.userAgent.indexOf('Chrome') !== -1) ? - 'https://chrome.authenticatortest.tk' : - 'https://firefox.authenticatortest.tk'; +function getBackupToken(service: string) { + let authUrl = ''; + if (service === 'dropbox') { + authUrl = + 'https://www.dropbox.com/oauth2/authorize?response_type=token&client_id=mmx38seexw3tvps&redirect_uri=' + + encodeURIComponent(chrome.identity.getRedirectURL()); + } else if (service === 'drive') { + authUrl = + 'https://accounts.google.com/o/oauth2/v2/auth?response_type=token&client_id=292457304165-ria4acohb2i875o1kmda5a31vkan7rj7.apps.googleusercontent.com&scope=https%3A//www.googleapis.com/auth/drive.file&prompt=consent&redirect_uri=' + + encodeURIComponent('https://authenticatortest.tk'); + } chrome.identity.launchWebAuthFlow( - { - url: - 'https://accounts.google.com/o/oauth2/v2/auth?response_type=token&client_id=292457304165-ria4acohb2i875o1kmda5a31vkan7rj7.apps.googleusercontent.com&scope=https%3A//www.googleapis.com/auth/drive.file&prompt=consent&redirect_uri=' + - // encodeURIComponent(chrome.identity.getRedirectURL()), - encodeURIComponent(redirURL), - interactive: true - }, - (url) => { + {url: authUrl, interactive: true}, (url) => { if (!url) { return; } @@ -230,9 +188,14 @@ function getDriveToken() { const key = kvMatches[1]; const value = kvMatches[2]; if (key === 'access_token') { - localStorage.driveToken = value; - chrome.runtime.sendMessage({action: 'drivetoken', value}); - return; + if (service === 'dropbox') { + localStorage.dropboxToken = value; + chrome.runtime.sendMessage({action: 'dropboxtoken', value}); + return; + } else if (service === 'drive') { + localStorage.driveToken = value; + chrome.runtime.sendMessage({action: 'drivetoken', value}); + } } } } diff --git a/src/popup.ts b/src/popup.ts index 7befbc0ba..570ed8173 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -7,6 +7,7 @@ /// /// /// +/// /// /// /// @@ -23,6 +24,7 @@ async function init() { .load(qr) .load(message) .load(addAccount) + .load(backup) .render(); try { @@ -60,7 +62,7 @@ async function init() { } else if ( clientTime - localStorage.lastRemindingBackupTime >= 30 || clientTime - localStorage.lastRemindingBackupTime < 0) { - // backup to Dropbox + // backup to cloud if (authenticator.dropboxToken) { chrome.permissions.contains( {origins: ['https://*.dropboxapi.com/*']}, @@ -119,16 +121,16 @@ async function init() { } chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message.action === 'dropboxtoken') { - authenticator.dropboxToken = message.value; - authenticator.dropboxUpload(); - if (authenticator.info === 'dropbox') { - setTimeout(authenticator.closeInfo, 500); + if (['dropboxtoken', 'drivetoken'].indexOf(message.action) > -1) { + if (message.action === 'dropboxtoken') { + authenticator.dropboxToken = message.value; + } else if (message.account === 'drivetoken') { + authenticator.driveToken = message.value; } - } else if (message.action === 'drivetoken') { - authenticator.driveToken = message.value; - authenticator.driveUpload(); - if (authenticator.info === 'drive') { + authenticator.backupUpload( + String(message.action) + .substring(0, String(message.action).indexOf('token'))); + if (['dropbox', 'drive'].indexOf(authenticator.info) > -1) { setTimeout(authenticator.closeInfo, 500); } } diff --git a/src/ui/backup.ts b/src/ui/backup.ts new file mode 100644 index 000000000..dba8a60f3 --- /dev/null +++ b/src/ui/backup.ts @@ -0,0 +1,56 @@ +/* tslint:disable:no-reference */ +/// +/// + +async function backup(_ui: UI) { + const ui: UIConfig = { + data: { + dropboxEncrypted: localStorage.dropboxEncrypted, + driveEncrypted: localStorage.driveEncrypted, + dropboxToken: localStorage.dropboxToken || '', + driveToken: localStorage.driveToken || '' + }, + methods: { + backupUpload: async (service: string) => { + if (service === 'dropbox') { + const dbox = new Dropbox(); + const response = await dbox.upload(_ui.instance.encryption); + if (response === true) { + _ui.instance.alert(_ui.instance.i18n.updateSuccess); + } else { + _ui.instance.alert(_ui.instance.i18n.updateFailure); + } + } else if (service === 'drive') { + const drive = new Drive(); + const response = await drive.upload(_ui.instance.encryption); + if (response === true) { + _ui.instance.alert(_ui.instance.i18n.updateSuccess); + } else { + _ui.instance.alert(_ui.instance.i18n.updateFailure); + } + } + }, + backupUpdateEncryption: (service: string) => { + if (service === 'dropbox') { + localStorage.dropboxEncrypted = _ui.instance.dropboxEncrypted; + } else if (service === 'drive') { + localStorage.driveEncrypted = _ui.instance.driveEncrypted; + } + }, + backupLogout: async (service: string) => { + localStorage.removeItem(service + 'Token'); + if (service === 'dropbox') { + _ui.instance.dropboxToken = ''; + } else if (service === 'drive') { + _ui.instance.driveToken = ''; + } + setTimeout(_ui.instance.closeInfo, 500); + }, + getBackupToken: (service: string) => { + chrome.runtime.sendMessage({action: service}); + }, + } + }; + + _ui.update(ui); +} diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 6ffbe6920..e2746e9a3 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -79,11 +79,7 @@ async function menu(_ui: UI) { version, zoom, useAutofill, - newStorageLocation: localStorage.storageLocation, - dropboxEncrypted: localStorage.dropboxEncrypted, - driveEncrypted: localStorage.driveEncrypted, - dropboxToken: localStorage.dropboxToken || '', - driveToken: localStorage.driveToken || '' + newStorageLocation: localStorage.storageLocation }, methods: { openLink: (url: string) => { @@ -193,43 +189,6 @@ async function menu(_ui: UI) { {height: adjustedHeight, width: adjustedWidth}); } }, - dropboxUpload: async () => { - const dbox = new Dropbox(); - const response = await dbox.upload(_ui.instance.encryption); - if (response === true) { - _ui.instance.alert(_ui.instance.i18n.updateSuccess); - } else { - _ui.instance.alert(_ui.instance.i18n.updateFailure); - } - }, - driveUpload: async () => { - const drive = new Drive(); - const response = await drive.upload(_ui.instance.encryption); - if (response === true) { - _ui.instance.alert(_ui.instance.i18n.updateSuccess); - } else { - _ui.instance.alert(_ui.instance.i18n.updateFailure); - } - }, - dropboxUpdateEncryption: () => { - localStorage.dropboxEncrypted = _ui.instance.dropboxEncrypted; - return; - }, - driveUpdateEncryption: () => { - localStorage.driveEncrypted = _ui.instance.driveEncrypted; - }, - backupLogout: async (service: string) => { - localStorage.removeItem(service + 'Token'); - if (service === 'dropbox') { - _ui.instance.dropboxToken = ''; - } else if (service === 'drive') { - _ui.instance.driveToken = ''; - } - setTimeout(_ui.instance.closeInfo, 500); - }, - getBackupToken: (service: string) => { - chrome.runtime.sendMessage({action: service}); - }, migrateStorage: async () => { // sync => local if (localStorage.storageLocation === 'sync' && diff --git a/view/popup.html b/view/popup.html index 7a7a08096..e64aaf192 100644 --- a/view/popup.html +++ b/view/popup.html @@ -170,14 +170,14 @@
{{ i18n.dropbox_risk }}
-
{{ i18n.log_out }}
{{ i18n.sign_in }}
-
{{ i18n.manual_dropbox }}
+
{{ i18n.manual_dropbox }}
@@ -186,14 +186,14 @@
{{ i18n.dropbox_risk }}
-
{{ i18n.log_out }}
{{ i18n.sign_in }}
-
{{ i18n.manual_dropbox }}
+
{{ i18n.manual_dropbox }}
@@ -262,6 +262,7 @@ + From d35c64808ecc0b9c9e2dffe5679ff57deeac2e0e Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 13 Sep 2018 20:13:00 -0500 Subject: [PATCH 33/53] Update sync toolbar icon --- _locales/en/messages.json | 4 ---- view/popup.html | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 3e83dab4c..c64b41846 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -239,10 +239,6 @@ "message": "Lock", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox sync enabled", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Edit", "description": "Edit" diff --git a/view/popup.html b/view/popup.html index e64aaf192..511092c59 100644 --- a/view/popup.html +++ b/view/popup.html @@ -24,7 +24,7 @@
-
+
From c48456deb5c62e77d4dae6aaa8120c08cf84f57b Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 13 Sep 2018 20:13:45 -0500 Subject: [PATCH 34/53] No auto strings when not on dev just makes pulls messy --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5745bf982..b07ccbbee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ jobs: - stage: test # new i18n strings script: bash scripts/i18n.sh + if: branch = dev AND type != pull_request - stage: deploy # release tagging script: bash scripts/tag.sh From 2a534db5e1d7d13e6332c500c7cb72178aa422f6 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 13 Sep 2018 23:13:22 -0500 Subject: [PATCH 35/53] Make Drive backup to folder --- manifest-chrome.json | 5 +- manifest-firefox.json | 5 +- src/models/backup.ts | 105 ++++++++++++++++++++++++++++++++++++++++-- src/ui/info.ts | 7 ++- 4 files changed, 112 insertions(+), 10 deletions(-) diff --git a/manifest-chrome.json b/manifest-chrome.json index d88f6c870..69cba857c 100644 --- a/manifest-chrome.json +++ b/manifest-chrome.json @@ -59,12 +59,13 @@ "clipboardWrite", "https://www.google.com/", "https://*.dropboxapi.com/*", - "https://www.googleapis.com/upload/drive/v3/files" + "https://www.googleapis.com/upload/drive/v3/files", + "https://www.googleapis.com/drive/v3/files" ], "offline_enabled": true, "web_accessible_resources": [ "view/qr.html", "images/scan.gif" ], - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/upload/drive/v3/files; default-src 'none'" + "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/upload/drive/v3/files https://www.googleapis.com/drive/v3/files ; default-src 'none'" } diff --git a/manifest-firefox.json b/manifest-firefox.json index 607ed38ba..eb730d923 100644 --- a/manifest-firefox.json +++ b/manifest-firefox.json @@ -66,11 +66,12 @@ "clipboardWrite", "https://www.google.com/", "https://*.dropboxapi.com/*", - "https://www.googleapis.com/upload/drive/v3/files" + "https://www.googleapis.com/upload/drive/v3/files", + "https://www.googleapis.com/drive/v3/files" ], "web_accessible_resources": [ "view/qr.html", "images/scan.gif" ], - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/upload/drive/v3/files; default-src 'none'" + "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/upload/drive/v3/files https://www.googleapis.com/drive/v3/files/ ; default-src 'none'" } diff --git a/src/models/backup.ts b/src/models/backup.ts index ce8b9081e..1f1be0f2c 100644 --- a/src/models/backup.ts +++ b/src/models/backup.ts @@ -4,7 +4,7 @@ /// class Dropbox { - async getToken() { + private async getToken() { return localStorage.dropboxToken || ''; } @@ -66,10 +66,100 @@ class Dropbox { } class Drive { - async getToken() { + private async getToken() { return localStorage.driveToken || ''; } + // Make sure to check if trashed! GET + // https://www.googleapis.com/drive/v3/files/[FOLDER ID]?fields=trashed + // returns error.message if deleted + private async getFolder() { + const token = await this.getToken(); + if (localStorage.driveFolder) { + await new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + const xhr = new XMLHttpRequest(); + xhr.open( + 'GET', + 'https://www.googleapis.com/drive/v3/files/' + + localStorage.driveFolder + '?fields=trashed'); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + if (res.error.code === 404) { + localStorage.driveFolder = ''; + resolve(true); + } + } else if (res.trashed) { + localStorage.driveFolder = ''; + resolve(true); + } else if (res.error) { + console.error(res.error.message); + resolve(false); + } else { + resolve(true); + } + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(); + }); + } + if (!localStorage.driveFolder) { + await new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + // create folder + const xhr = new XMLHttpRequest(); + xhr.open('POST', 'https://www.googleapis.com/drive/v3/files/'); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.onreadystatechange = () => { + console.log(xhr); + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (!res.error) { + localStorage.driveFolder = res.id; + resolve(true); + } else { + console.error(res.error.message); + resolve(false); + } + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(JSON.stringify({ + 'name': 'Authenticator Backups', + 'mimeType': 'application/vnd.google-apps.folder' + })); + }); + } + return localStorage.driveFolder; + } + async upload(encryption: Encryption) { if (localStorage.driveEncrypted === undefined) { localStorage.driveEncrypted = 'true'; @@ -79,6 +169,10 @@ class Drive { const backup = JSON.stringify(exportData, null, 2); const token = await this.getToken(); + const folderId = await this.getFolder(); + if (!folderId) { + return false; + } return new Promise( (resolve: (value: boolean) => void, reject: (reason: Error) => void) => { @@ -118,9 +212,10 @@ class Drive { const requestDataPrototype = [ '--segment_marker', 'Content-Type: application/json; charset=UTF-8', '', - JSON.stringify({name: `${now}.json`}), '', '--segment_marker', - 'Content-Type: application/octet-stream', '', backup, - '--segment_marker--' + JSON.stringify( + {name: `${now}.json`, parents: [localStorage.driveFolder]}), + '', '--segment_marker', 'Content-Type: application/octet-stream', + '', backup, '--segment_marker--' ]; let requestData = ''; requestDataPrototype.forEach((line) => { diff --git a/src/ui/info.ts b/src/ui/info.ts index 3547c663f..ad34e5c66 100644 --- a/src/ui/info.ts +++ b/src/ui/info.ts @@ -32,7 +32,12 @@ async function info(_ui: UI) { return; } else if (tab === 'drive') { chrome.permissions.request( - {origins: ['https://www.googleapis.com/upload/drive/v3/files']}, + { + origins: [ + 'https://www.googleapis.com/upload/drive/v3/files', + 'https://www.googleapis.com/drive/v3/files' + ] + }, async (granted) => { if (granted) { _ui.instance.class.fadein = true; From 58aed43217aab861656d0f6319d266b7e8de301b Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 13 Sep 2018 23:21:26 -0500 Subject: [PATCH 36/53] Minor changes --- src/models/backup.ts | 9 +-------- src/popup.ts | 7 ++++++- src/ui/info.ts | 5 +++++ view/popup.html | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/models/backup.ts b/src/models/backup.ts index 1f1be0f2c..2208f369e 100644 --- a/src/models/backup.ts +++ b/src/models/backup.ts @@ -70,9 +70,6 @@ class Drive { return localStorage.driveToken || ''; } - // Make sure to check if trashed! GET - // https://www.googleapis.com/drive/v3/files/[FOLDER ID]?fields=trashed - // returns error.message if deleted private async getFolder() { const token = await this.getToken(); if (localStorage.driveFolder) { @@ -129,7 +126,6 @@ class Drive { xhr.setRequestHeader('Accept', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onreadystatechange = () => { - console.log(xhr); if (xhr.readyState === 4) { if (xhr.status === 401) { localStorage.removeItem('driveToken'); @@ -170,13 +166,10 @@ class Drive { const token = await this.getToken(); const folderId = await this.getFolder(); - if (!folderId) { - return false; - } return new Promise( (resolve: (value: boolean) => void, reject: (reason: Error) => void) => { - if (!token) { + if (!token || !folderId) { resolve(false); } try { diff --git a/src/popup.ts b/src/popup.ts index 570ed8173..c2cc3ff03 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -86,7 +86,12 @@ async function init() { }); } else if (authenticator.driveToken) { chrome.permissions.contains( - {origins: ['https://www.googleapis.com/upload/drive/v3/files']}, + { + origins: [ + 'https://www.googleapis.com/upload/drive/v3/files', + 'https://www.googleapis.com/drive/v3/files' + ] + }, async (hasPermission) => { if (hasPermission) { try { diff --git a/src/ui/info.ts b/src/ui/info.ts index ad34e5c66..dbcd64076 100644 --- a/src/ui/info.ts +++ b/src/ui/info.ts @@ -31,6 +31,11 @@ async function info(_ui: UI) { }); return; } else if (tab === 'drive') { + if (localStorage.driveEncrypted !== 'true' && + localStorage.driveEncrypted !== 'false') { + localStorage.driveEncrypted = 'true'; + _ui.instance.driveEncrypted = localStorage.driveEncrypted; + } chrome.permissions.request( { origins: [ diff --git a/view/popup.html b/view/popup.html index 511092c59..6e57e1f8f 100644 --- a/view/popup.html +++ b/view/popup.html @@ -183,7 +183,7 @@
-
{{ i18n.dropbox_risk }}
+
{{ i18n.dropbox_risk }}