keeweb/app/scripts/comp/format/otp-qr-reader.js

175 lines
5.3 KiB
JavaScript
Raw Normal View History

2019-09-15 14:16:32 +02:00
import QrCode from 'jsqrcode';
2019-09-17 19:01:12 +02:00
import { Events } from 'framework/events';
2019-09-15 14:16:32 +02:00
import { Shortcuts } from 'comp/app/shortcuts';
import { Alerts } from 'comp/ui/alerts';
import { Otp } from 'util/data/otp';
import { Features } from 'util/features';
import { Locale } from 'util/locale';
import { Logger } from 'util/logger';
2016-03-31 22:52:04 +02:00
2017-01-31 07:50:28 +01:00
const logger = new Logger('otp-qr-reader');
2016-03-31 22:52:04 +02:00
2019-09-17 19:01:12 +02:00
class OtpQrReader {
2019-09-16 23:27:31 +02:00
alert = null;
2016-03-31 22:52:04 +02:00
2019-09-16 23:27:31 +02:00
fileInput = null;
constructor() {
this.pasteEvent = this.pasteEvent.bind(this);
}
2016-04-02 16:34:30 +02:00
2019-08-18 10:17:09 +02:00
read() {
let screenshotKey = Shortcuts.screenshotToClipboardShortcut();
2016-03-31 22:52:04 +02:00
if (screenshotKey) {
2020-05-09 16:35:11 +02:00
screenshotKey = Locale.detSetupOtpAlertBodyWith.replace('{}', screenshotKey);
2016-03-31 22:52:04 +02:00
}
2019-09-15 08:11:11 +02:00
const pasteKey = Features.isMobile
2019-08-16 23:05:39 +02:00
? ''
2020-05-09 16:35:11 +02:00
: Locale.detSetupOtpAlertBodyWith.replace('{}', Shortcuts.actionShortcutSymbol() + 'V');
2019-09-16 23:27:31 +02:00
this.startListenClipoard();
2019-08-16 23:05:39 +02:00
const buttons = [
{ result: 'manually', title: Locale.detSetupOtpManualButton, silent: true },
Alerts.buttons.cancel
];
2019-09-15 08:11:11 +02:00
if (Features.isMobile) {
2019-08-16 23:05:39 +02:00
buttons.unshift({ result: 'select', title: Locale.detSetupOtpScanButton });
2016-04-02 16:34:30 +02:00
}
2019-09-15 08:11:11 +02:00
const line3 = Features.isMobile
2019-08-16 23:05:39 +02:00
? Locale.detSetupOtpAlertBody3Mobile
2016-07-17 13:30:38 +02:00
: Locale.detSetupOtpAlertBody3.replace('{}', pasteKey || '');
2019-09-16 23:27:31 +02:00
this.alert = Alerts.alert({
2016-03-31 22:52:04 +02:00
icon: 'qrcode',
header: Locale.detSetupOtpAlert,
2019-08-16 23:05:39 +02:00
body: [
Locale.detSetupOtpAlertBody,
2016-03-31 22:52:04 +02:00
Locale.detSetupOtpAlertBody1,
Locale.detSetupOtpAlertBody2.replace('{}', screenshotKey || ''),
2016-04-04 20:45:17 +02:00
line3,
Locale.detSetupOtpAlertBody4
2020-04-23 19:55:52 +02:00
].join('\n'),
2016-03-31 22:52:04 +02:00
esc: '',
click: '',
enter: '',
2019-08-18 10:17:09 +02:00
buttons,
2020-06-01 16:53:51 +02:00
complete: (res) => {
2019-09-16 23:27:31 +02:00
this.alert = null;
this.stopListenClipboard();
2016-04-02 16:34:30 +02:00
if (res === 'select') {
2019-09-16 23:27:31 +02:00
this.selectFile();
2016-04-04 20:45:17 +02:00
} else if (res === 'manually') {
2019-09-16 23:27:31 +02:00
this.enterManually();
2016-04-02 16:34:30 +02:00
}
2016-03-31 22:52:04 +02:00
}
});
2019-09-16 23:27:31 +02:00
}
2016-03-31 22:52:04 +02:00
2019-08-18 10:17:09 +02:00
selectFile() {
2019-09-16 23:27:31 +02:00
if (!this.fileInput) {
2017-01-31 07:50:28 +01:00
const input = document.createElement('input');
2016-04-02 16:34:30 +02:00
input.setAttribute('type', 'file');
input.setAttribute('capture', 'camera');
input.setAttribute('accept', 'image/*');
input.setAttribute('class', 'hide-by-pos');
2019-09-16 23:27:31 +02:00
this.fileInput = input;
this.fileInput.onchange = this.fileSelected;
2016-04-02 16:34:30 +02:00
}
2019-09-16 23:27:31 +02:00
this.fileInput.click();
}
2016-04-02 16:34:30 +02:00
2019-08-18 10:17:09 +02:00
fileSelected() {
2019-09-16 23:27:31 +02:00
const file = this.fileInput.files[0];
2016-04-02 16:34:30 +02:00
if (!file || file.type.indexOf('image') < 0) {
return;
}
2019-09-16 23:27:31 +02:00
this.readFile(file);
}
2016-04-02 16:34:30 +02:00
2019-08-18 10:17:09 +02:00
startListenClipoard() {
2019-09-16 23:27:31 +02:00
document.addEventListener('paste', this.pasteEvent);
}
2016-03-31 22:52:04 +02:00
2019-08-18 10:17:09 +02:00
stopListenClipboard() {
2019-09-16 23:27:31 +02:00
document.removeEventListener('paste', this.pasteEvent);
}
2016-03-31 22:52:04 +02:00
2019-08-18 10:17:09 +02:00
pasteEvent(e) {
2019-09-18 20:42:17 +02:00
const item = [...e.clipboardData.items].find(
2020-06-01 16:53:51 +02:00
(item) => item.kind === 'file' && item.type.indexOf('image') !== -1
2019-08-18 08:05:38 +02:00
);
2016-03-31 22:52:04 +02:00
if (!item) {
2016-04-02 16:34:30 +02:00
logger.debug('Paste without file');
2016-03-31 22:52:04 +02:00
return;
}
2016-04-02 16:34:30 +02:00
logger.info('Reading pasted image', item.type);
2019-09-16 23:27:31 +02:00
if (this.alert) {
this.alert.change({
2016-03-31 22:52:04 +02:00
header: Locale.detOtpImageReading
});
}
2019-09-16 23:27:31 +02:00
this.readFile(item.getAsFile());
}
2016-04-02 16:34:30 +02:00
2019-08-18 10:17:09 +02:00
readFile(file) {
2017-01-31 07:50:28 +01:00
const reader = new FileReader();
2019-09-16 23:27:31 +02:00
reader.onload = () => {
2016-03-31 22:52:04 +02:00
logger.debug('Image data loaded');
2019-09-16 23:27:31 +02:00
this.readQr(reader.result);
2016-03-31 22:52:04 +02:00
};
2016-04-02 16:34:30 +02:00
reader.readAsDataURL(file);
2019-09-16 23:27:31 +02:00
}
2016-03-31 22:52:04 +02:00
2019-08-18 10:17:09 +02:00
readQr(imageData) {
2017-01-31 07:50:28 +01:00
const image = new Image();
2019-09-16 23:27:31 +02:00
image.onload = () => {
2016-03-31 22:52:04 +02:00
logger.debug('Image format loaded');
try {
2017-01-31 07:50:28 +01:00
const ts = logger.ts();
const url = new QrCode(image).decode();
2016-04-01 21:19:27 +02:00
logger.info('QR code read', logger.ts(ts));
2019-09-16 23:27:31 +02:00
this.removeAlert();
2016-03-31 22:52:04 +02:00
try {
2017-01-31 07:50:28 +01:00
const otp = Otp.parseUrl(url);
2019-09-17 19:01:12 +02:00
Events.emit('qr-read', otp);
2016-03-31 22:52:04 +02:00
} catch (err) {
2016-04-01 21:19:27 +02:00
logger.error('Error parsing QR code', err);
2016-03-31 22:52:04 +02:00
Alerts.error({
header: Locale.detOtpQrWrong,
2020-04-23 19:55:52 +02:00
body: Locale.detOtpQrWrongBody,
pre: err.toString()
2016-03-31 22:52:04 +02:00
});
}
} catch (e) {
logger.error('Error reading QR code', e);
2019-09-16 23:27:31 +02:00
this.removeAlert();
2016-03-31 22:52:04 +02:00
Alerts.error({
header: Locale.detOtpQrError,
body: Locale.detOtpQrErrorBody
});
}
};
2019-09-16 23:27:31 +02:00
image.onerror = () => {
2016-03-31 22:52:04 +02:00
logger.debug('Image load error');
2019-09-16 23:27:31 +02:00
this.removeAlert();
2016-03-31 22:52:04 +02:00
Alerts.error({
header: Locale.detOtpImageError,
body: Locale.detOtpImageErrorBody
});
};
image.src = imageData;
2019-09-16 23:27:31 +02:00
}
2016-04-02 16:34:30 +02:00
2019-08-18 10:17:09 +02:00
enterManually() {
2019-09-28 19:22:44 +02:00
Events.emit('qr-enter-manually');
2019-09-16 23:27:31 +02:00
}
2016-04-04 20:45:17 +02:00
2019-08-18 10:17:09 +02:00
removeAlert() {
2019-09-16 23:27:31 +02:00
if (this.alert) {
this.alert.closeImmediate();
2016-04-02 16:34:30 +02:00
}
2016-03-31 22:52:04 +02:00
}
2019-09-16 23:27:31 +02:00
}
2016-03-31 22:52:04 +02:00
2019-09-16 23:27:31 +02:00
const instance = new OtpQrReader();
2016-03-31 22:52:04 +02:00
2019-09-16 23:27:31 +02:00
export { instance as OtpQrReader };