Modal additions incl. QR fixup and Seed View

pull/714/head
Vincent 5 years ago
parent 2caffa9289
commit 2ede557a74

@ -2102,16 +2102,26 @@
},
"showSeed": {
"message": "Show seed",
"message": "Show Seed",
"description":
"Button action that the user can click to view their unique seed"
},
"showSeedPasswordRequest": {
"message": "Please enter your password",
"description": "Request for user to enter password to show seed."
},
"seedSavePrompt": {
"message":
"Please save the seed below in a safe location. They can be used to restore your account if you lose access or migrate to a new device.",
"description": "Prompt on seed modal requesting user to save their seed"
},
"QRCodeTitle": {
"message": "Your Public Key QRCode",
"description": "Title given to QRCode modal"
},
"QRCodeDescription": {
"message": "Your friends can scan this QR code to start a conversation with you.",
"message":
"Your friends can scan this QR code to start a conversation with you.",
"description": "Description given to QRCode modal"
},
"showQRCode": {
@ -2168,7 +2178,10 @@
"description":
"A button action that the user can click to reset the database"
},
"password": {
"message": "Password",
"description": "Placeholder for password input"
},
"setPassword": {
"message": "Set Password",
"description": "Button action that the user can click to set a password"
@ -2190,6 +2203,9 @@
"invalidPassword": {
"message": "Invalid password"
},
"noGivenPassword": {
"message": "Please enter your password"
},
"passwordsDoNotMatch": {
"message": "Passwords do not match"
},

@ -61,7 +61,12 @@ const {
const { SessionToast } = require('../../ts/components/session/SessionToast');
const { SessionToggle } = require('../../ts/components/session/SessionToggle');
const { SessionModal } = require('../../ts/components/session/SessionModal');
const { SessionQRModal } = require('../../ts/components/session/SessionQRModal');
const {
SessionQRModal,
} = require('../../ts/components/session/SessionQRModal');
const {
SessionSeedModal,
} = require('../../ts/components/session/SessionSeedModal');
const {
SessionConfirm,
} = require('../../ts/components/session/SessionConfirm');
@ -275,6 +280,7 @@ exports.setup = (options = {}) => {
SessionConfirm,
SessionModal,
SessionQRModal,
SessionSeedModal,
SessionDropdown,
MediaGallery,
Message,

@ -11,16 +11,17 @@
initialize(options) {
this.value = options.value || '';
this.close = this.close.bind(this);
this.onKeyup = this.onKeyup.bind(this);
this.render();
},
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'qr-dialog',
className: 'qr-dialog-wrapper',
Component: window.Signal.Components.SessionQRModal,
props: {
value: this.value,
onClose: this.close,
},
});
@ -31,16 +32,5 @@
close() {
this.remove();
},
onKeyup(event) {
switch (event.key) {
case 'Enter':
case 'Escape':
case 'Esc':
this.close();
break;
default:
break;
}
},
});
})();

@ -1,4 +1,4 @@
/* global Whisper, i18n, Signal, passwordUtil */
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
@ -8,90 +8,26 @@
Whisper.SeedDialogView = Whisper.View.extend({
className: 'loki-dialog seed-dialog modal',
templateName: 'seed-dialog',
initialize(options = {}) {
this.okText = options.okText || i18n('ok');
this.cancelText = options.cancelText || i18n('cancel');
this.confirmText = options.confirmText || i18n('confirm');
this.copyText = options.copyText || i18n('copySeed');
this.seed = options.seed || '-';
initialize() {
this.close = this.close.bind(this);
this.render();
this.showSeedView(false);
this.initPasswordHash();
this.$('#password').bind('keyup', event => this.onKeyup(event));
},
events: {
'click .ok': 'close',
'click .confirm': 'confirmPassword',
'click .copy-seed': 'copySeed',
},
render_attributes() {
return {
passwordViewTitle: i18n('passwordViewTitle'),
seedViewTitle: i18n('seedViewTitle'),
ok: this.okText,
copyText: this.copyText,
confirm: this.confirmText,
cancel: this.cancelText,
};
},
async initPasswordHash() {
const hash = await Signal.Data.getPasswordHash();
this.passwordHash = hash;
this.showSeedView(!hash);
},
showSeedView(show) {
const seedView = this.$('.seedView');
const passwordView = this.$('.passwordView');
if (show) {
this.$('.seed').html(this.seed);
seedView.show();
passwordView.hide();
} else {
this.$('.seed').html('');
passwordView.show();
this.$('#password').focus();
seedView.hide();
}
},
confirmPassword() {
this.$('.error').html();
const password = this.$('#password').val();
if (
this.passwordHash &&
!passwordUtil.matchesHash(password, this.passwordHash)
) {
this.$('.error').html(`Error: ${i18n('invalidPassword')}`);
return;
}
this.showSeedView(true);
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'seed-dialog-wrapper',
Component: window.Signal.Components.SessionSeedModal,
props: {
onClose: this.close,
},
});
this.$el.append(this.dialogView.el);
return this;
},
close() {
this.remove();
},
copySeed() {
window.clipboard.writeText(this.seed);
window.pushToast({
title: i18n('copiedMnemonic'),
type: 'success',
id: 'copySeedToast',
});
},
onKeyup(event) {
switch (event.key) {
case 'Enter':
this.confirmPassword();
break;
case 'Escape':
case 'Esc':
this.close();
break;
default:
break;
}
},
});
})();

@ -1,32 +0,0 @@
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.SessionDropdownView = Whisper.View.extend({
initialize(options) {
this.props = {
items: options.items,
};
this.render();
},
render() {
this.dropdownView = new Whisper.ReactWrapperView({
className: 'session-dropdown-wrapper',
Component: window.Signal.Components.SessionDropdown,
props: this.props,
});
this.$el.append(this.dropdownView.el);
},
openDropdown() {},
closeDropdown() {},
});
})();

@ -101,7 +101,6 @@
"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",

@ -105,6 +105,13 @@ div.spacer-lg {
text-align: center;
}
@mixin text-highlight($color) {
background-color: rgba($color, 0.8);
padding: $session-margin-xs;
border-radius: 3px;
display: inline-block;
}
.fullwidth {
width: 100%;
}
@ -115,7 +122,9 @@ $session-icon-size-sm: 15px;
$session-icon-size-md: 20px;
$session-icon-size-lg: 30px;
$session-modal-size: 220px;
$session-modal-size-sm: 220px;
$session-modal-size-md: 400px;
$session-modal-size-lg: 650px;
$session-conversation-header-height: 60px;
@ -470,7 +479,7 @@ label {
right: $session-margin-lg;
bottom: $session-margin-lg;
z-index: 100;
z-index: 10000;
}
.session-toast {
@ -552,6 +561,7 @@ label {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
min-width: 300px;
box-sizing: border-box;
max-height: 70vh;
@ -622,6 +632,10 @@ label {
margin: 0 $session-margin-xs;
}
}
&__text-highlight {
@include text-highlight($session-shade-15);
}
}
.session-toggle {
@ -803,16 +817,22 @@ label {
}
#qr svg {
width: $session-modal-size;
height: $session-modal-size;
width: $session-modal-size-sm;
height: $session-modal-size-sm;
border: 6px solid white;
border-radius: 3px;
}
.qr-dialog {
&__description {
max-width: $session-modal-size;
max-width: $session-modal-size-sm;
text-align: center;
margin: 0 auto;
}
}
}
.seed-dialog {
&__description {
max-width: $session-modal-size-lg;
}
}

@ -49,7 +49,7 @@ export class DevicePairingDialog extends React.Component<Props, State> {
};
}
componentDidMount() {
public componentDidMount() {
this.getSecondaryDevices();
}
@ -98,7 +98,7 @@ export class DevicePairingDialog extends React.Component<Props, State> {
value={window.textsecure.storage.user.getNumber()}
bgColor="#FFFFFF"
fgColor="#000000"
level="L"
level="L"
/>
</div>
@ -207,58 +207,58 @@ export class DevicePairingDialog extends React.Component<Props, State> {
this.showView();
}
private requestReceived(secondaryDevicePubKey: string | EventHandlerNonNull) {
// FIFO: push at the front of the array with unshift()
this.state.pubKeyRequests.unshift(secondaryDevicePubKey);
if (!this.state.currentPubKey) {
this.nextPubKey();
this.showView('requestReceived');
}
}
private allowDevice() {
this.setState({
accepted: true,
});
window.Whisper.trigger(
'devicePairingRequestAccepted',
this.state.currentPubKey,
(errors: any) => {
this.transmisssionCB(errors);
return true;
}
);
this.showView();
}
private transmisssionCB(errors: any) {
if (!errors) {
this.setState({
success: true,
});
} else {
return;
}
}
private skipDevice() {
window.Whisper.trigger(
'devicePairingRequestRejected',
this.state.currentPubKey
);
this.nextPubKey();
this.showView();
}
private nextPubKey() {
// FIFO: pop at the back of the array using pop()
const pubKeyRequests = this.state.pubKeyRequests;
this.setState({
currentPubKey: pubKeyRequests.pop(),
});
}
// private requestReceived(secondaryDevicePubKey: string | EventHandlerNonNull) {
// // FIFO: push at the front of the array with unshift()
// this.state.pubKeyRequests.unshift(secondaryDevicePubKey);
// if (!this.state.currentPubKey) {
// this.nextPubKey();
// this.showView('requestReceived');
// }
// }
// private allowDevice() {
// this.setState({
// accepted: true,
// });
// window.Whisper.trigger(
// 'devicePairingRequestAccepted',
// this.state.currentPubKey,
// (errors: any) => {
// this.transmisssionCB(errors);
// return true;
// }
// );
// this.showView();
// }
// private transmisssionCB(errors: any) {
// if (!errors) {
// this.setState({
// success: true,
// });
// } else {
// return;
// }
// }
// private skipDevice() {
// window.Whisper.trigger(
// 'devicePairingRequestRejected',
// this.state.currentPubKey
// );
// this.nextPubKey();
// this.showView();
// }
// private nextPubKey() {
// // FIFO: pop at the back of the array using pop()
// const pubKeyRequests = this.state.pubKeyRequests;
// this.setState({
// currentPubKey: pubKeyRequests.pop(),
// });
// }
private onKeyUp(event: any) {
switch (event.key) {

@ -3,12 +3,7 @@ import React from 'react';
import { ContactName } from './ContactName';
import { Avatar } from '../Avatar';
import { Colors, LocalizerType } from '../../types/Util';
import {
ContextMenu,
ContextMenuTrigger,
MenuItem,
SubMenu,
} from 'react-contextmenu';
import { ContextMenu, MenuItem, SubMenu } from 'react-contextmenu';
import {
SessionIconButton,
@ -22,8 +17,6 @@ import {
SessionButtonType,
} from '../session/SessionButton';
import { SessionDropdownTrigger } from '../session/SessionDropdownTrigger';
interface TimerOption {
name: string;
value: number;
@ -239,7 +232,7 @@ export class ConversationHeader extends React.Component<Props> {
);
}
public renderOptions(triggerId: string) {
public renderOptions() {
const { showBackButton } = this.props;
if (showBackButton) {
@ -247,12 +240,12 @@ export class ConversationHeader extends React.Component<Props> {
}
return (
<SessionDropdownTrigger>
<>
<SessionIconButton
iconType={SessionIconType.Ellipses}
iconSize={SessionIconSize.Large}
/>
</SessionDropdownTrigger>
</>
);
}

@ -84,7 +84,10 @@ export class SessionModal extends React.PureComponent<Props, State> {
});
window.removeEventListener('keyup', this.onKeyUp);
this.props.onClose();
if (this.props.onClose) {
this.props.onClose();
}
}
public onKeyUp(event: any) {

@ -2,9 +2,11 @@ import React from 'react';
import { QRCode } from 'react-qr-svg';
import { SessionModal } from './SessionModal';
import { SessionButton } from './SessionButton';
interface Props {
value: string;
onClose: any;
}
export class SessionQRModal extends React.Component<Props> {
@ -13,37 +15,30 @@ export class SessionQRModal extends React.Component<Props> {
}
public render() {
const { value } = this.props;
console.log('skbsvbsgb');
console.log('skbsvbsgb');
console.log('skbsvbsgb');
const { value, onClose } = this.props;
return (
<SessionModal
title={window.i18n('QRCodeTitle')}
onOk={() => null}
onClose={() => null}
onClose={onClose}
>
<div className="spacer-sm"></div>
<div className='qr-dialog__description text-subtle'>
<p>
{window.i18n('QRCodeDescription')}
</p>
<div className="spacer-sm" />
<div className="qr-dialog__description text-subtle">
<p>{window.i18n('QRCodeDescription')}</p>
</div>
<div className="spacer-lg"></div>
<div className="spacer-lg" />
<div id="qr">
<QRCode
value={value}
bgColor="#FFFFFF"
fgColor="#000000"
level="L"
/>
<QRCode value={value} bgColor="#FFFFFF" fgColor="#000000" level="L" />
</div>
<div className="spacer-lg" />
<div className="session-modal__button-group">
<SessionButton text={window.i18n('close')} onClick={onClose} />
</div>
</SessionModal>
);
}
}
);
}
}

@ -0,0 +1,198 @@
import React from 'react';
import { SessionModal } from './SessionModal';
import { SessionButton } from './SessionButton';
interface Props {
onClose: any;
}
interface State {
error: string;
loadingPassword: boolean;
loadingSeed: boolean;
seed: string;
hasPassword: boolean | null;
passwordHash: string;
passwordValid: boolean;
}
export class SessionSeedModal extends React.Component<Props, State> {
constructor(props: any) {
super(props);
this.state = {
error: '',
loadingPassword: true,
loadingSeed: true,
seed: '',
hasPassword: null,
passwordHash: '',
passwordValid: false,
};
this.copySeed = this.copySeed.bind(this);
this.getSeed = this.getSeed.bind(this);
this.confirmPassword = this.confirmPassword.bind(this);
this.checkHasPassword = this.checkHasPassword.bind(this);
}
public render() {
const i18n = window.i18n;
const { onClose } = this.props;
const maxPasswordLen = 64;
this.checkHasPassword();
this.getSeed();
const error = this.state.error;
const hasPassword = this.state.hasPassword;
const passwordValid = this.state.passwordValid;
const loading = this.state.loadingPassword || this.state.loadingSeed;
return (
<SessionModal
title={i18n('showSeed')}
onOk={() => null}
onClose={onClose}
>
{!loading && (
<>
<div className="spacer-sm" />
{hasPassword && !passwordValid ? (
<div>
<p>{i18n('showSeedPasswordRequest')}</p>
<input
type="password"
id="seed-input-password"
placeholder={i18n('password')}
maxLength={maxPasswordLen}
/>
{error && (
<>
<div className="spacer-xs" />
<div className="session-label danger">{error}</div>
</>
)}
<div className="spacer-lg" />
<div className="session-modal__button-group">
<SessionButton
text={i18n('confirm')}
onClick={this.confirmPassword}
/>
<SessionButton text={i18n('cancel')} onClick={onClose} />
</div>
</div>
) : (
<>
<div className="session-modal__centered text-center">
<p className="session-modal__description">
{i18n('seedSavePrompt')}
</p>
<div className="spacer-md" />
<i className="session-modal__text-highlight">
{this.state.seed}
</i>
</div>
<div className="spacer-lg" />
<div className="session-modal__button-group">
<SessionButton text={i18n('ok')} onClick={onClose} />
<SessionButton
text={i18n('copySeed')}
onClick={() => {
this.copySeed(this.state.seed);
}}
/>
</div>
</>
)}
</>
)}
</SessionModal>
);
}
private confirmPassword() {
const passwordHash = this.state.passwordHash;
const passwordValue = $('#seed-input-password').val();
const isPasswordValid = window.passwordUtil.matchesHash(
passwordValue,
passwordHash
);
if (!passwordValue) {
this.setState({
error: window.i18n('noGivenPassword'),
});
return false;
}
if (passwordHash && !isPasswordValid) {
this.setState({
error: window.i18n('invalidPassword'),
});
return false;
}
this.setState({
passwordValid: true,
error: '',
});
return true;
}
private checkHasPassword() {
if (!this.state.loadingPassword) {
return;
}
const hashPromise = window.Signal.Data.getPasswordHash();
hashPromise.then((hash: any) => {
this.setState({
hasPassword: !!hash,
passwordHash: hash,
loadingPassword: false,
});
});
}
private async getSeed() {
if (this.state.seed) {
return this.state.seed;
}
const manager = await window.getAccountManager();
const seed = manager.getCurrentMnemonic();
this.setState({
seed,
loadingSeed: false,
});
return seed;
}
private copySeed(seed: string) {
window.clipboard.writeText(seed);
window.pushToast({
title: window.i18n('copiedMnemonic'),
type: 'success',
id: 'copySeedToast',
});
}
}

1
ts/global.d.ts vendored

@ -1,6 +1,7 @@
interface Window {
getAccountManager: any;
mnemonic: any;
clipboard: any;
passwordUtil: any;
dcodeIO: any;
libsignal: any;

@ -139,9 +139,9 @@
integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
"@types/dompurify@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.0.1.tgz#0bf3a9f8ee21d81adb20b8c374ab034d6a74dbf7"
integrity sha512-OQ16dECrRv/I//woKkVUxyVGYR94W3qp3Wy//B63awHVe3h/1/URFqP5a/V2m4k01DEvWs1+z7FWW3xfM1lH3Q==
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.0.0.tgz#9616caa5bf2569aea2e4889d4f929d968c081b40"
integrity sha512-g/ilp+Bo6Ljy60i5LnjkGw00X7EIoFjoPGlxqZhV8TJ9fWEzXheioU1O+U/UzCzUA7pUDy/JNMytTQDJctpUHg==
dependencies:
"@types/trusted-types" "*"
@ -1243,19 +1243,6 @@ 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"
@ -1265,20 +1252,10 @@ 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"
@ -1295,14 +1272,6 @@ 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"
@ -2553,11 +2522,6 @@ 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"
@ -5018,11 +4982,6 @@ 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"
@ -7022,11 +6981,6 @@ 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"
@ -7525,19 +7479,6 @@ qr.js@0.0.0:
resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=
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"
@ -10446,7 +10387,7 @@ yargs@^10.0.3:
y18n "^3.2.1"
yargs-parser "^8.0.0"
yargs@^13.2.4, yargs@^13.3.0:
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