From 8252d7259859afdee5cbd799045b4e08cd9c699d Mon Sep 17 00:00:00 2001 From: antelle Date: Sat, 2 Apr 2016 16:20:48 +0300 Subject: [PATCH] otp bugfixes --- app/scripts/const/timeouts.js | 3 +- app/scripts/models/entry-model.js | 15 ++++-- app/scripts/views/details/details-view.js | 21 +++++++- app/scripts/views/fields/field-view-otp.js | 60 +++++++++++++++++++--- 4 files changed, 85 insertions(+), 14 deletions(-) diff --git a/app/scripts/const/timeouts.js b/app/scripts/const/timeouts.js index e82c211e..be0ddd74 100644 --- a/app/scripts/const/timeouts.js +++ b/app/scripts/const/timeouts.js @@ -6,7 +6,8 @@ var Timeouts = { AutoHideHint: 3000, FileChangeSync: 3000, BeforeAutoLock: 300, - CheckWindowClosed: 300 + CheckWindowClosed: 300, + OtpFadeDuration: 10000 }; module.exports = Timeouts; diff --git a/app/scripts/models/entry-model.js b/app/scripts/models/entry-model.js index ad8d31bf..27403ac0 100644 --- a/app/scripts/models/entry-model.js +++ b/app/scripts/models/entry-model.js @@ -371,7 +371,6 @@ var EntryModel = Backbone.Model.extend({ }, initOtpGenerator: function() { - this.otpGenerator = null; var otpUrl; if (this.fields.otp) { otpUrl = this.fields.otp; @@ -391,8 +390,7 @@ var EntryModel = Backbone.Model.extend({ (args.size ? '&digits=' + args.size : ''); } } - } - if (this.fields['TOTP Seed']) { + } else if (this.fields['TOTP Seed']) { // TrayTOTP plugin format var key = this.fields['TOTP Seed']; if (key.isProtected) { @@ -417,17 +415,26 @@ var EntryModel = Backbone.Model.extend({ this.fields.otp = kdbxweb.ProtectedValue.fromString(otpUrl); } if (otpUrl) { + if (this.otpGenerator && this.otpGenerator.url === otpUrl) { + return; + } try { this.otpGenerator = Otp.parseUrl(otpUrl); } catch (e) { this.otpGenerator = null; } + } else { + this.otpGenerator = null; } }, setOtp: function(otp) { this.otpGenerator = otp; - this.setField('otp', kdbxweb.ProtectedValue.fromString(otp.url)); + this.setOtpUrl(otp.url); + }, + + setOtpUrl: function(url) { + this.setField('otp', kdbxweb.ProtectedValue.fromString(url)); } }); diff --git a/app/scripts/views/details/details-view.js b/app/scripts/views/details/details-view.js index cb113034..c13d0917 100644 --- a/app/scripts/views/details/details-view.js +++ b/app/scripts/views/details/details-view.js @@ -158,7 +158,7 @@ var DetailsView = Backbone.View.extend({ this.fieldViews.push(new FieldViewHistory({ model: { name: 'History', title: Locale.detHistory, value: function() { return { length: model.historyLength, unsaved: model.unsaved }; } } })); _.forEach(model.fields, function(value, field) { - if (field.toLowerCase() === 'otp' && this.model.otpGenerator) { + if (field === 'otp' && this.model.otpGenerator) { this.fieldViews.push(new FieldViewOtp({ model: { name: '$' + field, title: field, value: function() { return model.otpGenerator; } } })); } else { @@ -434,7 +434,12 @@ var DetailsView = Backbone.View.extend({ if (e.field) { if (e.field[0] === '$') { var fieldName = e.field.substr(1); - if (e.newField) { + if (fieldName === 'otp') { + if (this.otpFieldChanged(e.val)) { + this.entryUpdated(); + return; + } + } else if (e.newField) { if (fieldName) { this.model.setField(fieldName, undefined); } @@ -478,6 +483,18 @@ var DetailsView = Backbone.View.extend({ } }, + otpFieldChanged: function(value) { + var oldValue = this.model.fields.otp; + if (oldValue && oldValue.isProtected) { + oldValue = oldValue.getText(); + } + if (oldValue === value) { + return false; + } + this.model.setOtpUrl(value); + return true; + }, + fieldCopied: function(e) { if (this.fieldCopyTip) { this.fieldCopyTip.hide(); diff --git a/app/scripts/views/fields/field-view-otp.js b/app/scripts/views/fields/field-view-otp.js index 01f6d964..7276efa5 100644 --- a/app/scripts/views/fields/field-view-otp.js +++ b/app/scripts/views/fields/field-view-otp.js @@ -1,15 +1,22 @@ 'use strict'; -var FieldViewText = require('./field-view-text'); +var FieldViewText = require('./field-view-text'), + Timeouts = require('../../const/timeouts'); + +var MinOpacity = 0.2; var FieldViewOtp = FieldViewText.extend({ otpTimeout: null, + otpTickInterval: null, otpValue: null, otpGenerator: null, + otpTimeLeft: 0, + otpValidUntil: 0, + fieldOpacity: null, renderValue: function(value) { - this.resetOtpTimer(); if (!value) { + this.resetOtp(); return ''; } if (value !== this.otpGenerator) { @@ -23,16 +30,29 @@ var FieldViewOtp = FieldViewText.extend({ return value && value.url; }, + render: function() { + FieldViewText.prototype.render.call(this); + this.fieldOpacity = null; + this.otpTick(); + }, + remove: function() { - this.resetOtpTimer(); - this.value = null; - this.otpGenerator = null; + this.resetOtp(); FieldViewText.prototype.remove.apply(this, arguments); }, - resetOtpTimer: function() { + resetOtp: function() { + this.otpGenerator = null; + this.otpValue = null; + this.otpTimeLeft = 0; + this.otpValidUntil = 0; if (this.otpTimeout) { clearTimeout(this.otpTimeout); + this.otpTimeout = null; + } + if (this.otpTickInterval) { + clearInterval(this.otpTickInterval); + this.otpTickInterval = null; } }, @@ -43,14 +63,40 @@ var FieldViewOtp = FieldViewText.extend({ }, otpUpdated: function(pass, timeLeft) { - if (!this.value) { + if (!this.value || !pass) { + this.resetOtp(); return; } this.otpValue = pass || ''; + this.otpTimeLeft = timeLeft || 0; + this.otpValidUntil = Date.now() + timeLeft; this.render(); if (this.otpValue && timeLeft) { this.otpTimeout = setTimeout(this.requestOtpUpdate.bind(this), timeLeft); + if (!this.otpTickInterval) { + this.otpTickInterval = setInterval(this.otpTick.bind(this), 300); + } } + }, + + otpTick: function() { + if (!this.value || !this.otpValidUntil) { + return; + } + var opacity; + var timeLeft = this.otpValidUntil - Date.now(); + if (timeLeft >= Timeouts.OtpFadeDuration || this.editing) { + opacity = 1; + } else if (timeLeft <= 0) { + opacity = MinOpacity; + } else { + opacity = Math.max(MinOpacity, Math.pow(timeLeft / Timeouts.OtpFadeDuration, 2)); + } + if (this.fieldOpacity === opacity) { + return; + } + this.fieldOpacity = opacity; + this.valueEl.css('opacity', opacity); } });