From def03c8baafe5cf90caeebf822b4c3af671c56be Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 26 Mar 2021 13:07:42 +1100 Subject: [PATCH] working encrypt/decrypt of attachments --- about.html | 9 +- package.json | 5 +- ts/components/Lightbox.tsx | 317 ++++++++---------- ts/components/LightboxGallery.tsx | 156 ++++----- ts/components/UserDetailsDialog.tsx | 2 +- ts/components/conversation/AttachmentList.tsx | 2 +- ts/components/conversation/Image.tsx | 15 +- .../media-gallery/MediaGridItem.tsx | 162 +++++---- .../conversation/SessionRightPanel.tsx | 25 +- ts/hooks/useEncryptedFileFetch.ts | 54 ++- .../crypto/DecryptedAttachmentsManager.ts | 5 + ts/session/utils/JobQueue.ts | 4 +- ts/session/utils/String.ts | 1 + .../media-gallery/groupMessagesByDate_test.ts | 13 + ts/types/Attachment.ts | 43 ++- yarn.lock | 207 +++++++++++- 16 files changed, 607 insertions(+), 413 deletions(-) create mode 100644 ts/session/crypto/DecryptedAttachmentsManager.ts diff --git a/about.html b/about.html index 3a5da534b..2ede5a05f 100644 --- a/about.html +++ b/about.html @@ -11,13 +11,12 @@ media-src 'self' blob:; object-src 'none'; script-src 'self'; - style-src 'self' 'unsafe-inline';" - > + style-src 'self' 'unsafe-inline';"> + @@ -49,4 +49,5 @@ - + + \ No newline at end of file diff --git a/package.json b/package.json index 47dc3ad74..5ca5adce4 100644 --- a/package.json +++ b/package.json @@ -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" } -} \ No newline at end of file +} diff --git a/ts/components/Lightbox.tsx b/ts/components/Lightbox.tsx index 790ba2295..4f4ed11bf 100644 --- a/ts/components/Lightbox.tsx +++ b/ts/components/Lightbox.tsx @@ -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): void => { if (!onClick) { return; @@ -200,38 +203,73 @@ const Icon = ({ /> ); -export class Lightbox extends React.Component { - private readonly containerRef: React.RefObject; - private readonly videoRef: React.RefObject; - private readonly playVideoBound: () => void; +export const LightboxObject = ({ + objectURL, + contentType, + videoRef, + onObjectClick, + playVideo, +}: { + objectURL: string; + contentType: MIME.MIMEType; + videoRef: React.MutableRefObject; + onObjectClick: (event: any) => any; + playVideo: () => void; +}) => { + const { urlToLoad } = useEncryptedFileFetch(objectURL, contentType); + + const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType); + if (isImageTypeSupported) { + return {window.i18n('lightboxImageAlt')}; + } - constructor(props: Props) { - super(props); + const isVideoTypeSupported = GoogleChrome.isVideoTypeSupported(contentType); + if (isVideoTypeSupported) { + return ( + + ); + } - 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 ; } - 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 ; +}; - public componentWillUnmount() { - const useCapture = true; - document.removeEventListener('keyup', this.onKeyUp, useCapture); - } +export const Lightbox = (props: Props) => { + const videoRef = useRef(null); + const containerRef = useRef(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 { } 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) => { + if (containerRef && event.target !== containerRef.current) { + return; + } + props.close?.(); + }; - return ( -
-
-
-
-
- {!is.undefined(contentType) - ? this.renderObject({ objectURL, contentType }) - : null} - {caption ?
{caption}
: null} -
-
-
- - - + // auto play video on showing a video attachment + useEffect(() => { + playVideo(); + }, []); - {onSave ? ( - +
+
+
+
+ {!is.undefined(contentType) ? ( + ) : null} + {caption ?
{caption}
: null}
-
- {onPrevious ? ( +
+ + { + props.close?.(); + }} + theme={theme} + /> + + + {onSave ? ( - ) : ( - - )} - {onNext ? ( - - ) : ( - - )} + ) : null}
- ); - } - - private readonly renderObject = ({ - objectURL, - contentType, - }: { - objectURL: string; - contentType: MIME.MIMEType; - }) => { - const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType); - if (isImageTypeSupported) { - return ( - {window.i18n('lightboxImageAlt')} - ); - } - - const isVideoTypeSupported = GoogleChrome.isVideoTypeSupported(contentType); - if (isVideoTypeSupported) { - return ( - - ); - } - - 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 ; - } - - // tslint:disable-next-line no-console - console.log('Lightbox: Unexpected content type', { contentType }); - - return ; - }; - - 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 - ) => { - if (this.containerRef && event.target !== this.containerRef.current) { - return; - } - this.onClose(); - }; - - private readonly onObjectClick = (event: any) => { - event.stopPropagation(); - this.onClose(); - }; -} +
+ {onPrevious ? ( + + ) : ( + + )} + {onNext ? ( + + ) : ( + + )} +
+
+ ); +}; diff --git a/ts/components/LightboxGallery.tsx b/ts/components/LightboxGallery.tsx index 23db6d2e0..f4f8b80bd 100644 --- a/ts/components/LightboxGallery.tsx +++ b/ts/components/LightboxGallery.tsx @@ -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; onSave?: (options: { @@ -28,83 +28,83 @@ interface Props { index: number; }) => void; selectedIndex: number; -} - -interface State { - selectedIndex: number; -} - -export class LightboxGallery extends React.Component { - public static defaultProps: Partial = { - 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 ( - - ); - } - - 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 ( + + ); +}; diff --git a/ts/components/UserDetailsDialog.tsx b/ts/components/UserDetailsDialog.tsx index 70e4e0301..70276c53d 100644 --- a/ts/components/UserDetailsDialog.tsx +++ b/ts/components/UserDetailsDialog.tsx @@ -64,7 +64,7 @@ export class UserDetailsDialog extends React.Component { 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 ( { {window.i18n('stagedImageAttachment', { 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 (
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 { - 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 ( -
- ); - } + if (!attachment) { + return null; + } + if (contentType && isImageTypeSupported(contentType)) { + if (imageBroken || !srcData) { return ( - {i18n('lightboxImageAlt')} ); - } else if (contentType && isVideoTypeSupported(contentType)) { - if (imageBroken || !mediaItem.thumbnailObjectUrl) { - return ( -
- ); - } - - return ( -
- {i18n('lightboxImageAlt')} -
-
-
-
- ); } return ( -
); - } + } else if (contentType && isVideoTypeSupported(contentType)) { + if (imageBroken || !srcData) { + return ( +
+ ); + } - public render() { return ( -
- {this.renderContent()} +
+ {i18n('lightboxImageAlt')} +
+
+
); } -} + + return ( +
+ ); +}; + +export const MediaGridItem = (props: Props) => { + return ( +
+ +
+ ); +}; diff --git a/ts/components/session/conversation/SessionRightPanel.tsx b/ts/components/session/conversation/SessionRightPanel.tsx index 7748b805d..208311e9b 100644 --- a/ts/components/session/conversation/SessionRightPanel.tsx +++ b/ts/components/session/conversation/SessionRightPanel.tsx @@ -159,8 +159,8 @@ class SessionRightPanel extends React.Component { ), thumbnailObjectUrl: thumbnail ? window.Signal.Migrations.getAbsoluteAttachmentPath( - thumbnail.path - ) + thumbnail.path + ) : null, contentType: attachment.contentType, index, @@ -195,12 +195,13 @@ class SessionRightPanel extends React.Component { 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 { 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 { diff --git a/ts/hooks/useEncryptedFileFetch.ts b/ts/hooks/useEncryptedFileFetch.ts index fa5630b70..42d55f054 100644 --- a/ts/hooks/useEncryptedFileFetch.ts +++ b/ts/hooks/useEncryptedFileFetch.ts @@ -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(); + +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 }; }; diff --git a/ts/session/crypto/DecryptedAttachmentsManager.ts b/ts/session/crypto/DecryptedAttachmentsManager.ts new file mode 100644 index 000000000..cf75f1e81 --- /dev/null +++ b/ts/session/crypto/DecryptedAttachmentsManager.ts @@ -0,0 +1,5 @@ + + + + +export const getDecryptedUrl \ No newline at end of file diff --git a/ts/session/utils/JobQueue.ts b/ts/session/utils/JobQueue.ts index 7293e8fe7..b774e25db 100644 --- a/ts/session/utils/JobQueue.ts +++ b/ts/session/utils/JobQueue.ts @@ -4,7 +4,7 @@ type Job = (() => PromiseLike) | (() => ResultType); // TODO: This needs to replace js/modules/job_queue.js export class JobQueue { - private pending: Promise = Promise.resolve(); + private pending?: Promise = Promise.resolve(); private readonly jobs: Map> = 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); }); diff --git a/ts/session/utils/String.ts b/ts/session/utils/String.ts index ba76d83df..5cdf4c1fa 100644 --- a/ts/session/utils/String.ts +++ b/ts/session/utils/String.ts @@ -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'); diff --git a/ts/test/components/media-gallery/groupMessagesByDate_test.ts b/ts/test/components/media-gallery/groupMessagesByDate_test.ts index 7e3367161..d6244e19f 100644 --- a/ts/test/components/media-gallery/groupMessagesByDate_test.ts +++ b/ts/test/components/media-gallery/groupMessagesByDate_test.ts @@ -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, diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index 56c22c533..734ea656a 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -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 => { 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(); }; diff --git a/yarn.lock b/yarn.lock index f4e984193..683d41150 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"