mirror of https://github.com/kern/filepizza
More logic for upload/download
parent
981bd31253
commit
a0a1d8643c
@ -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"
|
||||
}
|
||||
@ -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 (
|
||||
<button className="download-button" onClick={this.onClick}>
|
||||
Download
|
||||
</button>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DownloadButton.propTypes = {
|
||||
onClick: React.PropTypes.func.isRequired,
|
||||
}
|
||||
@ -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 (
|
||||
<div className="page">
|
||||
<h1>FilePizza</h1>
|
||||
<Spinner
|
||||
dir="down"
|
||||
name={this.state.fileName}
|
||||
size={this.state.fileSize}
|
||||
/>
|
||||
|
||||
<ChromeNotice />
|
||||
<p className="notice">
|
||||
Peers: {this.state.peers} · Up:{' '}
|
||||
{formatSize(this.state.speedUp)} · Down:{' '}
|
||||
{formatSize(this.state.speedDown)}
|
||||
</p>
|
||||
<DownloadButton onClick={this.downloadFile} />
|
||||
</div>
|
||||
)
|
||||
|
||||
case 'requesting':
|
||||
case 'downloading':
|
||||
return (
|
||||
<div className="page">
|
||||
<h1>FilePizza</h1>
|
||||
<Spinner
|
||||
dir="down"
|
||||
animated
|
||||
name={this.state.fileName}
|
||||
size={this.state.fileSize}
|
||||
/>
|
||||
|
||||
<ChromeNotice />
|
||||
<p className="notice">
|
||||
Peers: {this.state.peers} · Up:{' '}
|
||||
{formatSize(this.state.speedUp)} · Down:{' '}
|
||||
{formatSize(this.state.speedDown)}
|
||||
</p>
|
||||
<ProgressBar value={this.state.progress} />
|
||||
</div>
|
||||
)
|
||||
|
||||
case 'done':
|
||||
return (
|
||||
<div className="page">
|
||||
<h1>FilePizza</h1>
|
||||
<Spinner
|
||||
dir="down"
|
||||
name={this.state.fileName}
|
||||
size={this.state.fileSize}
|
||||
/>
|
||||
|
||||
<ChromeNotice />
|
||||
<p className="notice">
|
||||
Peers: {this.state.peers} · Up:{' '}
|
||||
{formatSize(this.state.speedUp)} · Down:{' '}
|
||||
{formatSize(this.state.speedDown)}
|
||||
</p>
|
||||
<ProgressBar value={1} />
|
||||
</div>
|
||||
)
|
||||
|
||||
default:
|
||||
return <ErrorPage />
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Props> = ({ uploaderPeerID }: Props) => {
|
||||
// const room = useRef<Room | null>(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<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldAttemptConnection) {
|
||||
return
|
||||
}
|
||||
|
||||
const conn = peer.connect(uploaderPeerID, {
|
||||
reliable: true,
|
||||
})
|
||||
|
||||
conn.on('open', () => {
|
||||
setOpen(true)
|
||||
|
||||
const request: t.TypeOf<typeof Message> = {
|
||||
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<HTMLInputElement>) => {
|
||||
setPassword(e.target.value)
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const handleSubmit = useCallback((ev) => {
|
||||
ev.preventDefault()
|
||||
setShouldAttemptConnection(true)
|
||||
}, [])
|
||||
|
||||
if (open) {
|
||||
return <div>Downloading</div>
|
||||
}
|
||||
|
||||
if (shouldAttemptConnection) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<form action="#" method="post" onSubmit={handleSubmit}>
|
||||
{errorMessage && <div style={{ color: 'red' }}>{errorMessage}</div>}
|
||||
<input type="password" value={password} onChange={handleChangePassword} />
|
||||
<button>Start</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<DropZone onDrop={this.uploadFile}>
|
||||
<div className="page">
|
||||
<Spinner dir="up" />
|
||||
|
||||
<h1>FilePizza</h1>
|
||||
<p>Free peer-to-peer file transfers in your browser.</p>
|
||||
<small className="notice">
|
||||
We never store anything. Files only served fresh.
|
||||
</small>
|
||||
<p>
|
||||
<label className="select-file-label">
|
||||
<input
|
||||
type="file"
|
||||
onChange={this.handleSelectedFile}
|
||||
required
|
||||
/>
|
||||
<span>select a file</span>
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
</DropZone>
|
||||
);
|
||||
|
||||
case "processing":
|
||||
return (
|
||||
<div className="page">
|
||||
<Spinner dir="up" animated />
|
||||
|
||||
<h1>FilePizza</h1>
|
||||
<p>Processing...</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
case "uploading":
|
||||
return (
|
||||
<div className="page">
|
||||
<h1>FilePizza</h1>
|
||||
<Spinner
|
||||
dir="up"
|
||||
animated
|
||||
name={this.state.fileName}
|
||||
size={this.state.fileSize}
|
||||
/>
|
||||
|
||||
<p>Send someone this link to download.</p>
|
||||
<small className="notice">
|
||||
This link will work as long as this page is open.
|
||||
</small>
|
||||
<p>
|
||||
Peers: {this.state.peers} · Up:{" "}
|
||||
{formatSize(this.state.speedUp)}
|
||||
</p>
|
||||
<Tempalink
|
||||
token={this.state.token}
|
||||
shortToken={this.state.shortToken}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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<typeof Message> {
|
||||
const onFailure = (errors: t.Errors): t.TypeOf<typeof Message> => {
|
||||
throw new Error(`${errors.length} error(s) found`)
|
||||
}
|
||||
|
||||
const onSuccess = (mesg: t.TypeOf<typeof Message>) => mesg
|
||||
|
||||
return pipe(Message.decode(data), fold(onFailure, onSuccess))
|
||||
}
|
||||
@ -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]}`
|
||||
}
|
||||
Loading…
Reference in New Issue