Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
274 lines
7.7 KiB
JavaScript
274 lines
7.7 KiB
JavaScript
'use strict';
|
|
|
|
/* globals addMixin, ObserverSubjectMixin, DebugMixin */
|
|
/* exported PinView */
|
|
|
|
(function(exports) {
|
|
// number of pin input attempts which should
|
|
// trigger the pin warning display
|
|
const PIN_ATTEMPTS_WARNING = 2;
|
|
const PIN_INCORRECT_TIMEOUT = 1000; //ms
|
|
const PIN_DEFAULT_LEN = 4;
|
|
|
|
const PIN_REQ_DEF = 'request-default-pin';
|
|
const PIN_REQ_BEFORE_CHANGE = 'request-pin-before-change';
|
|
const PIN_REQ_PUK = 'request-puk';
|
|
const PIN_REQ_NEW_PIN = 'request-new-pin';
|
|
const PIN_REQ_REPEAT_NEW_PIN = 'request-repeat-new-pin';
|
|
|
|
const PIN_ENTRY_TEXT_MAP = {};
|
|
|
|
PIN_ENTRY_TEXT_MAP[PIN_REQ_DEF] = {
|
|
entry: 'Please enter your PIN',
|
|
error: 'Incorrect PIN'
|
|
};
|
|
|
|
PIN_ENTRY_TEXT_MAP[PIN_REQ_BEFORE_CHANGE] = {
|
|
entry: 'Please enter current PIN',
|
|
error: 'Incorrect PIN'
|
|
},
|
|
|
|
PIN_ENTRY_TEXT_MAP[PIN_REQ_PUK] = {
|
|
entry: 'Please enter your PUK',
|
|
error: 'Incorrect PUK'
|
|
};
|
|
|
|
PIN_ENTRY_TEXT_MAP[PIN_REQ_NEW_PIN] = {
|
|
entry: 'Please enter new PIN',
|
|
error: 'New PIN mismatch',
|
|
};
|
|
|
|
PIN_ENTRY_TEXT_MAP[PIN_REQ_REPEAT_NEW_PIN] = {
|
|
entry: 'Please confirm new PIN',
|
|
error: ''
|
|
};
|
|
|
|
var PinView = function(id, defPinLen, templateId) {
|
|
this._id = id;
|
|
this._defaultPinLen = defPinLen || PIN_DEFAULT_LEN;
|
|
this._maxPinLen = this._defaultPinLen;
|
|
|
|
addMixin(this, ObserverSubjectMixin);
|
|
addMixin(this, DebugMixin);
|
|
|
|
if(templateId) {
|
|
this._el = document.querySelector('#' + templateId).cloneNode(true);
|
|
this._el.id = this._id;
|
|
} else {
|
|
this._el = document.querySelector('#' + this._id);
|
|
}
|
|
|
|
this._init();
|
|
};
|
|
|
|
PinView.prototype = {
|
|
REQUEST_PIN: PIN_REQ_DEF,
|
|
REQUEST_BEFORE_CHANGE: PIN_REQ_BEFORE_CHANGE,
|
|
REQUEST_PUK: PIN_REQ_PUK,
|
|
REQUEST_NEW_PIN: PIN_REQ_NEW_PIN,
|
|
REQUEST_CONFIRM: PIN_REQ_REPEAT_NEW_PIN,
|
|
|
|
_el: null,
|
|
_id: null,
|
|
_visible: false,
|
|
|
|
_pinInputEl: null,
|
|
_inputedPin: '',
|
|
|
|
// we have a default PIN length but we can do one time request for PINs
|
|
// of different length (i.e. PUK)
|
|
_defaultPinLen: 0,
|
|
_maxPinLen: 0,
|
|
|
|
_inputBlocked: false,
|
|
|
|
isVisible() {
|
|
return this._visible;
|
|
},
|
|
|
|
show: function() {
|
|
this._visible = true;
|
|
this._el.classList.remove('hide');
|
|
this._clearPin();
|
|
},
|
|
|
|
hide: function() {
|
|
this._visible = false;
|
|
this._el.classList.add('hide');
|
|
},
|
|
|
|
_init: function() {
|
|
this._pinInputEl = this._el.querySelector('.pin-input');
|
|
|
|
var keyHandler = this._keyHandler.bind(this);
|
|
var keypad = this._el.querySelector('.pin-keypad');
|
|
keypad.addEventListener('touchstart', keyHandler, true);
|
|
|
|
this._updatePinInput();
|
|
},
|
|
|
|
_keyHandler: function(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
var key = event.target.dataset.value;
|
|
if(!key || this._inputBlocked) {
|
|
return;
|
|
}
|
|
|
|
switch(key) {
|
|
case 'clear': this._clearPin(); break;
|
|
case 'backspace': this._backspacePin(); break;
|
|
default: this._appendPin(key);
|
|
}
|
|
},
|
|
|
|
_clearPin: function() {
|
|
this._inputedPin = '';
|
|
this._updatePinInput();
|
|
},
|
|
|
|
_backspacePin: function() {
|
|
if(!this._inputedPin.length) {
|
|
return;
|
|
}
|
|
|
|
this._inputedPin = this._inputedPin.slice(0, -1);
|
|
this._updatePinInput();
|
|
},
|
|
|
|
_appendPin: function(digit) {
|
|
if(this._inputedPin.length >= this._maxPinLen) {
|
|
return;
|
|
}
|
|
|
|
this._inputedPin += digit;
|
|
this._updatePinInput();
|
|
|
|
if(this._inputedPin.length === this._maxPinLen) {
|
|
if(this._maxPinLen !== this._defaultPinLen) {
|
|
this._maxPinLen = this._defaultPinLen;
|
|
}
|
|
this._notify({ action: 'pin-inputed', pin: this._inputedPin });
|
|
}
|
|
},
|
|
|
|
_updatePinInput: function() {
|
|
var inputedDigits = this._inputedPin.length;
|
|
var fragment = document.createDocumentFragment();
|
|
for(var i = 0; i < this._maxPinLen; i++) {
|
|
var pinDigit = document.createElement('div');
|
|
if(i < inputedDigits) {
|
|
pinDigit.classList.add('pin-digit-entered');
|
|
}
|
|
fragment.appendChild(pinDigit);
|
|
}
|
|
this._pinInputEl.innerHTML = '';
|
|
this._pinInputEl.appendChild(fragment);
|
|
},
|
|
|
|
_updatePinInputTexts: function _updatePinInputTexts(reqType) {
|
|
reqType = (reqType in PIN_ENTRY_TEXT_MAP) ? reqType : PIN_REQ_DEF;
|
|
this._el.querySelector('.pin-entry-text')
|
|
.textContent = PIN_ENTRY_TEXT_MAP[reqType].entry;
|
|
this._el.querySelector('.pin-entry-error-text')
|
|
.textContent = PIN_ENTRY_TEXT_MAP[reqType].error;
|
|
},
|
|
|
|
requestPin: function(attemptsLeft, showPinIncorrect, reqType, pinLen) {
|
|
this.debug('requestPIN, tries: ' + attemptsLeft + ', type: ' + reqType);
|
|
this._maxPinLen = pinLen || this._maxPinLen;
|
|
this._clearPin();
|
|
|
|
this._updatePinInputTexts(reqType);
|
|
this._showByClass('pin-unblocked');
|
|
|
|
if(showPinIncorrect) {
|
|
this._showPinIncorrect();
|
|
if(reqType !== PIN_REQ_NEW_PIN) {
|
|
this._showWarning(attemptsLeft);
|
|
}
|
|
} else if(attemptsLeft <= PIN_ATTEMPTS_WARNING) {
|
|
this._showWarning(attemptsLeft);
|
|
} else {
|
|
this._clearWarning();
|
|
}
|
|
},
|
|
|
|
cancelPinRequest: function() {
|
|
this._maxPinLen = this._defaultPinLen;
|
|
this._notify({});
|
|
},
|
|
|
|
blockPin: function() {
|
|
window.navigator.vibrate([200, 100, 200, 100, 200]);
|
|
this._showByClass('pin-blocked');
|
|
|
|
return new Promise((resolve, reject) => {
|
|
var unblockBtn = this._el.querySelector('.pin-unblock-btn');
|
|
var onclick = () => {
|
|
unblockBtn.removeEventListener('click', onclick);
|
|
resolve();
|
|
};
|
|
|
|
unblockBtn.addEventListener('click', onclick);
|
|
});
|
|
},
|
|
|
|
blockPuk: function() {
|
|
window.navigator.vibrate([200, 100, 200, 100, 200]);
|
|
this._showByClass('puk-blocked');
|
|
return new Promise(()=>{});
|
|
},
|
|
|
|
showEnableFailed: function(tries, defaultPin) {
|
|
window.navigator.vibrate([200, 100, 200, 100, 200]);
|
|
this._showByClass('pin-enable-error');
|
|
this._el.querySelector('.pin-default-val').textContent = defaultPin;
|
|
if(tries || tries === 0) {
|
|
this._el.querySelector('.pin-enable-warning').classList.remove('hide');
|
|
var att = (tries === 1) ? ' entry attempt' : ' entry attempts';
|
|
this._el.querySelector('.pin-enable-attempt').textContent = tries + att;
|
|
} else {
|
|
this._el.querySelector('.pin-enable-warning').classList.add('hide');
|
|
}
|
|
},
|
|
|
|
_showByClass: function(clss) {
|
|
var nodesList = this._el.querySelectorAll('article');
|
|
Array.prototype.slice.call(nodesList).forEach(el => {
|
|
if(el.classList.contains(clss)) {
|
|
el.classList.remove('hide');
|
|
} else {
|
|
el.classList.add('hide');
|
|
}
|
|
});
|
|
},
|
|
|
|
_showWarning: function(attemptsLeft) {
|
|
this._el.querySelector('.pin-warning').classList.remove('hide');
|
|
var warning = attemptsLeft + ' attempt';
|
|
warning = (attemptsLeft !== 1) ? (warning + 's') : warning;
|
|
warning += ' remaining';
|
|
this._el.querySelector('.pin-attempts').textContent = warning;
|
|
},
|
|
|
|
_togglePinIncorrect: function(show) {
|
|
this._inputBlocked = show;
|
|
this._el.querySelector('.pin-entry').classList.toggle('hide');
|
|
this._el.querySelector('.pin-incorrect').classList.toggle('hide');
|
|
},
|
|
|
|
_showPinIncorrect: function() {
|
|
window.navigator.vibrate([200, 100, 200]);
|
|
this._togglePinIncorrect(true);
|
|
setTimeout(() => this._togglePinIncorrect(false), PIN_INCORRECT_TIMEOUT);
|
|
},
|
|
|
|
_clearWarning: function() {
|
|
this._el.querySelector('.pin-warning').classList.add('hide');
|
|
}
|
|
};
|
|
|
|
exports.PinView = PinView;
|
|
}((typeof exports === 'undefined') ? window : exports));
|