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 (
+
+ )
+}
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
+ return (
+ <>
+
+
+
+ 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"