From a0a1d8643c4be38bea6c3e26393ebb9037bf7db2 Mon Sep 17 00:00:00 2001 From: Alex Kern Date: Tue, 19 Jan 2021 21:27:45 -0800 Subject: [PATCH] More logic for upload/download --- .github/main.workflow | 20 --- package.json | 5 + src/components/DownloadButton.tsx | 24 --- src/components/DownloadPage.tsx | 103 ----------- src/components/Downloader.tsx | 156 +++++++++++------ src/components/DropZone.tsx | 13 +- src/components/UploadPage.tsx | 104 ----------- src/components/Uploader.tsx | 278 +++++++++++++++++++++++++----- src/components/WebRTCProvider.tsx | 30 ++-- src/fs.ts | 12 ++ src/ice.ts | 52 ------ src/messages.ts | 77 +++++++++ src/pages/api/renew.ts | 7 +- src/pages/download/[...slug].tsx | 8 +- src/pages/index.tsx | 13 +- src/slugs.ts | 5 +- src/util.ts | 11 -- yarn.lock | 51 ++++++ 18 files changed, 519 insertions(+), 450 deletions(-) delete mode 100644 .github/main.workflow delete mode 100644 src/components/DownloadButton.tsx delete mode 100644 src/components/DownloadPage.tsx delete mode 100644 src/components/UploadPage.tsx delete mode 100644 src/ice.ts create mode 100644 src/messages.ts delete mode 100644 src/util.ts diff --git a/.github/main.workflow b/.github/main.workflow deleted file mode 100644 index eeb92ca..0000000 --- a/.github/main.workflow +++ /dev/null @@ -1,20 +0,0 @@ -workflow "Build on push" { - on = "push" - resolves = ["AWS deploy"] -} - -action "Docker build, tag, and push" { - uses = "pangzineng/Github-Action-One-Click-Docker@master" - secrets = ["DOCKER_USERNAME", "DOCKER_PASSWORD"] -} - -action "AWS deploy" { - uses = "actions/aws/cli@efb074ae4510f2d12c7801e4461b65bf5e8317e6" - needs = ["Docker build, tag, and push"] - secrets = [ - "AWS_ACCESS_KEY_ID", - "AWS_SECRET_ACCESS_KEY", - ] - args = "deploy --region us-west-2 create-deployment --application-name AppECS-filepizza-filepizza --deployment-config-name CodeDeployDefault.ECSAllAtOnce --deployment-group-name DgpECS-filepizza-filepizza --github-location repository=kern/filepizza,commitId=$GITHUB_REF" - runs = "aws" -} diff --git a/package.json b/package.json index 77341ad..56efa33 100644 --- a/package.json +++ b/package.json @@ -20,16 +20,21 @@ "dependencies": { "debug": "^4.2.0", "express": "^4.12.0", + "fp-ts": "^2.9.3", + "immer": "^8.0.0", + "io-ts": "^2.2.13", "ioredis": "^4.17.3", "next": "^9.5.3", "nodemon": "^1.4.1", "peer": "^0.5.3", "peerjs": "^1.3.1", "react": "^16.13.1", + "react-device-detect": "^1.15.0", "react-dom": "^16.13.1", "react-qr": "0.0.2", "styled-components": "^5.2.0", "twilio": "^2.9.1", + "use-http": "^1.0.16", "webrtcsupport": "^2.2.0", "xkcd-password": "^1.2.0" }, diff --git a/src/components/DownloadButton.tsx b/src/components/DownloadButton.tsx deleted file mode 100644 index 64e14ff..0000000 --- a/src/components/DownloadButton.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' - -export default class DownloadButton extends React.Component { - constructor() { - super() - this.onClick = this.onClick.bind(this) - } - - onClick(e) { - this.props.onClick(e) - } - - render() { - return ( - - ) - } -} - -DownloadButton.propTypes = { - onClick: React.PropTypes.func.isRequired, -} diff --git a/src/components/DownloadPage.tsx b/src/components/DownloadPage.tsx deleted file mode 100644 index 514d4cb..0000000 --- a/src/components/DownloadPage.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react' -import DownloadActions from '../actions/DownloadActions' -import DownloadStore from '../stores/DownloadStore' -import { formatSize } from '../util' -import ChromeNotice from './ChromeNotice' -import DownloadButton from './DownloadButton' -import ErrorPage from './ErrorPage' -import ProgressBar from './ProgressBar' -import Spinner from './Spinner' - -export default class DownloadPage extends React.Component { - constructor() { - super() - this.state = DownloadStore.getState() - - this._onChange = () => { - this.setState(DownloadStore.getState()) - } - - this.downloadFile = this.downloadFile.bind(this) - } - - componentDidMount() { - DownloadStore.listen(this._onChange) - } - - componentWillUnmount() { - DownloadStore.unlisten(this._onChange) - } - - downloadFile() { - DownloadActions.requestDownload() - } - - render() { - switch (this.state.status) { - case 'ready': - return ( -
-

FilePizza

- - - -

- Peers: {this.state.peers} · Up:{' '} - {formatSize(this.state.speedUp)} · Down:{' '} - {formatSize(this.state.speedDown)} -

- -
- ) - - case 'requesting': - case 'downloading': - return ( -
-

FilePizza

- - - -

- Peers: {this.state.peers} · Up:{' '} - {formatSize(this.state.speedUp)} · Down:{' '} - {formatSize(this.state.speedDown)} -

- -
- ) - - case 'done': - return ( -
-

FilePizza

- - - -

- Peers: {this.state.peers} · Up:{' '} - {formatSize(this.state.speedUp)} · Down:{' '} - {formatSize(this.state.speedDown)} -

- -
- ) - - default: - return - } - } -} diff --git a/src/components/Downloader.tsx b/src/components/Downloader.tsx index 540fdfc..0ab7b3d 100644 --- a/src/components/Downloader.tsx +++ b/src/components/Downloader.tsx @@ -1,56 +1,108 @@ -import React from 'react' +import React, { useCallback, useEffect, useState } from 'react' +import { useWebRTC } from './WebRTCProvider' +import { + browserName, + browserVersion, + osName, + osVersion, + mobileVendor, + mobileModel, +} from 'react-device-detect' +import * as t from 'io-ts' +import { decodeMessage, Message, MessageType } from '../messages' -interface Props { +export default function Downloader({ + uploaderPeerID, +}: { uploaderPeerID: string -} +}): JSX.Element { + const peer = useWebRTC() -const Downloader: React.FC = ({ uploaderPeerID }: Props) => { - // const room = useRef(null) - // const peerData = usePeerData() - - // useEffect(() => { - // if (room.current) return - - // room.current = peerData.connect(roomName) - // console.log(room.current) - // setInterval(() => console.log(room.current), 1000) - // room.current - // .on('participant', (participant) => { - // console.log(participant.getId() + ' joined') - // participant.newDataChannel() - - // participant - // .on('connected', () => { - // console.log('connected', participant.id) - // }) - // .on('disconnected', () => { - // console.log('disconnected', participant.id) - // }) - // .on('track', (event) => { - // console.log('stream', participant.id, event.streams[0]) - // }) - // .on('message', (payload) => { - // console.log(participant.id, payload) - // }) - // .on('error', (event) => { - // console.error('peer', participant.id, event) - // participant.renegotiate() - // }) - - // console.log(participant) - // participant.send(`hello there, I'm the downloader`) - // }) - // .on('error', (event) => { - // console.error('room', roomName, event) - // }) - - // return () => { - // room.current.disconnect() - // room.current = null - // } - // }, [peerData]) - - return null -} + const [password, setPassword] = useState('') + const [shouldAttemptConnection, setShouldAttemptConnection] = useState(false) + const [open, setOpen] = useState(false) + const [errorMessage, setErrorMessage] = useState(null) + + useEffect(() => { + if (!shouldAttemptConnection) { + return + } + + const conn = peer.connect(uploaderPeerID, { + reliable: true, + }) + + conn.on('open', () => { + setOpen(true) + + const request: t.TypeOf = { + type: MessageType.Start, + browserName: browserName, + browserVersion: browserVersion, + osName: osName, + osVersion: osVersion, + mobileVendor: mobileVendor, + mobileModel: mobileModel, + password, + } + + conn.send(request) + }) + + conn.on('data', (data) => { + try { + const message = decodeMessage(data) + switch (message.type) { + case MessageType.Info: + console.log(message) + break -export default Downloader + case MessageType.Error: + console.error(message.error) + setErrorMessage(message.error) + conn.close() + break + } + } catch (err) { + console.error(err) + } + }) + + conn.on('close', () => { + setOpen(false) + setShouldAttemptConnection(false) + }) + + return () => { + if (conn.open) conn.close() + } + }, [peer, password, shouldAttemptConnection]) + + const handleChangePassword = useCallback( + (e: React.ChangeEvent) => { + setPassword(e.target.value) + }, + [], + ) + + const handleSubmit = useCallback((ev) => { + ev.preventDefault() + setShouldAttemptConnection(true) + }, []) + + if (open) { + return
Downloading
+ } + + if (shouldAttemptConnection) { + return
Loading...
+ } + + return ( +
+ {errorMessage &&
{errorMessage}
} + + +
+ ) +} diff --git a/src/components/DropZone.tsx b/src/components/DropZone.tsx index 676c0bd..24752de 100644 --- a/src/components/DropZone.tsx +++ b/src/components/DropZone.tsx @@ -10,12 +10,13 @@ const Overlay = styled.div` display: block; ` -interface Props { - onDrop: (files: any) => void +export default function DropZone({ + children, + onDrop, +}: { + onDrop: (files: File[]) => void children?: React.ReactNode -} - -const Dropzone: React.FC = ({ children, onDrop }: Props) => { +}): JSX.Element { const overlay = useRef() const [focus, setFocus] = useState(false) @@ -62,5 +63,3 @@ const Dropzone: React.FC = ({ children, onDrop }: Props) => { ) } - -export default Dropzone diff --git a/src/components/UploadPage.tsx b/src/components/UploadPage.tsx deleted file mode 100644 index f048e66..0000000 --- a/src/components/UploadPage.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react' -import DropZone from './DropZone' -import Spinner from "./Spinner"; -import Tempalink from "./Tempalink"; -import UploadActions from "../actions/UploadActions"; -import UploadStore from "../stores/UploadStore"; -import socket from "filepizza-socket"; -import { formatSize } from "../util"; - -export default class UploadPage extends React.Component { - constructor() { - super(); - this.state = UploadStore.getState(); - - this._onChange = () => { - this.setState(UploadStore.getState()); - }; - - this.uploadFile = this.uploadFile.bind(this); - } - - componentDidMount() { - UploadStore.listen(this._onChange); - } - - componentWillUnmount() { - UploadStore.unlisten(this._onChange); - } - - uploadFile(file) { - UploadActions.uploadFile(file); - } - - handleSelectedFile(event) { - const files = event.target.files; - if (files.length > 0) { - UploadActions.uploadFile(files[0]); - } - } - - render() { - switch (this.state.status) { - case "ready": - return ( - -
- - -

FilePizza

-

Free peer-to-peer file transfers in your browser.

- - We never store anything. Files only served fresh. - -

- -

-
-
- ); - - case "processing": - return ( -
- - -

FilePizza

-

Processing...

-
- ); - - case "uploading": - return ( -
-

FilePizza

- - -

Send someone this link to download.

- - This link will work as long as this page is open. - -

- Peers: {this.state.peers} · Up:{" "} - {formatSize(this.state.speedUp)} -

- -
- ); - } -} diff --git a/src/components/Uploader.tsx b/src/components/Uploader.tsx index d0243fa..3d2e4ac 100644 --- a/src/components/Uploader.tsx +++ b/src/components/Uploader.tsx @@ -1,54 +1,244 @@ -import React, { useRef, useEffect } from 'react' +import React, { useEffect, useState } from 'react' import { UploadedFile } from '../types' import { useWebRTC } from './WebRTCProvider' +import useFetch from 'use-http' +import Peer, { DataConnection } from 'peerjs' +import { decodeMessage, Message, MessageType } from '../messages' +import produce from 'immer' +import * as t from 'io-ts' -interface Props { - roomName: string - files: UploadedFile[] +enum UploaderConnectionStatus { + Pending = 'PENDING', + Uploading = 'UPLOADING', + Done = 'DONE', + InvalidPassword = 'INVALID_PASSWORD', + Closed = 'CLOSED', + Paused = 'PAUSED', +} + +type UploaderConnection = { + status: UploaderConnectionStatus + dataConnection: DataConnection + browserName?: string + browserVersion?: string + osName?: string + osVersion?: string + mobileVendor?: string + mobileModel?: string +} + +const RENEW_INTERVAL = 5000 // 20 minutes + +function useUploaderChannel( + uploaderPeerID: string, +): { + loading: boolean + error: Error | null + longSlug: string + shortSlug: string +} { + const { loading, error, data } = useFetch( + '/api/create', + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ uploaderPeerID }), + }, + [uploaderPeerID], + ) + + if (!data) { + return { loading, error, longSlug: null, shortSlug: null } + } + + return { + loading: false, + error: null, + longSlug: data.longSlug, + shortSlug: data.shortSlug, + } +} + +function useUploaderChannelRenewal(shortSlug: string): void { + const { post } = useFetch('/api/renew') + + useEffect(() => { + let timeout = null + + const run = (): void => { + timeout = setTimeout(() => { + post({ slug: shortSlug }) + .then(() => { + run() + }) + .catch((err) => { + console.error(err) + run() + }) + }, RENEW_INTERVAL) + } + + run() + + return () => { + clearTimeout(timeout) + } + }, [shortSlug]) } -const Uploader: React.FC = ({ roomName, files }: Props) => { - const room = useRef(null) +function useUploaderConnections( + peer: Peer, + files: UploadedFile[], + password: string, +): Array { + const [connections, setConnections] = useState>([]) + + useEffect(() => { + peer.on('connection', (conn: DataConnection) => { + const newConn = { + status: UploaderConnectionStatus.Pending, + dataConnection: conn, + } + + setConnections((conns) => [...conns, newConn]) + const updateConnection = ( + fn: (draftConn: UploaderConnection) => void, + ) => { + setConnections((conns) => + produce(conns, (draft) => { + const updatedConn = draft.find((c) => c.dataConnection === conn) + if (!updatedConn) { + return + } + + fn(updatedConn) + }), + ) + } + + conn.on('data', (data): void => { + try { + const message = decodeMessage(data) + switch (message.type) { + case MessageType.RequestInfo: { + if (message.password !== password) { + const request: t.TypeOf = { + type: MessageType.Error, + error: 'Invalid password', + } + + conn.send(request) + + updateConnection((draft) => { + if (draft.status !== UploaderConnectionStatus.Pending) { + return + } + + draft.status = UploaderConnectionStatus.InvalidPassword + draft.browserName = message.browserName + draft.browserVersion = message.browserVersion + draft.osName = message.osName + draft.osVersion = message.osVersion + draft.mobileVendor = message.mobileVendor + draft.mobileModel = message.mobileModel + }) + + return + } + + updateConnection((draft) => { + if (draft.status !== UploaderConnectionStatus.Pending) { + return + } + + draft.status = UploaderConnectionStatus.Uploading + draft.browserName = message.browserName + draft.browserVersion = message.browserVersion + draft.osName = message.osName + draft.osVersion = message.osVersion + draft.mobileVendor = message.mobileVendor + draft.mobileModel = message.mobileModel + }) + + const fileInfo = files.map((f) => { + return { + fullPath: f.fullPath, + } + }) + + const request: t.TypeOf = { + type: MessageType.Info, + files: fileInfo, + } + conn.send(request) + + // TODO(@kern): Handle sending chunks + break + } + } + } catch (err) { + console.error(err) + } + }) + + conn.on('close', (): void => { + updateConnection((draft) => { + if (draft.status === UploaderConnectionStatus.InvalidPassword) { + return + } + + draft.status = UploaderConnectionStatus.Closed + }) + }) + }) + }, [peer, files, password]) + + return connections +} + +export default function Uploader({ + files, + password, +}: { + files: UploadedFile[] + password: string +}): JSX.Element { const peer = useWebRTC() + const { longSlug, shortSlug } = useUploaderChannel(peer.id) + useUploaderChannelRenewal(shortSlug) + const connections = useUploaderConnections(peer, files, password) - // useEffect(() => { - // room.current = peerData.connect(roomName) - // room.current - // .on('participant', (participant) => { - // console.log(participant.getId() + ' joined') - // participant.newDataChannel() - // - // participant - // .on('connected', () => { - // console.log('connected', participant.id) - // }) - // .on('disconnected', () => { - // console.log('disconnected', participant.id) - // }) - // .on('track', (event) => { - // console.log('stream', participant.id, event.streams[0]) - // }) - // .on('message', (payload) => { - // console.log(participant.id, payload) - // }) - // .on('error', (event) => { - // console.error('peer', participant.id, event) - // participant.renegotiate() - // }) - // participant.send(`hello there, I'm the uploader`) - // }) - // .on('error', (event) => { - // console.error('room', roomName, event) - // }) - // - // return () => { - // room.current.disconnect() - // room.current = null - // } - // }, [peerData]) + if (!longSlug || !shortSlug) { + return null + } + + const longURL = `/download/${longSlug}` + const shortURL = `/download/${shortSlug}` const items = files.map((f) =>
  • {f.fullPath}
  • ) - return
      {items}
    + return ( + <> +
    + Long: + + {longURL} + +
    +
    + Short: + + {shortURL} + +
    +
      {items}
    +

    Connections

    +
      + {connections.map((conn) => ( +
    • + {conn.status} {conn.browserName} {conn.browserVersion} +
    • + ))} +
    + + ) } - -export default Uploader diff --git a/src/components/WebRTCProvider.tsx b/src/components/WebRTCProvider.tsx index 2cb92fe..034bd9b 100644 --- a/src/components/WebRTCProvider.tsx +++ b/src/components/WebRTCProvider.tsx @@ -14,36 +14,38 @@ const ICE_SERVERS: RTCConfiguration = { ], } -interface Props { - servers?: RTCConfiguration - children?: React.ReactNode -} - -const WebRTCContext = React.createContext(null) +const WebRTCContext = React.createContext(null) export const useWebRTC = (): WebRTCValue => { - return useContext(WebRTCContext) + return useContext(WebRTCContext)! } -export const WebRTCProvider: React.FC = ({ +export function WebRTCProvider({ servers = ICE_SERVERS, children, -}: Props) => { - const [pageLoaded, setPageLoaded] = useState(false) - const peer = useRef(null) +}: { + servers?: RTCConfiguration + children?: React.ReactNode +}): JSX.Element { + const [loaded, setLoaded] = useState(false) + const peer = useRef(null) useEffect(() => { const effect = async () => { - peer.current = new Peer(undefined, { + const peerObj = new Peer(undefined, { config: servers, }) - setPageLoaded(true) + + peerObj.on('open', () => { + peer.current = peerObj + setLoaded(true) + }) } effect() }, []) - if (!pageLoaded || !peer.current) { + if (!loaded || !peer.current) { return null } diff --git a/src/fs.ts b/src/fs.ts index d968217..5402fb1 100644 --- a/src/fs.ts +++ b/src/fs.ts @@ -58,3 +58,15 @@ export const extractFileList = async (e: React.DragEvent): Promise => { return [scanResults, fileResults].flat(2) } + +// Borrowed from StackOverflow +// http://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript +export const formatSize = (bytes: number): string => { + if (bytes === 0) { + return '0 Bytes' + } + const k = 1000 + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toPrecision(3)} ${sizes[i]}` +} diff --git a/src/ice.ts b/src/ice.ts deleted file mode 100644 index 602b348..0000000 --- a/src/ice.ts +++ /dev/null @@ -1,52 +0,0 @@ -const twilio = require('twilio') -const winston = require('winston') - -if (process.env.TWILIO_SID && process.env.TWILIO_TOKEN) { - const twilioSID = process.env.TWILIO_SID - const twilioToken = process.env.TWILIO_TOKEN - var client = twilio(twilioSID, twilioToken) - winston.info('Using Twilio TURN service') -} else { - var client = null -} - -let ICE_SERVERS = [ - { - urls: 'stun:stun.l.google.com:19302', - }, -] - -if (process.env.ICE_SERVERS) { - ICE_SERVERS = JSON.parse(process.env.ICE_SERVERS) -} - -const CACHE_LIFETIME = 5 * 60 * 1000 // 5 minutes -let cachedPromise = null - -function clearCache() { - cachedPromise = null -} - -exports.getICEServers = function() { - if (client == null) { - return Promise.resolve(ICE_SERVERS) - } - if (cachedPromise) { - return cachedPromise - } - - cachedPromise = new Promise((resolve, reject) => { - client.tokens.create({}, (err, token) => { - if (err) { - winston.error(err) - return resolve(DEFAULT_ICE_SERVERS) - } - - winston.info('Retrieved ICE servers from Twilio') - setTimeout(clearCache, CACHE_LIFETIME) - resolve(token.ice_servers) - }) - }) - - return cachedPromise -} diff --git a/src/messages.ts b/src/messages.ts new file mode 100644 index 0000000..1dc4614 --- /dev/null +++ b/src/messages.ts @@ -0,0 +1,77 @@ +import * as t from 'io-ts' +import { pipe } from 'fp-ts/function' +import { fold } from 'fp-ts/Either' + +export enum MessageType { + RequestInfo = 'REQUEST_INFO', + Info = 'INFO', + Start = 'START', + Chunk = 'CHUNK', + Pause = 'PAUSE', + Error = 'ERROR', +} + +const RequestInfoMessage = t.type({ + type: t.literal(MessageType.RequestInfo), + browserName: t.string, + browserVersion: t.string, + osName: t.string, + osVersion: t.string, + mobileVendor: t.string, + mobileModel: t.string, + password: t.string, +}) + +const InfoMessage = t.type({ + type: t.literal(MessageType.Info), + files: t.array( + t.type({ + fullPath: t.string, + }), + ), +}) + +const StartMessage = t.type({ + type: t.literal(MessageType.Start), + browserName: t.string, + browserVersion: t.string, + osName: t.string, + osVersion: t.string, + mobileVendor: t.string, + mobileModel: t.string, + password: t.string, +}) + +const ChunkMessage = t.type({ + type: t.literal(MessageType.Chunk), + // TODO(@kern): Chunk +}) + +const PauseMessage = t.type({ + type: t.literal(MessageType.Pause), + // TODO(@kern): Pausing +}) + +const ErrorMessage = t.type({ + type: t.literal(MessageType.Error), + error: t.string, +}) + +export const Message = t.union([ + RequestInfoMessage, + InfoMessage, + StartMessage, + ChunkMessage, + PauseMessage, + ErrorMessage, +]) + +export function decodeMessage(data: any): t.TypeOf { + const onFailure = (errors: t.Errors): t.TypeOf => { + throw new Error(`${errors.length} error(s) found`) + } + + const onSuccess = (mesg: t.TypeOf) => mesg + + return pipe(Message.decode(data), fold(onFailure, onSuccess)) +} diff --git a/src/pages/api/renew.ts b/src/pages/api/renew.ts index 61ea8d9..63ec358 100644 --- a/src/pages/api/renew.ts +++ b/src/pages/api/renew.ts @@ -2,9 +2,10 @@ import { NextApiRequest, NextApiResponse } from 'next' import { channelRepo } from '../../channel' import { routeHandler, getBodyKey } from '../../routes' -export default routeHandler( - (req: NextApiRequest, _res: NextApiResponse): Promise => { +export default routeHandler( + async (req: NextApiRequest, _res: NextApiResponse): Promise => { const slug = getBodyKey(req, 'slug') - return channelRepo.renew(slug) + await channelRepo.renew(slug) + return true }, ) diff --git a/src/pages/download/[...slug].tsx b/src/pages/download/[...slug].tsx index e2f67ea..1d32d2e 100644 --- a/src/pages/download/[...slug].tsx +++ b/src/pages/download/[...slug].tsx @@ -10,14 +10,10 @@ type Props = { error?: string } -const DownloadPage: NextPage = ({ slug, uploaderPeerID }) => { +const DownloadPage: NextPage = ({ uploaderPeerID }) => { return ( - <> -
    {slug}
    -
    {uploaderPeerID}
    - - +
    ) } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 021fc18..388ae4b 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useState } from 'react' import WebRTCProvider from '../components/WebRTCProvider' -import Dropzone from '../components/Dropzone' +import DropZone from '../components/DropZone' import UploadFileList from '../components/UploadFileList' import Uploader from '../components/Uploader' import PasswordField from '../components/PasswordField' @@ -15,7 +15,6 @@ export const IndexPage: NextPage = () => { const [uploading, setUploading] = useState(false) const handleDrop = useCallback((files: UploadedFile[]): void => { - console.log('Received files', files) setUploadedFiles(files) }, []) @@ -34,7 +33,7 @@ export const IndexPage: NextPage = () => { if (!uploadedFiles.length) { return ( <> - Drop a file to get started. + Drop a file to get started. ) } @@ -51,11 +50,9 @@ export const IndexPage: NextPage = () => { return ( - <> - - - - + + + ) } diff --git a/src/slugs.ts b/src/slugs.ts index 6da53ab..dae6305 100644 --- a/src/slugs.ts +++ b/src/slugs.ts @@ -15,10 +15,11 @@ export const generateShortSlug = (): string => { const longSlugGenerator = new xkcdPassword() longSlugGenerator.initWithWordList(config.longSlug.words) -export const generateLongSlug = (): Promise => { - return longSlugGenerator.generate({ +export const generateLongSlug = async (): Promise => { + const parts = await longSlugGenerator.generate({ numWords: config.longSlug.numWords, minLength: 1, maxLength: 256, }) + return parts.join('/') } diff --git a/src/util.ts b/src/util.ts deleted file mode 100644 index 18ad34c..0000000 --- a/src/util.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Borrowed from StackOverflow -// http://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript -export const formatSize = (bytes: number): string => { - if (bytes === 0) { - return '0 Bytes' - } - const k = 1000 - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return `${(bytes / Math.pow(k, i)).toPrecision(3)} ${sizes[i]}` -} diff --git a/yarn.lock b/yarn.lock index 315f174..87722fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3749,6 +3749,11 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +fp-ts@^2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.9.3.tgz#419b9103018ddf7c785acf2b3fecb2662afab5af" + integrity sha512-NjzcHYgigcbPQ6yJ52zwgsVDwKz3vwy9sjbxyzcvfXQm+j1BGeOPRuzLKEwsLyE4Xut6gG1FXJtsU9/gUB7tXg== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -4201,6 +4206,11 @@ ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +immer@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.0.tgz#08763549ba9dd7d5e2eb4bec504a8315bd9440c2" + integrity sha512-jm87NNBAIG4fHwouilCHIecFXp5rMGkiFrAuhVO685UnMAlOneEAnOyzPt8OnP47TC11q/E7vpzZe0WvwepFTg== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" @@ -4278,6 +4288,11 @@ invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +io-ts@^2.2.13: + version "2.2.13" + resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.2.13.tgz#e04016685e863dd5cffb2b5262c5c9c74432dfda" + integrity sha512-BYJgE/BanovJKDvCnAkrr7f3gTucSyk+Sr5VtpouBO1/YfBKUyIn2z1ODG8LEF+1D4sjKZ3Bd/A5/v8JrJe5UQ== + ioredis@^4.17.3: version "4.17.3" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.17.3.tgz#9938c60e4ca685f75326337177bdc2e73ae9c9dc" @@ -6171,6 +6186,13 @@ rc@^1.0.1, rc@^1.1.6: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-device-detect@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-1.15.0.tgz#5321f94ae3c4d51ef399b0502a6c739e32d0f315" + integrity sha512-ywjtWW04U7vaJK87IAFHhKozZhTPeDVWsfYx5CxQSQCjU5+fnMMxWZt9HnVWaNTqBEn6g8wCNWyqav7sXJrURg== + dependencies: + ua-parser-js "^0.7.23" + react-dom@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" @@ -7529,6 +7551,11 @@ typescript@^4.0.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5" integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg== +ua-parser-js@^0.7.23: + version "0.7.23" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.23.tgz#704d67f951e13195fbcd3d78818577f5bc1d547b" + integrity sha512-m4hvMLxgGHXG3O3fQVAyyAQpZzDOvwnhOTjYz5Xmr7r/+LpkNy3vJXdVRWgd1TkAb7NGROZuSy96CrlNVjA7KA== + undefsafe@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" @@ -7666,6 +7693,25 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +urs@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/urs/-/urs-0.0.7.tgz#2dbf320a68a54bbd67ab9c5ac103b0f2227ca6be" + integrity sha512-NDwuby5BP7D60RjHlow8Gch4yfglLdCpZagGeVDZNcL+jsA2v6RcBhPTJraqq1oQ1U3LT1bQfgmwgNUUIFht+g== + +use-http@^1.0.16: + version "1.0.16" + resolved "https://registry.yarnpkg.com/use-http/-/use-http-1.0.16.tgz#6d75f3e734abf990049c7a02b22d757c3abda883" + integrity sha512-t2/Q6Ic644Jn9z62sOVhfURXX58429NwgRDvJZikxhDjLG5FrZ0SLLxGgm4GYznOnKBqkXKVrhW/wdjwXkw1rA== + dependencies: + urs "^0.0.7" + use-ssr "^1.0.22" + utility-types "^3.10.0" + +use-ssr@^1.0.22: + version "1.0.23" + resolved "https://registry.yarnpkg.com/use-ssr/-/use-ssr-1.0.23.tgz#3bde1e10cd01b3b61ab6386d7cddb72e74828bf8" + integrity sha512-5bvlssgROgPgIrnILJe2mJch4e2Id0/bVm1SQzqvPvEAXmlsinCCVHWK3a2iHcPat7PkdJHBo0gmSmODIz6tNA== + use-subscription@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.4.1.tgz#edcbcc220f1adb2dd4fa0b2f61b6cc308e620069" @@ -7697,6 +7743,11 @@ util@^0.11.0: dependencies: inherits "2.0.3" +utility-types@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"