QR code completion

pull/713/head
Vincent 5 years ago
parent 798eb402cb
commit 659d4412bd

@ -960,6 +960,9 @@
"pairedDevices": {
"message": "Paired Devices"
},
"noPairedDevices": {
"message": "No paired devices"
},
"allowPairing": {
"message": "Allow Pairing"
},

@ -50,8 +50,9 @@ const {
} = require('../../ts/components/conversation/CreateGroupDialog');
const { EditProfileDialog } = require('../../ts/components/EditProfileDialog');
const { UserDetailsDialog } = require('../../ts/components/UserDetailsDialog');
const {
SessionSettings,
const { DevicePairingDialog } = require('../../ts/components/DevicePairingDialog');
const { SessionSettings,
} = require('../../ts/components/session/SessionSettings');
const { SessionToast } = require('../../ts/components/session/SessionToast');
const { SessionToggle } = require('../../ts/components/session/SessionToggle');
@ -251,6 +252,7 @@ exports.setup = (options = {}) => {
CreateGroupDialog,
EditProfileDialog,
UserDetailsDialog,
DevicePairingDialog,
SessionRegistrationView,
ConfirmDialog,
UpdateGroupDialog,

@ -5,7 +5,6 @@
textsecure,
ConversationController,
$,
QRCode,
*/
// eslint-disable-next-line func-names
@ -16,194 +15,30 @@
Whisper.DevicePairingDialogView = Whisper.View.extend({
className: 'loki-dialog device-pairing-dialog modal',
templateName: 'device-pairing-dialog',
initialize() {
this.pubKeyRequests = [];
this.reset();
this.close = this.close.bind(this);
this.render();
this.showView();
this.qr = new QRCode(this.$('#qr')[0], {
correctLevel: QRCode.CorrectLevel.L,
});
this.qr.makeCode(textsecure.storage.user.getNumber());
},
reset() {
this.pubKey = null;
this.accepted = false;
this.isListening = false;
this.pubKeyToUnpair = null;
this.success = false;
},
events: {
'click #startPairing': 'startReceivingRequests',
'click #close': 'close',
'click .waitingForRequestView .cancel': 'stopReceivingRequests',
'click .requestReceivedView .skip': 'skipDevice',
'click #allowPairing': 'allowDevice',
'click .requestAcceptedView .ok': 'stopReceivingRequests',
'click .confirmUnpairView .cancel': 'stopReceivingRequests',
'click .confirmUnpairView .unpairDevice': 'confirmUnpairDevice',
},
render_attributes() {
return {
defaultTitle: i18n('pairedDevices'),
waitingForRequestTitle: i18n('waitingForDeviceToRegister'),
requestReceivedTitle: i18n('devicePairingReceived'),
requestAcceptedTitle: i18n('devicePairingAccepted'),
startPairingText: i18n('pairNewDevice'),
cancelText: i18n('cancel'),
unpairDevice: i18n('unpairDevice'),
closeText: i18n('close'),
skipText: i18n('skip'),
okText: i18n('ok'),
allowPairingText: i18n('allowPairing'),
confirmUnpairViewTitle: i18n('confirmUnpairingTitle'),
};
},
startReceivingRequests() {
this.trigger('startReceivingRequests');
this.isListening = true;
this.showView();
},
stopReceivingRequests() {
if (this.success) {
const deviceAlias = this.$('#deviceAlias')[0].value.trim();
const conv = ConversationController.get(this.pubKey);
if (conv) {
conv.setNickname(deviceAlias);
}
}
this.trigger('stopReceivingRequests');
this.reset();
this.showView();
},
requestReceived(secondaryDevicePubKey) {
// FIFO: push at the front of the array with unshift()
this.pubKeyRequests.unshift(secondaryDevicePubKey);
if (!this.pubKey) {
this.nextPubKey();
this.showView('requestReceived');
}
},
allowDevice() {
this.accepted = true;
this.trigger('devicePairingRequestAccepted', this.pubKey, errors =>
this.transmisssionCB(errors)
);
this.showView();
},
transmisssionCB(errors) {
if (!errors) {
this.$('.transmissionStatus').text(i18n('provideDeviceAlias'));
this.$('#deviceAliasView').show();
this.$('#deviceAlias').on('input', e => {
if (e.target.value.trim()) {
this.$('.requestAcceptedView .ok').removeAttr('disabled');
} else {
this.$('.requestAcceptedView .ok').attr('disabled', true);
}
});
this.$('.requestAcceptedView .ok').show();
this.$('.requestAcceptedView .ok').attr('disabled', true);
this.success = true;
} else {
this.$('.transmissionStatus').text(errors);
this.$('.requestAcceptedView .ok').show();
}
},
skipDevice() {
this.trigger('devicePairingRequestRejected', this.pubKey);
this.nextPubKey();
this.showView();
},
nextPubKey() {
// FIFO: pop at the back of the array using pop()
this.pubKey = this.pubKeyRequests.pop();
},
async confirmUnpairDevice() {
this.trigger('deviceUnpairingRequested', this.pubKeyToUnpair);
this.reset();
this.showView();
},
requestUnpairDevice(pubKey) {
this.pubKeyToUnpair = pubKey;
this.showView();
},
getPubkeyName(pubKey) {
const secretWords = window.mnemonic.pubkey_to_secret_words(pubKey);
const conv = ConversationController.get(pubKey);
const deviceAlias = conv ? conv.getNickname() : 'Unnamed Device';
return `${deviceAlias} (pairing secret: <i>${secretWords}</i>)`;
},
async showView() {
const defaultView = this.$('.defaultView');
const waitingForRequestView = this.$('.waitingForRequestView');
const requestReceivedView = this.$('.requestReceivedView');
const requestAcceptedView = this.$('.requestAcceptedView');
const confirmUnpairView = this.$('.confirmUnpairView');
if (this.pubKeyToUnpair) {
defaultView.hide();
requestReceivedView.hide();
waitingForRequestView.hide();
requestAcceptedView.hide();
confirmUnpairView.show();
const name = this.getPubkeyName(this.pubKeyToUnpair);
this.$('.confirmUnpairView #pubkey').html(name);
} else if (!this.isListening) {
requestReceivedView.hide();
waitingForRequestView.hide();
requestAcceptedView.hide();
confirmUnpairView.hide();
const ourPubKey = textsecure.storage.user.getNumber();
defaultView.show();
const pubKeys = await libloki.storage.getSecondaryDevicesFor(ourPubKey);
this.$('#pairedPubKeys').empty();
if (pubKeys && pubKeys.length > 0) {
this.$('#startPairing').attr('disabled', true);
pubKeys.forEach(x => {
const name = this.getPubkeyName(x);
const li = $('<li>').html(name);
if (window.lokiFeatureFlags.multiDeviceUnpairing) {
const link = $('<a>')
.text('Unpair')
.attr('href', '#');
link.on('click', () => this.requestUnpairDevice(x));
li.append(' - ');
li.append(link);
}
this.$('#pairedPubKeys').append(li);
});
} else {
this.$('#startPairing').removeAttr('disabled');
this.$('#pairedPubKeys').append('<li>No paired devices</li>');
}
} else if (this.accepted) {
defaultView.hide();
requestReceivedView.hide();
waitingForRequestView.hide();
requestAcceptedView.show();
} else if (this.pubKey) {
const secretWords = window.mnemonic.pubkey_to_secret_words(this.pubKey);
this.$('.secretWords').text(secretWords);
requestReceivedView.show();
waitingForRequestView.hide();
requestAcceptedView.hide();
defaultView.hide();
} else {
waitingForRequestView.show();
requestReceivedView.hide();
requestAcceptedView.hide();
defaultView.hide();
}
render(){
this.dialogView = new Whisper.ReactWrapperView({
className: 'device-pairing-dialog',
Component: window.Signal.Components.DevicePairingDialog,
props: {
i18n,
onClose: this.close,
},
});
this.$el.append(this.dialogView.el);
return this;
},
close() {
this.remove();
this.qr.clear();
if (this.pubKey && !this.accepted) {
this.trigger('devicePairingRequestRejected', this.pubKey);
}
this.trigger('close');
},
});
})();

@ -0,0 +1,209 @@
/* global
Whisper,
i18n,
libloki,
textsecure,
ConversationController,
$,
QRCode,
*/
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.DevicePairingDialogView = Whisper.View.extend({
className: 'loki-dialog device-pairing-dialog modal',
templateName: 'device-pairing-dialog',
initialize() {
this.pubKeyRequests = [];
this.reset();
this.render();
this.showView();
this.qr = new QRCode(this.$('#qr')[0], {
correctLevel: QRCode.CorrectLevel.L,
});
this.qr.makeCode(textsecure.storage.user.getNumber());
},
reset() {
this.pubKey = null;
this.accepted = false;
this.isListening = false;
this.pubKeyToUnpair = null;
this.success = false;
},
events: {
'click #startPairing': 'startReceivingRequests',
'click #close': 'close',
'click .waitingForRequestView .cancel': 'stopReceivingRequests',
'click .requestReceivedView .skip': 'skipDevice',
'click #allowPairing': 'allowDevice',
'click .requestAcceptedView .ok': 'stopReceivingRequests',
'click .confirmUnpairView .cancel': 'stopReceivingRequests',
'click .confirmUnpairView .unpairDevice': 'confirmUnpairDevice',
},
render_attributes() {
return {
defaultTitle: i18n('pairedDevices'),
waitingForRequestTitle: i18n('waitingForDeviceToRegister'),
requestReceivedTitle: i18n('devicePairingReceived'),
requestAcceptedTitle: i18n('devicePairingAccepted'),
startPairingText: i18n('pairNewDevice'),
cancelText: i18n('cancel'),
unpairDevice: i18n('unpairDevice'),
closeText: i18n('close'),
skipText: i18n('skip'),
okText: i18n('ok'),
allowPairingText: i18n('allowPairing'),
confirmUnpairViewTitle: i18n('confirmUnpairingTitle'),
};
},
startReceivingRequests() {
this.trigger('startReceivingRequests');
this.isListening = true;
this.showView();
},
stopReceivingRequests() {
if (this.success) {
const deviceAlias = this.$('#deviceAlias')[0].value.trim();
const conv = ConversationController.get(this.pubKey);
if (conv) {
conv.setNickname(deviceAlias);
}
}
this.trigger('stopReceivingRequests');
this.reset();
this.showView();
},
requestReceived(secondaryDevicePubKey) {
// FIFO: push at the front of the array with unshift()
this.pubKeyRequests.unshift(secondaryDevicePubKey);
if (!this.pubKey) {
this.nextPubKey();
this.showView('requestReceived');
}
},
allowDevice() {
this.accepted = true;
this.trigger('devicePairingRequestAccepted', this.pubKey, errors =>
this.transmisssionCB(errors)
);
this.showView();
},
transmisssionCB(errors) {
if (!errors) {
this.$('.transmissionStatus').text(i18n('provideDeviceAlias'));
this.$('#deviceAliasView').show();
this.$('#deviceAlias').on('input', e => {
if (e.target.value.trim()) {
this.$('.requestAcceptedView .ok').removeAttr('disabled');
} else {
this.$('.requestAcceptedView .ok').attr('disabled', true);
}
});
this.$('.requestAcceptedView .ok').show();
this.$('.requestAcceptedView .ok').attr('disabled', true);
this.success = true;
} else {
this.$('.transmissionStatus').text(errors);
this.$('.requestAcceptedView .ok').show();
}
},
skipDevice() {
this.trigger('devicePairingRequestRejected', this.pubKey);
this.nextPubKey();
this.showView();
},
nextPubKey() {
// FIFO: pop at the back of the array using pop()
this.pubKey = this.pubKeyRequests.pop();
},
async confirmUnpairDevice() {
this.trigger('deviceUnpairingRequested', this.pubKeyToUnpair);
this.reset();
this.showView();
},
requestUnpairDevice(pubKey) {
this.pubKeyToUnpair = pubKey;
this.showView();
},
getPubkeyName(pubKey) {
const secretWords = window.mnemonic.pubkey_to_secret_words(pubKey);
const conv = ConversationController.get(pubKey);
const deviceAlias = conv ? conv.getNickname() : 'Unnamed Device';
return `${deviceAlias} (pairing secret: <i>${secretWords}</i>)`;
},
async showView() {
const defaultView = this.$('.defaultView');
const waitingForRequestView = this.$('.waitingForRequestView');
const requestReceivedView = this.$('.requestReceivedView');
const requestAcceptedView = this.$('.requestAcceptedView');
const confirmUnpairView = this.$('.confirmUnpairView');
if (this.pubKeyToUnpair) {
defaultView.hide();
requestReceivedView.hide();
waitingForRequestView.hide();
requestAcceptedView.hide();
confirmUnpairView.show();
const name = this.getPubkeyName(this.pubKeyToUnpair);
this.$('.confirmUnpairView #pubkey').html(name);
} else if (!this.isListening) {
requestReceivedView.hide();
waitingForRequestView.hide();
requestAcceptedView.hide();
confirmUnpairView.hide();
const ourPubKey = textsecure.storage.user.getNumber();
defaultView.show();
const pubKeys = await libloki.storage.getSecondaryDevicesFor(ourPubKey);
this.$('#pairedPubKeys').empty();
if (pubKeys && pubKeys.length > 0) {
this.$('#startPairing').attr('disabled', true);
pubKeys.forEach(x => {
const name = this.getPubkeyName(x);
const li = $('<li>').html(name);
if (window.lokiFeatureFlags.multiDeviceUnpairing) {
const link = $('<a>')
.text('Unpair')
.attr('href', '#');
link.on('click', () => this.requestUnpairDevice(x));
li.append(' - ');
li.append(link);
}
this.$('#pairedPubKeys').append(li);
});
} else {
this.$('#startPairing').removeAttr('disabled');
this.$('#pairedPubKeys').append('<li>No paired devices</li>');
}
} else if (this.accepted) {
defaultView.hide();
requestReceivedView.hide();
waitingForRequestView.hide();
requestAcceptedView.show();
} else if (this.pubKey) {
const secretWords = window.mnemonic.pubkey_to_secret_words(this.pubKey);
this.$('.secretWords').text(secretWords);
requestReceivedView.show();
waitingForRequestView.hide();
requestAcceptedView.hide();
defaultView.hide();
} else {
waitingForRequestView.show();
requestReceivedView.hide();
requestAcceptedView.hide();
defaultView.hide();
}
},
close() {
this.remove();
this.qr.clear();
if (this.pubKey && !this.accepted) {
this.trigger('devicePairingRequestRejected', this.pubKey);
}
this.trigger('close');
},
});
})();

@ -101,10 +101,12 @@
"pify": "3.0.0",
"protobufjs": "6.8.6",
"proxy-agent": "3.0.3",
"qrcode": "^1.4.4",
"react": "16.8.3",
"react-contextmenu": "2.11.0",
"react-dom": "16.8.3",
"react-portal": "^4.2.0",
"react-qrcode": "^0.2.0",
"react-redux": "6.0.1",
"react-virtualized": "9.21.0",
"read-last-lines": "1.3.0",

@ -74,6 +74,7 @@ $session-color-light-grey: #a0a0a0;
$session-shadow-opacity: 0.15;
$session-overlay-opacity: 0.3;
$session-margin-xs: 5px;
$session-margin-sm: 10px;
$session-margin-md: 15px;
$session-margin-lg: 20px;
@ -92,6 +93,10 @@ div.spacer-lg {
color: rgba($color, 0.6);
}
.text-subtle {
opacity: 0.6;
}
$session-transition-duration: 0.25s;
$session-icon-size-sm: 15px;
@ -560,14 +565,23 @@ label {
&__button-group {
display: flex;
justify-content: flex-end;
.session-button {
margin-left: $session-margin-sm;
}
&__center {
align-items: center;
display: flex;
justify-content: center;
}
.session-button {
margin-left: $session-margin-sm;
margin: 0 $session-margin-xs;
}
}
}
.session-toggle {

@ -0,0 +1,165 @@
import React from 'react';
import { QRCode } from 'react-qrcode'
import { SessionModal } from './session/SessionModal';
import { SessionButton } from './session/SessionButton';
interface Props {
i18n: any,
onClose: any,
pubKeyToUnpair: string | null;
pubKey: string | null;
}
interface State {
accepted: boolean;
isListening: boolean;
success: boolean;
loading: boolean;
data: Array<any>;
}
export class DevicePairingDialog extends React.Component<Props, State> {
constructor(props: any) {
super(props);
this.closeDialog = this.closeDialog.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.startReceivingRequests = this.startReceivingRequests.bind(this);
this.stopReceivingRequests = this.stopReceivingRequests.bind(this);
this.getPubkeyName = this.getPubkeyName.bind(this);
this.state = {
accepted: false,
isListening: false,
success: false,
loading: true,
data: [],
};
}
componentDidMount() {
this.getSecondaryDevices();
}
private async getSecondaryDevices(){
const secondaryDevices = await window.libloki.storage.getSecondaryDevicesFor(this.props.pubKey);
this.setState({
data: secondaryDevices,
loading: false
});
}
public render() {
const {i18n, } = this.props;
const newData = ['053e18835c106a5f9f463a44a9d7ff9a26281d529285a047bd969cfc59d4ab8607'];
setTimeout(() => {
this.setState({
data: newData,
});
}, 2000);
return (
<>
{ ! this.state.loading ? (
<SessionModal
title={i18n('pairedDevices')}
onOk={() => null}
onClose={this.closeDialog}
>
{ this.state.isListening ? (
<div>
{i18n('waitingForDeviceToRegister')}
<div className="spacer-lg"></div>
<div id="qr">
<QRCode value={window.textsecure.storage.user.getNumber()}/>
</div>
</div>
)
: (
<>
{this.state.data.length == 0 ? (
<>
<div>{i18n('noPairedDevices')}</div>
<div className="spacer-lg"></div>
<div className="session-modal__button-group__center">
<SessionButton
text = {i18n('pairNewDevice')}
onClick = {this.startReceivingRequests}
/>
</div>
</>
)
: (
<>
{
this.state.data.map((pubKey: any) => {
const pubKeyInfo = this.getPubkeyName(pubKey);
return (
<p>
{ pubKeyInfo.deviceAlias }
<br/>
<span className="text-subtle">Pairing Secret:</span> { pubKeyInfo.secretWords }
</p>
);
})
}
</>
)}
</>
)}
</SessionModal>
) : null }
</>
);
}
private startReceivingRequests() {
this.setState({
isListening: true,
});
}
private getPubkeyName(pubKey: string) {
const secretWords = window.mnemonic.pubkey_to_secret_words(pubKey);
const conv = window.ConversationController.get(this.props.pubKey);
const deviceAlias = conv ? conv.getNickname() : 'Unnamed Device';
return {deviceAlias, secretWords};
}
private stopReceivingRequests() {
if (this.state.success) {
const conv = window.ConversationController.get(this.props.pubKey);
//if (conv) {
// conv.setNickname(this.props.deviceAlias);
//}
}
this.forceUpdate();
}
private onKeyUp(event: any) {
switch (event.key) {
case 'Esc':
case 'Escape':
this.closeDialog();
break;
default:
}
}
private closeDialog() {
window.removeEventListener('keyup', this.onKeyUp);
this.props.onClose();
}
}

1
ts/global.d.ts vendored

@ -4,6 +4,7 @@ interface Window {
passwordUtil: any;
dcodeIO: any;
libsignal: any;
libloki: any;
displayNameRegex: any;
Signal: any;
Whisper: any;

@ -1243,6 +1243,19 @@ buble@^0.19.3:
os-homedir "^1.0.1"
vlq "^1.0.0"
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
buffer-alloc@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
dependencies:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
buffer-crc32@0.2.13, buffer-crc32@^0.2.1:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
@ -1252,10 +1265,20 @@ buffer-equal@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b"
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
buffer-from@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
buffer-from@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
buffer-indexof@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
@ -1272,6 +1295,14 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.4.3:
version "5.4.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115"
integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==
dependencies:
base64-js "^1.0.2"
ieee754 "^1.1.4"
buffers@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
@ -2522,6 +2553,11 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
dijkstrajs@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.1.tgz#d3cd81221e3ea40742cfcde556d4e99e98ddc71b"
integrity sha1-082BIh4+pAdCz83lVtTpnpjdxxs=
dir-glob@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034"
@ -4982,6 +5018,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
isarray@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isbinaryfile@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.2.tgz#bfc45642da645681c610cca831022e30af426488"
@ -6981,6 +7022,11 @@ pngjs@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.0.1.tgz#b15086ac1ac47298c8fd3f9cdf364fa9879c4db6"
pngjs@^3.3.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
portfinder@^1.0.9:
version "1.0.13"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
@ -7474,6 +7520,19 @@ q@^1.1.2, q@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
qrcode@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83"
integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==
dependencies:
buffer "^5.4.3"
buffer-alloc "^1.2.0"
buffer-from "^1.1.1"
dijkstrajs "^1.0.1"
isarray "^2.0.1"
pngjs "^3.3.0"
yargs "^13.2.4"
qs@5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.0.tgz#a9f31142af468cb72b25b30136ba2456834916be"
@ -7699,6 +7758,11 @@ react-portal@^4.2.0:
dependencies:
prop-types "^15.5.8"
react-qrcode@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/react-qrcode/-/react-qrcode-0.2.0.tgz#a05cf2ae5ac57c3a9751e512132a821ed60533f9"
integrity sha512-3JzSzkTUUMb26sbq5/u75zw9l3gQ1BLvdCAYgRnAZ1wGJj1Su94pzv4g/XfzaJEj6h8Y0H9mYX4djmKBGZQHSQ==
react-redux@6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-6.0.1.tgz#0d423e2c1cb10ada87293d47e7de7c329623ba4d"
@ -10374,7 +10438,7 @@ yargs@^10.0.3:
y18n "^3.2.1"
yargs-parser "^8.0.0"
yargs@^13.3.0:
yargs@^13.2.4, yargs@^13.3.0:
version "13.3.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"
integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==

Loading…
Cancel
Save