working encrypt/decrypt of attachments

pull/1554/head
Audric Ackermann 4 years ago
parent 5c6c5c2b8c
commit def03c8baa
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -11,13 +11,12 @@
media-src 'self' blob:;
object-src 'none';
script-src 'self';
style-src 'self' 'unsafe-inline';"
>
style-src 'self' 'unsafe-inline';">
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
<style>
body {
text-align: center;
background-color: rgba(0,0,0);
background-color: rgba(0, 0, 0);
color: white;
font-size: 14px;
}
@ -32,6 +31,7 @@
}
</style>
</head>
<body>
<img src='images/session/session_icon_64.png' width='250' height='250'>
@ -49,4 +49,5 @@
<script type='text/javascript' src='node_modules/jquery/dist/jquery.js'></script>
<script type='text/javascript' src='js/about_start.js'></script>
</body>
</html>
</html>

@ -109,6 +109,7 @@
"react-qr-svg": "^2.2.1",
"react-redux": "7.2.1",
"react-toastify": "^6.0.9",
"react-use": "^17.2.1",
"react-virtualized": "9.21.0",
"read-last-lines": "1.3.0",
"redux": "4.0.1",
@ -212,7 +213,7 @@
"tslint": "5.19.0",
"tslint-microsoft-contrib": "6.0.0",
"tslint-react": "3.6.0",
"typescript": "3.7.4",
"typescript": "^4.2.3",
"webpack": "4.4.1"
},
"engines": {
@ -349,4 +350,4 @@
"*/**/*.{css,js,json,scss,ts,tsx}": "prettier --write",
"*.js": "eslint --cache --fix"
}
}
}

@ -1,8 +1,7 @@
// tslint:disable:react-a11y-anchors
import React from 'react';
import React, { useEffect, useRef } from 'react';
import classNames from 'classnames';
import is from '@sindresorhus/is';
import * as GoogleChrome from '../util/GoogleChrome';
@ -13,7 +12,12 @@ import {
SessionIconType,
} from './session/icon';
import { Flex } from './session/Flex';
import { DefaultTheme } from 'styled-components';
import { DefaultTheme, useTheme } from 'styled-components';
// useCss has some issues on our setup. so import it directly
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch';
import { darkTheme } from '../state/ducks/SessionTheme';
const Colors = {
TEXT_SECONDARY: '#bbb',
@ -28,7 +32,7 @@ const colorSVG = (url: string, color: string) => {
};
};
interface Props {
type Props = {
close: () => void;
contentType: MIME.MIMEType | undefined;
objectURL: string;
@ -36,8 +40,7 @@ interface Props {
onNext?: () => void;
onPrevious?: () => void;
onSave?: () => void;
theme: DefaultTheme;
}
};
const CONTROLS_WIDTH = 50;
const CONTROLS_SPACING = 10;
@ -135,7 +138,7 @@ interface IconButtonProps {
theme: DefaultTheme;
}
const IconButton = ({ onClick, style, type, theme }: IconButtonProps) => {
const IconButton = ({ onClick, type, theme }: IconButtonProps) => {
const clickHandler = (event: React.MouseEvent<HTMLAnchorElement>): void => {
if (!onClick) {
return;
@ -200,38 +203,73 @@ const Icon = ({
/>
);
export class Lightbox extends React.Component<Props> {
private readonly containerRef: React.RefObject<HTMLDivElement>;
private readonly videoRef: React.RefObject<HTMLVideoElement>;
private readonly playVideoBound: () => void;
export const LightboxObject = ({
objectURL,
contentType,
videoRef,
onObjectClick,
playVideo,
}: {
objectURL: string;
contentType: MIME.MIMEType;
videoRef: React.MutableRefObject<any>;
onObjectClick: (event: any) => any;
playVideo: () => void;
}) => {
const { urlToLoad } = useEncryptedFileFetch(objectURL, contentType);
const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType);
if (isImageTypeSupported) {
return <img alt={window.i18n('lightboxImageAlt')} src={urlToLoad} />;
}
constructor(props: Props) {
super(props);
const isVideoTypeSupported = GoogleChrome.isVideoTypeSupported(contentType);
if (isVideoTypeSupported) {
return (
<video
role="button"
ref={videoRef}
onClick={playVideo}
controls={true}
style={styles.object}
key={objectURL}
>
<source src={objectURL} />
</video>
);
}
this.playVideoBound = this.playVideo.bind(this);
const isUnsupportedImageType =
!isImageTypeSupported && MIME.isImage(contentType);
const isUnsupportedVideoType =
!isVideoTypeSupported && MIME.isVideo(contentType);
if (isUnsupportedImageType || isUnsupportedVideoType) {
const iconUrl = isUnsupportedVideoType
? 'images/video.svg'
: 'images/image.svg';
this.videoRef = React.createRef();
this.containerRef = React.createRef();
return <Icon url={iconUrl} onClick={onObjectClick} />;
}
public componentDidMount() {
const useCapture = true;
document.addEventListener('keyup', this.onKeyUp, useCapture);
// tslint:disable-next-line no-console
console.log('Lightbox: Unexpected content type', { contentType });
this.playVideo();
}
return <Icon onClick={onObjectClick} url="images/file.svg" />;
};
public componentWillUnmount() {
const useCapture = true;
document.removeEventListener('keyup', this.onKeyUp, useCapture);
}
export const Lightbox = (props: Props) => {
const videoRef = useRef<any>(null);
const containerRef = useRef<HTMLDivElement>(null);
// there is no theme in use on the lightbox
const theme = darkTheme;
const { caption, contentType, objectURL, onNext, onPrevious, onSave } = props;
public playVideo() {
if (!this.videoRef) {
const playVideo = () => {
if (!videoRef) {
return;
}
const { current } = this.videoRef;
const { current } = videoRef;
if (!current) {
return;
}
@ -241,170 +279,81 @@ export class Lightbox extends React.Component<Props> {
} else {
current.pause();
}
}
};
const onObjectClick = (event: any) => {
event.stopPropagation();
props.close?.();
};
public render() {
const {
caption,
contentType,
objectURL,
onNext,
onPrevious,
onSave,
} = this.props;
const onContainerClick = (event: React.MouseEvent<HTMLDivElement>) => {
if (containerRef && event.target !== containerRef.current) {
return;
}
props.close?.();
};
return (
<div
style={styles.container}
onClick={this.onContainerClick}
ref={this.containerRef}
role="dialog"
>
<div style={styles.mainContainer}>
<div style={styles.controlsOffsetPlaceholder} />
<div style={styles.objectParentContainer}>
<div style={styles.objectContainer}>
{!is.undefined(contentType)
? this.renderObject({ objectURL, contentType })
: null}
{caption ? <div style={styles.caption}>{caption}</div> : null}
</div>
</div>
<div style={styles.controls}>
<Flex flex="1 1 auto">
<IconButton
type="close"
onClick={this.onClose}
theme={this.props.theme}
/>
</Flex>
// auto play video on showing a video attachment
useEffect(() => {
playVideo();
}, []);
{onSave ? (
<IconButton
type="save"
onClick={onSave}
style={styles.saveButton}
theme={this.props.theme}
return (
<div
style={styles.container}
onClick={onContainerClick}
ref={containerRef}
role="dialog"
>
<div style={styles.mainContainer}>
<div style={styles.controlsOffsetPlaceholder} />
<div style={styles.objectParentContainer}>
<div style={styles.objectContainer}>
{!is.undefined(contentType) ? (
<LightboxObject
objectURL={objectURL}
contentType={contentType}
videoRef={videoRef}
onObjectClick={onObjectClick}
playVideo={playVideo}
/>
) : null}
{caption ? <div style={styles.caption}>{caption}</div> : null}
</div>
</div>
<div style={styles.navigationContainer}>
{onPrevious ? (
<div style={styles.controls}>
<Flex flex="1 1 auto">
<IconButton
type="close"
onClick={() => {
props.close?.();
}}
theme={theme}
/>
</Flex>
{onSave ? (
<IconButton
type="previous"
onClick={onPrevious}
theme={this.props.theme}
type="save"
onClick={onSave}
style={styles.saveButton}
theme={theme}
/>
) : (
<IconButtonPlaceholder />
)}
{onNext ? (
<IconButton type="next" onClick={onNext} theme={this.props.theme} />
) : (
<IconButtonPlaceholder />
)}
) : null}
</div>
</div>
);
}
private readonly renderObject = ({
objectURL,
contentType,
}: {
objectURL: string;
contentType: MIME.MIMEType;
}) => {
const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType);
if (isImageTypeSupported) {
return (
<img
alt={window.i18n('lightboxImageAlt')}
style={styles.object}
src={objectURL}
onClick={this.onObjectClick}
/>
);
}
const isVideoTypeSupported = GoogleChrome.isVideoTypeSupported(contentType);
if (isVideoTypeSupported) {
return (
<video
role="button"
ref={this.videoRef}
onClick={this.playVideoBound}
controls={true}
style={styles.object}
key={objectURL}
>
<source src={objectURL} />
</video>
);
}
const isUnsupportedImageType =
!isImageTypeSupported && MIME.isImage(contentType);
const isUnsupportedVideoType =
!isVideoTypeSupported && MIME.isVideo(contentType);
if (isUnsupportedImageType || isUnsupportedVideoType) {
const iconUrl = isUnsupportedVideoType
? 'images/video.svg'
: 'images/image.svg';
return <Icon url={iconUrl} onClick={this.onObjectClick} />;
}
// tslint:disable-next-line no-console
console.log('Lightbox: Unexpected content type', { contentType });
return <Icon onClick={this.onObjectClick} url="images/file.svg" />;
};
private readonly onClose = () => {
const { close } = this.props;
if (!close) {
return;
}
close();
};
private readonly onKeyUp = (event: KeyboardEvent) => {
const { onNext, onPrevious } = this.props;
switch (event.key) {
case 'Escape':
this.onClose();
break;
case 'ArrowLeft':
if (onPrevious) {
onPrevious();
}
break;
case 'ArrowRight':
if (onNext) {
onNext();
}
break;
default:
}
};
private readonly onContainerClick = (
event: React.MouseEvent<HTMLDivElement>
) => {
if (this.containerRef && event.target !== this.containerRef.current) {
return;
}
this.onClose();
};
private readonly onObjectClick = (event: any) => {
event.stopPropagation();
this.onClose();
};
}
<div style={styles.navigationContainer}>
{onPrevious ? (
<IconButton type="previous" onClick={onPrevious} theme={theme} />
) : (
<IconButtonPlaceholder />
)}
{onNext ? (
<IconButton type="next" onClick={onNext} theme={theme} />
) : (
<IconButtonPlaceholder />
)}
</div>
</div>
);
};

@ -1,25 +1,25 @@
/**
* @prettier
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import * as MIME from '../types/MIME';
import { Lightbox } from './Lightbox';
import { Message } from './conversation/media-gallery/types/Message';
import { AttachmentType } from '../types/Attachment';
import { darkTheme } from '../state/ducks/SessionTheme';
import useKey from 'react-use/lib/useKey';
export interface MediaItemType {
objectURL?: string;
thumbnailObjectUrl?: string;
contentType?: MIME.MIMEType;
contentType: MIME.MIMEType;
index: number;
attachment: AttachmentType;
message: Message;
}
interface Props {
type Props = {
close: () => void;
media: Array<MediaItemType>;
onSave?: (options: {
@ -28,83 +28,83 @@ interface Props {
index: number;
}) => void;
selectedIndex: number;
}
interface State {
selectedIndex: number;
}
export class LightboxGallery extends React.Component<Props, State> {
public static defaultProps: Partial<Props> = {
selectedIndex: 0,
};
constructor(props: Props) {
super(props);
this.state = {
selectedIndex: this.props.selectedIndex,
};
}
public render() {
const { close, media, onSave } = this.props;
const { selectedIndex } = this.state;
const selectedMedia = media[selectedIndex];
const firstIndex = 0;
const lastIndex = media.length - 1;
const onPrevious =
selectedIndex > firstIndex ? this.handlePrevious : undefined;
const onNext = selectedIndex < lastIndex ? this.handleNext : undefined;
const objectURL = selectedMedia.objectURL || 'images/alert-outline.svg';
const { attachment } = selectedMedia;
const saveCallback = onSave ? this.handleSave : undefined;
const captionCallback = attachment ? attachment.caption : undefined;
return (
<Lightbox
close={close}
onPrevious={onPrevious}
onNext={onNext}
onSave={saveCallback}
objectURL={objectURL}
caption={captionCallback}
contentType={selectedMedia.contentType}
// there is no theme in use on the lightbox
theme={darkTheme}
/>
);
}
private readonly handlePrevious = () => {
this.setState(prevState => ({
selectedIndex: Math.max(prevState.selectedIndex - 1, 0),
}));
};
private readonly handleNext = () => {
this.setState((prevState, props) => ({
selectedIndex: Math.min(
prevState.selectedIndex + 1,
props.media.length - 1
),
}));
};
private readonly handleSave = () => {
const { media, onSave } = this.props;
};
export const LightboxGallery = (props: Props) => {
const { close, media, onSave } = props;
const [currentIndex, setCurrentIndex] = useState(0);
// just run once, when the component is mounted. It's to show the lightbox on the specified index at start.
useEffect(() => {
setCurrentIndex(props.selectedIndex);
}, []);
const selectedMedia = media[currentIndex];
const firstIndex = 0;
const lastIndex = media.length - 1;
const onPrevious =
currentIndex > firstIndex
? () => {
setCurrentIndex(Math.max(currentIndex - 1, 0));
}
: undefined;
const onNext =
currentIndex < lastIndex
? () => {
setCurrentIndex(Math.min(currentIndex + 1, lastIndex));
}
: undefined;
const handleSave = () => {
if (!onSave) {
return;
}
const { selectedIndex } = this.state;
const mediaItem = media[selectedIndex];
const { attachment, message, index } = mediaItem;
const mediaItem = media[currentIndex];
onSave({ attachment, message, index });
onSave({
attachment: mediaItem.attachment,
message: mediaItem.message,
index: mediaItem.index,
});
};
}
const objectURL = selectedMedia.objectURL || 'images/alert-outline.svg';
const { attachment } = selectedMedia;
const saveCallback = onSave ? handleSave : undefined;
const captionCallback = attachment ? attachment.caption : undefined;
useKey(
'ArrowRight',
() => {
onNext?.();
},
undefined,
[currentIndex]
);
useKey(
'ArrowLeft',
() => {
onPrevious?.();
},
undefined,
[currentIndex]
);
useKey('Escape', () => {
props.close?.();
});
return (
<Lightbox
close={close}
onPrevious={onPrevious}
onNext={onNext}
onSave={saveCallback}
objectURL={objectURL}
caption={captionCallback}
contentType={selectedMedia.contentType}
/>
);
};

@ -64,7 +64,7 @@ export class UserDetailsDialog extends React.Component<Props, State> {
private renderAvatar() {
const { avatarPath, pubkey, profileName } = this.props;
const size = this.state.isEnlargedImageShown ? 300 : 80;
const userName = name || profileName || pubkey;
const userName = profileName || pubkey;
return (
<Avatar

@ -70,7 +70,7 @@ export class AttachmentList extends React.Component<Props> {
<Image
key={imageKey}
alt={window.i18n('stagedImageAttachment', [
getUrl(attachment) || attachment.fileName,
attachment.fileName,
])}
i18n={window.i18n}
attachment={attachment}

@ -5,8 +5,6 @@ import { Spinner } from '../Spinner';
import { LocalizerType } from '../../types/Util';
import { AttachmentType } from '../../types/Attachment';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import { fromArrayBufferToBase64 } from '../../session/utils/String';
import toArrayBuffer from 'to-arraybuffer';
type Props = {
alt: string;
@ -66,13 +64,12 @@ export const Image = (props: Props) => {
const canClick = onClick && !pending;
const role = canClick ? 'button' : undefined;
const { loading, data } = useEncryptedFileFetch(url);
//FIXME jpg is hardcoded
const srcData = !loading
? data?.length
? `data:image/jpg;base64,${fromArrayBufferToBase64(toArrayBuffer(data))}`
: url
: '';
const { loading, urlToLoad } = useEncryptedFileFetch(
url,
attachment.contentType
);
// data will be url if loading is finished and '' if not
const srcData = !loading ? urlToLoad : '';
return (
<div

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import classNames from 'classnames';
import {
@ -7,115 +7,105 @@ import {
} from '../../../util/GoogleChrome';
import { LocalizerType } from '../../../types/Util';
import { MediaItemType } from '../../LightboxGallery';
import { useEncryptedFileFetch } from '../../../hooks/useEncryptedFileFetch';
interface Props {
type Props = {
mediaItem: MediaItemType;
onClick?: () => void;
i18n: LocalizerType;
}
};
interface State {
imageBroken: boolean;
}
const MediaGridItemContent = (props: Props) => {
const { mediaItem, i18n } = props;
const { attachment, contentType } = mediaItem;
export class MediaGridItem extends React.Component<Props, State> {
private readonly onImageErrorBound: () => void;
const urlToDecrypt = mediaItem.thumbnailObjectUrl || '';
const [imageBroken, setImageBroken] = useState(false);
constructor(props: Props) {
super(props);
const { loading, urlToLoad } = useEncryptedFileFetch(
urlToDecrypt,
contentType
);
// data will be url if loading is finished and '' if not
const srcData = !loading ? urlToLoad : '';
this.state = {
imageBroken: false,
};
this.onImageErrorBound = this.onImageError.bind(this);
}
public onImageError() {
const onImageError = () => {
// tslint:disable-next-line no-console
console.log(
'MediaGridItem: Image failed to load; failing over to placeholder'
);
this.setState({
imageBroken: true,
});
}
public renderContent() {
const { mediaItem, i18n } = this.props;
const { imageBroken } = this.state;
const { attachment, contentType } = mediaItem;
if (!attachment) {
return null;
}
setImageBroken(true);
};
if (contentType && isImageTypeSupported(contentType)) {
if (imageBroken || !mediaItem.thumbnailObjectUrl) {
return (
<div
className={classNames(
'module-media-grid-item__icon',
'module-media-grid-item__icon-image'
)}
/>
);
}
if (!attachment) {
return null;
}
if (contentType && isImageTypeSupported(contentType)) {
if (imageBroken || !srcData) {
return (
<img
alt={i18n('lightboxImageAlt')}
className="module-media-grid-item__image"
src={mediaItem.thumbnailObjectUrl}
onError={this.onImageErrorBound}
<div
className={classNames(
'module-media-grid-item__icon',
'module-media-grid-item__icon-image'
)}
/>
);
} else if (contentType && isVideoTypeSupported(contentType)) {
if (imageBroken || !mediaItem.thumbnailObjectUrl) {
return (
<div
className={classNames(
'module-media-grid-item__icon',
'module-media-grid-item__icon-video'
)}
/>
);
}
return (
<div className="module-media-grid-item__image-container">
<img
alt={i18n('lightboxImageAlt')}
className="module-media-grid-item__image"
src={mediaItem.thumbnailObjectUrl}
onError={this.onImageErrorBound}
/>
<div className="module-media-grid-item__circle-overlay">
<div className="module-media-grid-item__play-overlay" />
</div>
</div>
);
}
return (
<div
className={classNames(
'module-media-grid-item__icon',
'module-media-grid-item__icon-generic'
)}
<img
alt={i18n('lightboxImageAlt')}
className="module-media-grid-item__image"
src={srcData}
onError={onImageError}
/>
);
}
} else if (contentType && isVideoTypeSupported(contentType)) {
if (imageBroken || !srcData) {
return (
<div
className={classNames(
'module-media-grid-item__icon',
'module-media-grid-item__icon-video'
)}
/>
);
}
public render() {
return (
<div
className="module-media-grid-item"
role="button"
onClick={this.props.onClick}
>
{this.renderContent()}
<div className="module-media-grid-item__image-container">
<img
alt={i18n('lightboxImageAlt')}
className="module-media-grid-item__image"
src={srcData}
onError={onImageError}
/>
<div className="module-media-grid-item__circle-overlay">
<div className="module-media-grid-item__play-overlay" />
</div>
</div>
);
}
}
return (
<div
className={classNames(
'module-media-grid-item__icon',
'module-media-grid-item__icon-generic'
)}
/>
);
};
export const MediaGridItem = (props: Props) => {
return (
<div
className="module-media-grid-item"
role="button"
onClick={props.onClick}
>
<MediaGridItemContent {...props} />
</div>
);
};

@ -159,8 +159,8 @@ class SessionRightPanel extends React.Component<Props, State> {
),
thumbnailObjectUrl: thumbnail
? window.Signal.Migrations.getAbsoluteAttachmentPath(
thumbnail.path
)
thumbnail.path
)
: null,
contentType: attachment.contentType,
index,
@ -195,12 +195,13 @@ class SessionRightPanel extends React.Component<Props, State> {
const saveAttachment = ({ attachment, message }: any = {}) => {
const timestamp = message.received_at;
save({
attachment,
document,
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
timestamp,
});
attachment.url =
save({
attachment,
document,
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
timestamp,
});
};
const onItemClick = ({ message, attachment, type }: any) => {
@ -258,10 +259,10 @@ class SessionRightPanel extends React.Component<Props, State> {
const leaveGroupString = isPublic
? window.i18n('leaveGroup')
: isKickedFromGroup
? window.i18n('youGotKickedFromGroup')
: left
? window.i18n('youLeftTheGroup')
: window.i18n('leaveGroup');
? window.i18n('youGotKickedFromGroup')
: left
? window.i18n('youLeftTheGroup')
: window.i18n('leaveGroup');
const disappearingMessagesOptions = timerOptions.map(option => {
return {

@ -3,27 +3,57 @@ import toArrayBuffer from 'to-arraybuffer';
import * as fse from 'fs-extra';
import { decryptAttachmentBuffer } from '../types/Attachment';
export const useEncryptedFileFetch = (url: string) => {
const urlToDecryptedBlobMap = new Map<string, string>();
export const useEncryptedFileFetch = (url: string, contentType: string) => {
// tslint:disable-next-line: no-bitwise
const [data, setData] = useState(new Uint8Array());
const [urlToLoad, setUrlToLoad] = useState('');
const [loading, setLoading] = useState(true);
async function fetchUrl() {
// this is a file encoded by session
//FIXME find another way to know if the file in encrypted or not
// maybe rely on
if (url.includes('/attachments.noindex/')) {
const encryptedFileContent = await fse.readFile(url);
const decryptedContent = await decryptAttachmentBuffer(
toArrayBuffer(encryptedFileContent)
);
setData(decryptedContent);
if (url.startsWith('blob:')) {
setUrlToLoad(url);
} else if (
window.Signal.Migrations.attachmentsPath &&
url.startsWith(window.Signal.Migrations.attachmentsPath)
) {
// this is a file encoded by session on our current attachments path.
// we consider the file is encrypted.
// if it's not, the hook caller has to fallback to setting the img src as an url to the file instead and load it
if (urlToDecryptedBlobMap.has(url)) {
// typescript does not realize that the has above makes sure the get is not undefined
setUrlToLoad(urlToDecryptedBlobMap.get(url) as string);
} else {
const encryptedFileContent = await fse.readFile(url);
const decryptedContent = await decryptAttachmentBuffer(
toArrayBuffer(encryptedFileContent)
);
if (decryptedContent?.length) {
const arrayBuffer = decryptedContent.buffer;
const { makeObjectUrl } = window.Signal.Types.VisualAttachment;
const obj = makeObjectUrl(arrayBuffer, contentType);
if (!urlToDecryptedBlobMap.has(url)) {
urlToDecryptedBlobMap.set(url, obj);
}
setUrlToLoad(obj);
} else {
// failed to decrypt, fallback to url image loading
setUrlToLoad(url);
}
}
} else {
// already a blob.
setUrlToLoad(url);
}
setLoading(false);
}
useEffect(() => {
void fetchUrl();
}, [url]);
return { data, loading };
return { urlToLoad, loading };
};

@ -0,0 +1,5 @@
export const getDecryptedUrl

@ -4,7 +4,7 @@ type Job<ResultType> = (() => PromiseLike<ResultType>) | (() => ResultType);
// TODO: This needs to replace js/modules/job_queue.js
export class JobQueue {
private pending: Promise<any> = Promise.resolve();
private pending?: Promise<any> = Promise.resolve();
private readonly jobs: Map<string, Promise<unknown>> = new Map();
public has(id: string): boolean {
@ -36,7 +36,7 @@ export class JobQueue {
})
.finally(() => {
if (this.pending === current) {
delete this.pending;
delete this?.pending;
}
this.jobs.delete(id);
});

@ -40,3 +40,4 @@ export const fromBase64ToArray = (d: string) =>
new Uint8Array(encode(d, 'base64'));
export const fromArrayBufferToBase64 = (d: BufferType) => decode(d, 'base64');
export const fromUInt8ArrayToBase64 = (d: Uint8Array) => decode(d, 'base64');

@ -21,8 +21,10 @@ const toMediaItem = (date: Date): MediaItemType => ({
contentType: IMAGE_JPEG,
url: 'url',
},
contentType: IMAGE_JPEG,
});
// tslint:disable: max-func-body-length
describe('groupMediaItemsByDate', () => {
it('should group mediaItems', () => {
const referenceTime = new Date('2018-04-12T18:00Z').getTime(); // Thu
@ -51,6 +53,8 @@ describe('groupMediaItemsByDate', () => {
{
objectURL: 'Thu, 12 Apr 2018 12:00:00 GMT',
index: 0,
contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1523534400000,
@ -65,6 +69,7 @@ describe('groupMediaItemsByDate', () => {
{
objectURL: 'Thu, 12 Apr 2018 00:01:00 GMT',
index: 0,
contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1523491260000,
@ -84,6 +89,7 @@ describe('groupMediaItemsByDate', () => {
{
objectURL: 'Wed, 11 Apr 2018 23:59:00 GMT',
index: 0,
contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1523491140000,
@ -103,6 +109,7 @@ describe('groupMediaItemsByDate', () => {
{
objectURL: 'Mon, 09 Apr 2018 00:01:00 GMT',
index: 0,
contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1523232060000,
@ -122,6 +129,7 @@ describe('groupMediaItemsByDate', () => {
{
objectURL: 'Sun, 08 Apr 2018 23:59:00 GMT',
index: 0,
contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1523231940000,
@ -141,6 +149,7 @@ describe('groupMediaItemsByDate', () => {
received_at: 1522540860000,
attachments: [],
},
contentType: IMAGE_JPEG,
attachment: {
fileName: 'fileName',
contentType: IMAGE_JPEG,
@ -157,6 +166,7 @@ describe('groupMediaItemsByDate', () => {
{
objectURL: 'Sat, 31 Mar 2018 23:59:00 GMT',
index: 0,
contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1522540740000,
@ -171,6 +181,7 @@ describe('groupMediaItemsByDate', () => {
{
objectURL: 'Thu, 01 Mar 2018 14:00:00 GMT',
index: 0,
contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1519912800000,
@ -202,10 +213,12 @@ describe('groupMediaItemsByDate', () => {
contentType: IMAGE_JPEG,
url: 'url',
},
contentType: IMAGE_JPEG,
},
{
objectURL: 'Tue, 01 Feb 2011 10:00:00 GMT',
index: 0,
contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1296554400000,

@ -444,7 +444,6 @@ export const encryptAttachmentBuffer = async (bufferIn: ArrayBuffer) => {
state,
header,
} = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
console.warn('header', toHex(header));
/* Now, encrypt the buffer. */
const bufferOut = sodium.crypto_secretstream_xchacha20poly1305_push(
state,
@ -458,7 +457,6 @@ export const encryptAttachmentBuffer = async (bufferIn: ArrayBuffer) => {
);
encryptedBufferWithHeader.set(header);
encryptedBufferWithHeader.set(bufferOut, header.length);
console.warn('bufferOut', toHex(encryptedBufferWithHeader));
console.timeEnd(`timer #*. encryptAttachmentBuffer ${ourIndex}`);
return { encryptedBufferWithHeader, header, key };
@ -469,7 +467,7 @@ let indexDecrypt = 0;
export const decryptAttachmentBuffer = async (
bufferIn: ArrayBuffer,
key: string = '0c5f7147b6d3239cbb5a418814cee1bfca2df5c94bffddf22ee37eea3ede972b'
) => {
): Promise<Uint8Array> => {
if (!isArrayBuffer(bufferIn)) {
throw new TypeError("'bufferIn' must be an array buffer");
}
@ -484,19 +482,30 @@ export const decryptAttachmentBuffer = async (
const encryptedBuffer = new Uint8Array(
bufferIn.slice(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES)
);
try {
/* Decrypt the stream: initializes the state, using the key and a header */
const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(
header,
fromHexToArray(key)
);
// what if ^ this call fail (? try to load as a unencrypted attachment?)
const messageTag = sodium.crypto_secretstream_xchacha20poly1305_pull(
state,
encryptedBuffer
);
console.timeEnd(`timer .*# decryptAttachmentBuffer ${ourIndex}`);
// we expect the final tag to be there. If not, we might have an issue with this file
// maybe not encrypted locally?
if (
messageTag.tag === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
) {
return messageTag.message;
}
} catch (e) {
console.timeEnd(`timer .*# decryptAttachmentBuffer ${ourIndex}`);
/* Decrypt the stream: initializes the state, using the key and a header */
const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(
header,
fromHexToArray(key)
);
// what if ^ this call fail (? try to load as a unencrypted attachment?)
const messageTag = sodium.crypto_secretstream_xchacha20poly1305_pull(
state,
encryptedBuffer
);
console.timeEnd(`timer .*# decryptAttachmentBuffer ${ourIndex}`);
return messageTag.message;
window.log.warn('Failed to load the file as an encrypted one', e);
}
return new Uint8Array();
};

@ -514,6 +514,11 @@
dependencies:
"@types/sizzle" "*"
"@types/js-cookie@^2.2.6":
version "2.2.6"
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f"
integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw==
"@types/json-schema@^7.0.4":
version "7.0.5"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
@ -792,6 +797,11 @@
dependencies:
common-tags "^1.7.2"
"@xobotyi/scrollbar-width@^1.9.5":
version "1.9.5"
resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d"
integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==
"@yarnpkg/lockfile@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
@ -2709,6 +2719,13 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
copy-to-clipboard@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
dependencies:
toggle-selection "^1.0.6"
copy-webpack-plugin@^4.5.1:
version "4.6.0"
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz#e7f40dd8a68477d405dd1b7a854aae324b158bae"
@ -2887,6 +2904,14 @@ css-color-names@0.0.4:
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=
css-in-js-utils@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz#3b472b398787291b47cfe3e44fecfdd9e914ba99"
integrity sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==
dependencies:
hyphenate-style-name "^1.0.2"
isobject "^3.0.1"
css-initials@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/css-initials/-/css-initials-0.2.0.tgz#14c225bd8656255a6baee07231ef82fa55aacaa3"
@ -2956,6 +2981,14 @@ css-to-react-native@^3.0.0:
css-color-keywords "^1.0.0"
postcss-value-parser "^4.0.2"
css-tree@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.2.tgz#9ae393b5dafd7dae8a622475caec78d3d8fbd7b5"
integrity sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==
dependencies:
mdn-data "2.0.14"
source-map "^0.6.1"
css-value@~0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/css-value/-/css-value-0.0.1.tgz#5efd6c2eea5ea1fd6b6ac57ec0427b18452424ea"
@ -3032,6 +3065,11 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8"
integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==
csstype@^3.0.6:
version "3.0.7"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b"
integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==
cuint@^0.2.1:
version "0.2.2"
resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"
@ -3735,6 +3773,13 @@ error-ex@^1.2.0, error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
error-stack-parser@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8"
integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==
dependencies:
stackframe "^1.1.1"
es-abstract@^1.17.0, es-abstract@^1.17.0-next.1:
version "1.17.4"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
@ -4227,6 +4272,11 @@ fast-deep-equal@^3.1.1:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@ -4237,6 +4287,16 @@ fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-shallow-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b"
integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==
fastest-stable-stringify@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76"
integrity sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==
fastparse@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9"
@ -5591,6 +5651,13 @@ ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
inline-style-prefixer@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-6.0.0.tgz#f73d5dbf2855733d6b153a4d24b7b47a73e9770b"
integrity sha512-XTHvRUS4ZJNzC1GixJRmOlWSS45fSt+DJoyQC9ytj0WxQfcgofQtDtyKKYxHUqEsWCs+LIWftPF1ie7+i012Fg==
dependencies:
css-in-js-utils "^2.0.0"
inquirer@3.3.0, inquirer@^3.0.6, inquirer@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
@ -6176,6 +6243,11 @@ js-base64@^2.1.8, js-base64@^2.1.9:
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.2.tgz#313b6274dda718f714d00b3330bbae6e38e90209"
integrity sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -6932,6 +7004,11 @@ mdast-util-compact@^1.0.0:
dependencies:
unist-util-visit "^1.1.0"
mdn-data@2.0.14:
version "2.0.14"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@ -7332,6 +7409,20 @@ nan@^2.10.0, nan@^2.12.1, nan@^2.14.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
nano-css@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.1.tgz#b709383e07ad3be61f64edffacb9d98250b87a1f"
integrity sha512-ENPIyNzANQRyYVvb62ajDd7PAyIgS2LIUnT9ewih4yrXSZX4hKoUwssy8WjUH++kEOA5wUTMgNnV7ko5n34kUA==
dependencies:
css-tree "^1.1.2"
csstype "^3.0.6"
fastest-stable-stringify "^2.0.2"
inline-style-prefixer "^6.0.0"
rtl-css-js "^1.14.0"
sourcemap-codec "^1.4.8"
stacktrace-js "^2.0.2"
stylis "^4.0.6"
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@ -9352,6 +9443,31 @@ react-transition-group@^4.4.1:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-universal-interface@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b"
integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==
react-use@^17.2.1:
version "17.2.1"
resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.2.1.tgz#c81e12544115ed049c7deba1e3bb3d977dfee9b8"
integrity sha512-9r51/at7/Nr/nEP4CsHz+pl800EAqhIY9R6O68m68kaWc8slDAfx1UrIedQqpsb4ImddFYb+6hF1i5Vj4u4Cnw==
dependencies:
"@types/js-cookie" "^2.2.6"
"@xobotyi/scrollbar-width" "^1.9.5"
copy-to-clipboard "^3.3.1"
fast-deep-equal "^3.1.3"
fast-shallow-equal "^1.0.0"
js-cookie "^2.2.1"
nano-css "^5.3.1"
react-universal-interface "^0.6.2"
resize-observer-polyfill "^1.5.1"
screenfull "^5.1.0"
set-harmonic-interval "^1.0.1"
throttle-debounce "^3.0.1"
ts-easing "^0.2.0"
tslib "^2.1.0"
react-virtualized@9.21.0:
version "9.21.0"
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.0.tgz#8267c40ffb48db35b242a36dea85edcf280a6506"
@ -9824,6 +9940,11 @@ reselect@4.0.0, reselect@^4.0.0:
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@ -9967,6 +10088,13 @@ roarr@^2.15.2:
semver-compare "^1.0.0"
sprintf-js "^1.1.2"
rtl-css-js@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.14.0.tgz#daa4f192a92509e292a0519f4b255e6e3c076b7d"
integrity sha512-Dl5xDTeN3e7scU1cWX8c9b6/Nqz3u/HgR4gePc1kWXYiQWVQbKCEyK6+Hxve9LbcJ5EieHy1J9nJCN3grTtGwg==
dependencies:
"@babel/runtime" "^7.1.2"
run-async@^2.2.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8"
@ -10079,6 +10207,11 @@ schema-utils@^2.7.0:
ajv "^6.12.2"
ajv-keywords "^3.4.1"
screenfull@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.1.0.tgz#85c13c70f4ead4c1b8a935c70010dfdcd2c0e5c8"
integrity sha512-dYaNuOdzr+kc6J6CFcBrzkLCfyGcMg+gWkJ8us93IQ7y1cevhQAugFsaCdMHb6lw8KV3xPzSxzH7zM1dQap9mA==
scss-tokenizer@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
@ -10195,6 +10328,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
set-harmonic-interval@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249"
integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
@ -10430,6 +10568,11 @@ source-map-url@^0.4.0:
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
source-map@0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
integrity sha1-dc449SvwczxafwwRjYEzSiu19BI=
source-map@^0.4.2:
version "0.4.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
@ -10447,7 +10590,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sourcemap-codec@^1.4.4:
sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
@ -10581,6 +10724,35 @@ ssri@^5.2.4:
dependencies:
safe-buffer "^5.1.1"
stack-generator@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36"
integrity sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==
dependencies:
stackframe "^1.1.1"
stackframe@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303"
integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==
stacktrace-gps@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz#7688dc2fc09ffb3a13165ebe0dbcaf41bcf0c69a"
integrity sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==
dependencies:
source-map "0.5.6"
stackframe "^1.1.1"
stacktrace-js@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b"
integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==
dependencies:
error-stack-parser "^2.0.6"
stack-generator "^2.0.5"
stacktrace-gps "^3.0.4"
stat-mode@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-1.0.0.tgz#68b55cb61ea639ff57136f36b216a291800d1465"
@ -10856,6 +11028,11 @@ styled-components@5.1.1:
shallowequal "^1.1.0"
supports-color "^5.5.0"
stylis@^4.0.6:
version "4.0.8"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.8.tgz#b03cc47dcedcd2dbac93d8224e687c43ceda4e20"
integrity sha512-WCHD2YHu2gp4GN9M8TqD7DZljL/UC5mIFaKyYJRuRyPdnqkTqzTnxCIQ1Z3VgQvz1aPcua5bSS2h0HrcbDUdBg==
substyle@^9.1.0:
version "9.3.0"
resolved "https://registry.yarnpkg.com/substyle/-/substyle-9.3.0.tgz#569af81723f74cd895b08b6b1e6bc06727f2a2bd"
@ -11066,6 +11243,11 @@ thenify-all@^1.0.0, thenify-all@^1.6.0:
dependencies:
any-promise "^1.0.0"
throttle-debounce@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb"
integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==
throttleit@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf"
@ -11210,6 +11392,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
toggle-selection@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
@ -11281,6 +11468,11 @@ truncate-utf8-bytes@^1.0.0:
dependencies:
utf8-byte-length "^1.0.1"
ts-easing@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec"
integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==
ts-loader@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.1.0.tgz#6216e75600941df3270bc4a7125e20aefb2dc5ea"
@ -11307,6 +11499,11 @@ tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
tslint-microsoft-contrib@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/tslint-microsoft-contrib/-/tslint-microsoft-contrib-6.0.0.tgz#7bff73c9ad7a0b7eb5cdb04906de58f42a2bf7a2"
@ -11418,10 +11615,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@3.7.4:
version "3.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19"
integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==
typescript@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
typings-for-css-modules-loader@^1.7.0:
version "1.7.0"

Loading…
Cancel
Save