VulpesOS/gaia/dev_apps/mobile-wallet/js/pin/pin_controller.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

316 lines
9.2 KiB
JavaScript
Executable file

'use strict';
/* globals addMixin, DebugMixin, PinView, DEFAULTS, PIN_DEFAULTS */
/* exported PinController */
(function(exports) {
const PIN_STATE = {
UNKNOWN: 'unknown',
ENABLED: 'enabled',
DISABLED: 'disabled',
BLOCKED: 'blocked',
};
var _defaultPin = DEFAULTS.PIN_VALUE;
var PinController = function(simDataSource, id, viewId, templateId) {
this._simDataSource = simDataSource;
this._id = id;
this._view = new PinView(viewId, PIN_DEFAULTS.PIN_LEN, templateId);
addMixin(this, DebugMixin);
};
PinController.prototype = {
_id: null,
_simDataSource: null,
_view: null,
// TODO consider removing
_simPinState: PIN_STATE.UNKNOWN,
_pinCheckInProgress: false,
_lastInputedPin: null,
get pinCheckInProgress() {
return this._pinCheckInProgress;
},
set defaultPin(value) {
_defaultPin = value;
},
get view() {
return this._view._el;
},
hideView: function() {
this._view.hide();
},
verifyPin: function() {
return this._requestPinVerification().then((result) => {
if(result) {
this.hideView();
}
return result;
});
},
enablePin: function() {
return this._requestPinVerification()
.then((verified) => {
if(verified) {
this.debug('Pin verified');
this._simPinState = PIN_STATE.ENABLED;
this._lastInputedPin = null;
return true;
} else {
this.debug('Pin verification failed, pin probably disabled');
var pin = this._lastInputedPin;
this._lastInputedPin = null;
return this._enableSimPin(pin);
}
});
},
disablePin: function() {
return this._requestPinVerification()
.then((verified) => {
if(verified) {
this.debug('Pin verified');
var pin = this._lastInputedPin;
this._lastInputedPin = null;
return this._disableSimPin(pin);
} else {
this.debug('Pin verification failed, pin probably disabled');
this._lastInputedPin = null;
return true;
}
});
},
changePin: function() {
var pin;
return this._requestPinVerification(this._view.REQUEST_BEFORE_CHANGE)
.then((verified) => {
if(!verified) {
this.debug('Pin not verfied, not changing');
this._lastInputedPin = null;
return false;
}
pin = this._lastInputedPin;
this._lastInputedPin = null;
return this._getNewPin();
})
.then((newPin) => {
this.debug('New pin value ' + newPin);
var pinBytes = this._formatPin(pin);
var newPinBytes = this._formatPin(newPin);
return this._simDataSource.changePIN(pinBytes, newPinBytes);
})
.then((result) => this._handlePinChangeResult(result));
},
cancelPinAction: function() {
this._view.cancelPinRequest();
},
_handlePinChangeResult: function(result) {
this.debug('Pin change result ' + result);
if(result === '9000') {
return true;
} else {
this.debug('Unexpected error');
return false;
}
},
_getNewPin: function(prevAttempFailed) {
var newPin;
return this._getUserPin(PIN_DEFAULTS.MAX_PUK_ATTEMPTS, prevAttempFailed,
this._view.REQUEST_NEW_PIN)
.then((pin1) => {
newPin = pin1;
return this._getUserPin(PIN_DEFAULTS.MAX_PUK_ATTEMPTS, false,
this._view.REQUEST_CONFIRM);
})
.then((pin2) => {
if(newPin === pin2) {
return newPin;
}
this.debug('Pins do not match');
return this._getNewPin(true);
});
},
_requestPinVerification: function(reqType) {
this.debug('_requestPinVerification, SIM PIN : ' + this._simPinState);
// TODO verify if we need this
//if(this._pinCheckInProgress) {
// return Promise.resolve(false);
//}
this._pinCheckInProgress = true;
this._view.show();
return this._performVerification(PIN_DEFAULTS.MAX_PIN_ATTEMPTS, false, reqType)
.then((verified) => {
if(verified) {
//this._view.hide();
// TODO think this through
this._pinCheckInProgress = false;
}
return verified;
});
},
_getUserPin: function(attemptsLeft, lastVerFailed, reqType, pinLen) {
return new Promise((resolve, reject) => {
var listener = {
onEvent: (id, data) => {
this._view.removeListener(listener);
if (data.pin) {
this.debug('got PIN: ' + data.pin);
resolve(data.pin);
} else {
reject('pin-cancelled');
}
}
};
this._view.addListener(listener);
if(!this._view.isVisible) {
this._view.show();
}
this.debug('waiting for PIN input from user');
this._view.requestPin(attemptsLeft, lastVerFailed, reqType, pinLen);
});
},
_performVerification: function(attemptsLeft, lastVerFailed, reqType) {
return this._getUserPin(attemptsLeft, lastVerFailed, reqType)
.then((pin) => {
this._lastInputedPin = pin;
var pinBytes = this._formatPin(pin);
this.debug('verifying PIN ' + pinBytes);
return this._simDataSource.verifyPIN(pinBytes);
})
.then((result) => this._handleVerficationResult(result));
//TODO should we catch here?
},
_handleVerficationResult: function(result) {
this.debug('Verification finished, status word:' + result);
if(result === '9000') {
return true;
} else if(result === '63C0' || result === '6983') {
this.debug('Pin blocked');
return this._handlePinBlocked();
} else if(result.startsWith('63C')) {
var attemptsLeft = Number.parseInt(result[3]);
this.debug('Verification failed, tries left: ' + attemptsLeft);
return this._performVerification(attemptsLeft, true);
} else {
this.debug('Unknown status word, PIN probably not enabled');
return false;
}
},
_handlePinBlocked: function() {
this._simPinState = PIN_STATE.BLOCKED;
this.debug('PUK unblock flow start.');
var unblockRequested = () => this._performPinUnblock(PIN_DEFAULTS.MAX_PUK_ATTEMPTS);
return this._view.blockPin().then(unblockRequested);
},
_performPinUnblock: function(attemptsLeft, lastUnblockFailed) {
var puk;
return this._getUserPin(attemptsLeft, lastUnblockFailed,
this._view.REQUEST_PUK, PIN_DEFAULTS.MAX_SIM_PIN_LEN)
.then((inputedPuk) => {
this.debug('Inputed PUK ' + inputedPuk);
puk = inputedPuk;
return this._getNewPin(false);
})
.then((inputedPin) => {
this.debug('Inputed PIN ' + inputedPin);
var bytePuk = this._formatPin(puk);
var bytePin = this._formatPin(inputedPin);
// if we're part of disable/enable flow we need to save pin here
this._lastInputedPin = inputedPin;
return this._simDataSource.unblockPIN(bytePuk, bytePin);
})
.then((result) => this._handlePinUnblockResult(result));
},
_handlePinUnblockResult: function(result) {
this.debug('PIN unblock result ' + result);
if(result === '9000') {
return true;
} else if(result === '63C0' || !result.startsWith('63C')) {
this.debug('Complate block');
return this._view.blockPuk();
} else {
var attemptsLeft = Number.parseInt(result[3]);
this.debug('PIN unblock failed, tries left: ' + attemptsLeft);
return this._performPinUnblock(attemptsLeft, true);
}
},
_enableSimPin: function(pin) {
var pinBytes = this._formatPin(pin);
return this._simDataSource.enablePIN(pinBytes)
.then((result) => this._handlePinEnabledResult(result));
},
_handlePinEnabledResult: function(result) {
this.debug('PIN enable result ' + result);
if(result === '9000') {
this._simPinState = PIN_STATE.ENABLED;
//this._view.hide();
return true;
} else if(result === '63C0' || result === '6983') {
this.debug('Pin blocked');
return this._handlePinBlocked();
} else {
var tries = result.startsWith('63C') ? Number.parseInt(result[3]) : 0;
this._view.showEnableFailed(tries, _defaultPin);
return false;
}
},
_disableSimPin: function(pin) {
var pinBytes = this._formatPin(pin);
return this._simDataSource.disablePIN(pinBytes)
.then((result) => this._handlePinDisableResult(result));
},
_handlePinDisableResult: function(result) {
this.debug('PIN disable result ' + result);
if(result === '9000') {
this._simPinState = PIN_STATE.DISABLED;
//this._view.hide();
return true;
}
return false;
},
_formatPin: function(pin) {
var pinBytes = pin.split('').map((d) => d.charCodeAt());
for(var i = pinBytes.length; i < PIN_DEFAULTS.MAX_SIM_PIN_LEN; i++) {
pinBytes.push(PIN_DEFAULTS.PIN_PADDING);
}
return pinBytes;
}
};
exports.PinController = PinController;
}((typeof exports === 'undefined') ? window : exports));