Just keep going

pull/152/head
Alex Kern 5 years ago
parent b601c89eeb
commit 6a0ab981f0
No known key found for this signature in database
GPG Key ID: F3141D5EDF48F89F

@ -1,16 +1,18 @@
'use strict';
module.exports = {
parser: '@typescript-eslint/parser',
root: true,
parser: "@typescript-eslint/parser",
parserOptions: {
project: './tsconfig.json',
},
extends: [
'@strv/typescript',
'@strv/typescript/optional',
'@strv/typescript/style'
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
plugins: [
"@typescript-eslint",
"prettier"
],
rules: {
@ -25,7 +27,7 @@ module.exports = {
"new-cap": "off",
"no-inline-comments": "off",
"no-shadow": "warn",
"no-use-before-define": ["error", { "variables": false }],
"no-use-before-define": "off",
"prettier/prettier": "error",
}
};

@ -7,7 +7,7 @@
"license": "BSD-3-Clause",
"homepage": "https://github.com/kern/filepizza",
"scripts": {
"dev": "next dev",
"dev": "node src/server.js",
"build": "next build",
"start": "next start"
},
@ -21,6 +21,7 @@
"dependencies": {
"alt": "^0.14.4",
"classnames": "^1.2.0",
"debug": "^4.2.0",
"express": "^4.12.0",
"express-force-ssl": "^0.3.1",
"express-winston": "^0.3.1",
@ -30,6 +31,8 @@
"nib": "^1.1.0",
"node-uuid": "^1.4.3",
"nodemon": "^1.4.1",
"peer-data": "^3.2.5",
"peer-data-server": "^1.0.10",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-frozenhead": "^0.3.0",
@ -39,6 +42,7 @@
"react-router": "^0.13.1",
"socket.io": "^1.3.5",
"socket.io-client": "^1.3.5",
"styled-components": "^5.2.0",
"stylus": "^0.52.4",
"twilio": "^2.9.1",
"webrtcsupport": "^2.2.0",
@ -46,16 +50,31 @@
"xkcd-password": "^1.2.0"
},
"devDependencies": {
"@strv/eslint-config-typescript": "^2.3.0",
"@types/debug": "^4.1.5",
"@types/node": "^14.11.1",
"@types/react": "^16.9.49",
"@types/styled-components": "^5.1.3",
"@typescript-eslint/eslint-plugin": "^4.1.1",
"@typescript-eslint/parser": "^4.1.1",
"eslint": "^7.9.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.6",
"husky": "^4.3.0",
"lint-staged": "^10.4.0",
"prettier": "^2.1.2",
"typescript": "^4.0.3"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"git add"
]
}
}

@ -0,0 +1,58 @@
import React, { useEffect, useRef } from 'react'
import { usePeerData } from 'react-peer-data'
import { Room } from 'peer-data'
interface Props {
roomName: string
}
const Downloader: React.FC<Props> = ({ roomName }: 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
}
export default Downloader

@ -1,64 +1,66 @@
import React from 'react'
import React, { useState, useRef, useCallback } from 'react'
import styled from 'styled-components'
import { extractFileList } from '../fs'
export default class DropZone extends React.Component {
constructor() {
super()
this.state = { focus: false }
const Wrapper = styled.div`
display: block;
`
this.onDragEnter = this.onDragEnter.bind(this)
this.onDragLeave = this.onDragLeave.bind(this)
this.onDragOver = this.onDragOver.bind(this)
this.onDrop = this.onDrop.bind(this)
}
const Overlay = styled.div`
display: block;
`
onDragEnter() {
this.setState({ focus: true })
}
interface Props {
onDrop: (files: any) => void
children?: React.ReactNode
}
onDragLeave(e) {
if (e.target !== this.refs.overlay.getDOMNode()) {
return
}
this.setState({ focus: false })
}
const Dropzone: React.FC<Props> = ({ children, onDrop }: Props) => {
const overlay = useRef()
const [focus, setFocus] = useState(false)
onDragOver(e) {
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
}
const handleDragEnter = useCallback(() => {
setFocus(true)
}, [])
const handleDragLeave = useCallback(
(e: React.DragEvent) => {
if (e.target !== overlay.current) {
return
}
onDrop(e) {
setFocus(false)
},
[overlay.current],
)
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault()
this.setState({ focus: false })
e.dataTransfer.dropEffect = 'copy'
}, [])
const file = e.dataTransfer.files[0]
if (this.props.onDrop && file) {
this.props.onDrop(file)
}
}
const handleDrop = useCallback(
async (e: React.DragEvent) => {
e.preventDefault()
setFocus(false)
render() {
return (
<div
className="drop-zone"
ref="root"
onDragEnter={this.onDragEnter}
onDragLeave={this.onDragLeave}
onDragOver={this.onDragOver}
onDrop={this.onDrop}
>
<div
className="drop-zone-overlay"
hidden={!this.state.focus}
ref="overlay"
/>
const files = await extractFileList(e)
onDrop(files)
},
[onDrop],
)
{this.props.children}
</div>
)
}
return (
<Wrapper
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={handleDrop}
>
<Overlay ref={overlay} hidden={!focus} />
{children}
</Wrapper>
)
}
DropZone.propTypes = {
onDrop: React.PropTypes.func.isRequired,
}
export default Dropzone

@ -0,0 +1,27 @@
import React, { useCallback } from 'react'
import styled from 'styled-components'
const StyledPasswordInput = styled.input`
background: red;
`
interface Props {
value: string
onChange: (value: string) => void
}
export const PasswordField: React.FC<Props> = ({ value, onChange }: Props) => {
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value)
}, [])
return (
<StyledPasswordInput
type="password"
value={value}
onChange={handleChange}
/>
)
}
export default PasswordField

@ -0,0 +1,16 @@
import React from 'react'
import styled from 'styled-components'
const StyledStartButton = styled.button`
background: green;
`
type Props = {
onClick: React.MouseEventHandler
}
const StartButton: React.FC<Props> = ({ onClick }: Props) => {
return <StyledStartButton onClick={onClick}>Start</StyledStartButton>
}
export default StartButton

@ -0,0 +1,16 @@
import React from 'react'
import styled from 'styled-components'
const StyledStopButton = styled.button`
background: blue;
`
type Props = {
onClick: React.MouseEventHandler
}
const StopButton: React.FC<Props> = ({ onClick }: Props) => {
return <StyledStopButton onClick={onClick}>Stop</StyledStopButton>
}
export default StopButton

@ -0,0 +1,13 @@
import React from 'react'
import { UploadedFile } from '../types'
interface Props {
files: UploadedFile[]
}
const UploadFileList: React.FC<Props> = ({ files }: Props) => {
const items = files.map((f) => <li key={f.fullPath}>{f.fullPath}</li>)
return <ul>{items}</ul>
}
export default UploadFileList

@ -1,46 +1,54 @@
import Arrow from '@app/components/Arrow'
import React from 'react'
import UploadActions from '@app/actions/UploadActions'
import React, { useRef, useEffect } from 'react'
import { usePeerData } from 'react-peer-data'
import { UploadedFile } from '../types'
import { Room } from 'peer-data'
export default class UploadPage extends React.Component {
constructor() {
super()
this.uploadFile = this.uploadFile.bind(this)
}
interface Props {
roomName: string
files: UploadedFile[]
}
uploadFile(file) {
UploadActions.uploadFile(file)
}
const Uploader: React.FC<Props> = ({ roomName, files }: Props) => {
const room = useRef<Room | null>(null)
const peerData = usePeerData()
render() {
switch (this.props.status) {
case 'ready':
return (
<div>
<DropZone onDrop={this.uploadFile} />
<Arrow dir="up" />
</div>
)
break
useEffect(() => {
room.current = peerData.connect(roomName)
room.current
.on('participant', (participant) => {
console.log(participant.getId() + ' joined')
case 'processing':
return (
<div>
<Arrow dir="up" animated />
<FileDescription file={this.props.file} />
</div>
)
break
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)
})
case 'uploading':
return (
<div>
<Arrow dir="up" animated />
<FileDescription file={this.props.file} />
<Temaplink token={this.props.token} />
</div>
)
break
return () => {
room.current.disconnect()
room.current = null
}
}
}, [peerData])
const items = files.map((f) => <li key={f.fullPath}>{f.fullPath}</li>)
return <ul>{items}</ul>
}
export default Uploader

@ -2,7 +2,27 @@ import React, { useState, useEffect } from 'react'
import { EventDispatcher } from 'peer-data'
import { PeerDataProvider } from 'react-peer-data'
export const WebRTCProvider: React.FC = ({ children }) => {
const dispatcher = new EventDispatcher()
const constraints = { ordered: true }
const signaling = { dispatcher }
const ICE_SERVERS: RTCConfiguration = {
iceServers: [
{
urls: 'stun:stun.l.google.com:19302',
},
],
}
interface Props {
servers?: RTCConfiguration
children?: React.ReactNode
}
export const WebRTCProvider: React.FC<Props> = ({
servers = ICE_SERVERS,
children,
}: Props) => {
const [pageLoaded, setPageLoaded] = useState(false)
useEffect(() => {
@ -15,9 +35,9 @@ export const WebRTCProvider: React.FC = ({ children }) => {
return (
<PeerDataProvider
servers={{ iceServers: [{ urls: 'stun:stun.1.google.com:19302' }] }}
constraints={{ ordered: true }}
signaling={{ dispatcher: new EventDispatcher() }}
servers={servers}
constraints={constraints}
signaling={signaling}
>
{children}
</PeerDataProvider>

@ -0,0 +1,60 @@
const getAsFile = (entry: any): Promise<File> =>
new Promise((resolve, reject) => {
entry.file((file: File) => {
file.fullPath = entry.fullPath
resolve(file)
}, reject)
})
const readDirectoryEntries = (reader: any): Promise<any[]> =>
new Promise((resolve, reject) => {
reader.readEntries((entries) => {
resolve(entries)
}, reject)
})
const scanDirectoryEntry = async (entry: any): Promise<File[]> => {
const directoryReader = entry.createReader()
const result = []
// eslint-disable-next-line no-constant-condition
while (true) {
const subentries = await readDirectoryEntries(directoryReader)
if (!subentries.length) {
return result
}
for (const se of subentries) {
if (se.isDirectory) {
const ses = await scanDirectoryEntry(se)
result.push(...ses)
} else {
const file = await getAsFile(se)
result.push(file)
}
}
}
}
export const extractFileList = async (e: React.DragEvent): Promise<File[]> => {
if (!e.dataTransfer.items.length) {
return []
}
const items = e.dataTransfer.items
const scans = []
const files = []
for (const item of items) {
const entry = item.webkitGetAsEntry()
if (entry.isDirectory) {
scans.push(scanDirectoryEntry(entry))
} else {
files.push(getAsFile(entry))
}
}
const scanResults = await Promise.all(scans)
const fileResults = await Promise.all(files)
return [scanResults, fileResults].flat(2)
}

@ -0,0 +1,7 @@
import debug from 'debug'
export const error = debug('filepizza:error')
export const info = debug('filepizza:info')
export const warn = debug('filepizza:warn')

@ -1,11 +1,25 @@
import React from 'react'
import WebRTCProvider from '../../components/WebRTCProvider'
import { useRouter } from 'next/router'
import Downloader from '../../components/Downloader'
import { NextPage } from 'next'
const DownloadPage: React.FC = () => {
const DownloadPage: NextPage = () => {
const router = useRouter()
const { slug } = router.query
return <div>{JSON.stringify(slug)}</div>
return (
<WebRTCProvider>
<>
<div>{JSON.stringify(slug)}</div>
<Downloader roomName="my-room" />
</>
</WebRTCProvider>
)
}
DownloadPage.getInitialProps = () => {
return {}
}
export default DownloadPage

@ -1,10 +1,67 @@
import React from 'react'
import React, { useCallback, useState } from 'react'
import WebRTCProvider from '../components/WebRTCProvider'
import Dropzone from '../components/Dropzone'
import UploadFileList from '../components/UploadFileList'
import Uploader from '../components/Uploader'
import PasswordField from '../components/PasswordField'
import StartButton from '../components/StartButton'
import StopButton from '../components/StopButton'
import { UploadedFile } from '../types'
import { NextPage } from 'next'
export const IndexPage: React.FC = () => (
<WebRTCProvider>
<>Index page</>
</WebRTCProvider>
)
export const IndexPage: NextPage = () => {
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([])
const [password, setPassword] = useState('')
const [uploading, setUploading] = useState(false)
const handleDrop = useCallback((files: UploadedFile[]): void => {
console.log('Received files', files)
setUploadedFiles(files)
}, [])
const handleChangePassword = useCallback((pw: string) => {
setPassword(pw)
}, [])
const handleStart = useCallback(() => {
setUploading(true)
}, [])
const handleStop = useCallback(() => {
setUploading(false)
}, [])
if (!uploadedFiles.length) {
return (
<>
<Dropzone onDrop={handleDrop}>Drop a file to get started.</Dropzone>
</>
)
}
if (!uploading) {
return (
<>
<UploadFileList files={uploadedFiles} />
<PasswordField value={password} onChange={handleChangePassword} />
<StartButton onClick={handleStart} />
</>
)
}
return (
<WebRTCProvider>
<>
<UploadFileList files={uploadedFiles} />
<StopButton onClick={handleStop} />
<Uploader roomName={'my-room'} files={uploadedFiles} />
</>
</WebRTCProvider>
)
}
IndexPage.getInitialProps = () => {
return {}
}
export default IndexPage

@ -0,0 +1,26 @@
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const PeerDataServer = require('peer-data-server')
const appendPeerDataServer = PeerDataServer.default || PeerDataServer
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = createServer((req, res) => {
const parsedUrl = parse(req.url, true)
handle(req, res, parsedUrl)
})
appendPeerDataServer(server)
server.listen(3000, (err) => {
if (err) {
throw err
}
console.log('> Ready on http://localhost:3000')
})
})

@ -1,112 +0,0 @@
const fs = require('fs')
const express = require('express')
const expressWinston = require('express-winston')
const socketIO = require('socket.io')
const winston = require('winston')
const ice = require('./ice')
const db = require('./db')
process.on('unhandledRejection', (reason, p) => {
p.catch(err => {
log.error('Exiting due to unhandled rejection!')
log.error(err)
process.exit(1)
})
})
process.on('uncaughtException', (err) => {
log.error('Exiting due to uncaught exception!')
log.error(err)
process.exit(1)
})
const app = express()
let port
process.env.PORT || (process.env.NODE_ENV === 'production' ? 80 : 3000)
if (!process.env.QUIET) {
app.use(
expressWinston.logger({
winstonInstance: winston,
expressFormat: true,
}))
}
app.get('/app.js', require('./middleware/javascript'))
app.use(require('./middleware/static'))
app.use([
require('./middleware/bootstrap'),
require('./middleware/error'),
require('./middleware/react'),
])
const TRACKERS = process.env.WEBTORRENT_TRACKERS
? process.env.WEBTORRENT_TRACKERS.split(',').map(t => [t.trim()])
: [
['wss://tracker.openwebtorrent.com'],
['wss://tracker.btorrent.xyz'],
['wss://tracker.fastcast.nz'],
]
function bootServer(server) {
const io = socketIO(server)
io.set('transports', ['polling'])
io.on('connection', (socket) => {
let upload = null
socket.on('upload', (metadata, res) => {
if (upload) {
return
}
db.create(socket).then(u => {
upload = u
upload.fileName = metadata.fileName
upload.fileSize = metadata.fileSize
upload.fileType = metadata.fileType
upload.infoHash = metadata.infoHash
res({ token: upload.token, shortToken: upload.shortToken })
})
})
socket.on('trackerConfig', (_, res) => {
ice.getICEServers().then(iceServers => {
res({ rtcConfig: { iceServers }, announce: TRACKERS })
})
})
socket.on('disconnect', () => {
db.remove(upload)
})
})
server.on('error', (err) => {
winston.error(err.message)
process.exit(1)
})
server.listen(port, (err) => {
const host = server.address().address
const port = server.address().port
winston.info('FilePizza listening on %s:%s', host, port)
})
}
if (process.env.HTTPS_KEY && process.env.HTTPS_CERT) {
// user-supplied HTTPS key/cert
const https = require('https')
var server = https.createServer(
{
key: fs.readFileSync(process.env.HTTPS_KEY),
cert: fs.readFileSync(process.env.HTTPS_CERT),
},
app,
)
bootServer(server)
} else {
// no HTTPS
const http = require('http')
var server = http.Server(app)
bootServer(server)
}

@ -0,0 +1 @@
export type UploadedFile = File & { fullPath: string }

@ -1,51 +0,0 @@
const nib = require("nib");
const webpack = require('webpack')
;module.exports = {
entry: "./src/client",
target: "web",
output: {
filename: "dist/bundle.js",
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
},
{
test: /\.json$/,
loader: "json",
},
{
test: /\.styl$/,
loader: "style-loader!css-loader!stylus-loader",
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
GA_ACCESS_TOKEN: JSON.stringify(process.env.GA_ACCESS_TOKEN),
},
}),
],
node: {
fs: "empty",
},
stylus: {
use: [nib()],
},
rules: [
{
test: /react-google-analytics/,
use: process.env.GA_ACCESS_TOKEN ? 'null-loader' : 'noop-loader',
},
],
};

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save