Begin redis channel impl

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

@ -1,23 +1,11 @@
version: '3'
services:
filepizza:
image: kern/filepizza:latest
restart: always
build:
context: .
redis:
image: redis:latest
ports:
- 3333:3333
environment:
- PORT=3333
- EXTRA_ICE_SERVERS=turn:localhost:3478
- WEBTORRENT_TRACKERS=ws://localhost:8000
coturn:
image: instrumentisto/coturn:latest
network_mode: host
ports:
- 3478:3478
bittorrent-tracker:
image: henkel/bittorrent-tracker:latest
command: ["npx", "bittorrent-tracker", "--http-hostname", "0.0.0.0", "--ws"]
ports:
- 8000:8000
- 6379:6379
# coturn:
# image: instrumentisto/coturn:latest
# network_mode: host
# ports:
# - 3478:3478

@ -7,7 +7,7 @@
"license": "BSD-3-Clause",
"homepage": "https://github.com/kern/filepizza",
"scripts": {
"dev": "node src/server.js",
"dev": "next",
"build": "next build",
"start": "next start"
},
@ -19,38 +19,24 @@
"url": "https://github.com/kern/filepizza/issues"
},
"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",
"filepizza-socket": "^1.0.0",
"newrelic": "^1.21.1",
"ioredis": "^4.17.3",
"next": "^9.5.3",
"nib": "^1.1.0",
"node-uuid": "^1.4.3",
"nodemon": "^1.4.1",
"peer-data": "^3.2.5",
"peer-data-server": "^1.0.10",
"peer": "^0.5.3",
"peerjs": "^1.3.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-frozenhead": "^0.3.0",
"react-google-analytics": "^0.2.0",
"react-peer-data": "^1.1.4",
"react-qr": "0.0.2",
"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",
"winston": "^1.0.1",
"xkcd-password": "^1.2.0"
},
"devDependencies": {
"@types/debug": "^4.1.5",
"@types/ioredis": "^4.17.4",
"@types/node": "^14.11.1",
"@types/react": "^16.9.49",
"@types/styled-components": "^5.1.3",

@ -0,0 +1,103 @@
import config from './config'
import Redis from 'ioredis'
export type Channel = {
uploaderPeerID: string
longSlug: string
shortSlug: string
}
export interface ChannelRepo {
create(uploaderPeerID: string, ttl?: number): Promise<Channel>
fetch(slug: string): Promise<Channel | null>
renew(slug: string, ttl: number): Promise<void>
destroy(slug: string): Promise<void>
}
export class RedisChannelRepo implements ChannelRepo {
client: Redis.Redis
constructor(redisURL: string) {
this.client = new Redis(redisURL)
}
async create(
uploaderPeerID: string,
ttl: number = config.channel.ttl,
): Promise<Channel> {
const shortSlug = await this.generateShortSlug()
const longSlug = await this.generateLongSlug()
const channel: Channel = {
uploaderPeerID,
longSlug,
shortSlug,
}
const channelStr = this.serializeChannel(channel)
await this.client.setex(this.getLongSlugKey(longSlug), ttl, channelStr)
await this.client.setex(this.getShortSlugKey(shortSlug), ttl, channelStr)
return channel
}
async fetch(slug: string): Promise<Channel | null> {
const shortChannelStr = await this.client.get(this.getShortSlugKey(slug))
if (shortChannelStr) {
return this.deserializeChannel(shortChannelStr)
}
const longChannelStr = await this.client.get(this.getLongSlugKey(slug))
if (longChannelStr) {
return this.deserializeChannel(longChannelStr)
}
return null
}
async renew(slug: string, ttl: number = config.channel.ttl): Promise<void> {
const channel = await this.fetch(slug)
if (!channel) {
return
}
await this.client.expire(this.getShortSlugKey(channel.shortSlug), ttl)
await this.client.expire(this.getLongSlugKey(channel.longSlug), ttl)
}
async destroy(slug: string): Promise<void> {
const channel = await this.fetch(slug)
if (!channel) {
return
}
await this.client.del(channel.longSlug)
await this.client.del(channel.shortSlug)
}
private async generateShortSlug(): Promise<string> {
return 'foo' // TODO
}
private async generateLongSlug(): Promise<string> {
return 'foo/bar/baz' // TODO
}
private getShortSlugKey(shortSlug: string): string {
return `short:${shortSlug}`
}
private getLongSlugKey(longSlug: string): string {
return `long:${longSlug}`
}
private serializeChannel(channel: Channel): string {
return JSON.stringify(channel)
}
private deserializeChannel(str: string): Channel {
return JSON.parse(str) as Channel
}
}
export const channelRepo = new RedisChannelRepo(config.redisURL)

@ -1,7 +1,6 @@
import React, { useRef, useEffect } from 'react'
import { usePeerData } from 'react-peer-data'
import { UploadedFile } from '../types'
import { Room } from 'peer-data'
import { useWebRTC } from './WebRTCProvider'
interface Props {
roomName: string
@ -10,42 +9,43 @@ interface Props {
const Uploader: React.FC<Props> = ({ roomName, files }: Props) => {
const room = useRef<Room | null>(null)
const peerData = usePeerData()
const peer = useWebRTC()
useEffect(() => {
room.current = peerData.connect(roomName)
room.current
.on('participant', (participant) => {
console.log(participant.getId() + ' joined')
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])
// 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])
const items = files.map((f) => <li key={f.fullPath}>{f.fullPath}</li>)
return <ul>{items}</ul>

@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react'
import { EventDispatcher } from 'peer-data'
import { PeerDataProvider } from 'react-peer-data'
import React, { useState, useEffect, useRef, useContext } from 'react'
import type { default as PeerType } from 'peerjs'
const dispatcher = new EventDispatcher()
const constraints = { ordered: true }
const signaling = { dispatcher }
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Peer = process.browser ? require('peerjs').default : null
export type WebRTCValue = PeerType | null
const ICE_SERVERS: RTCConfiguration = {
iceServers: [
@ -19,28 +19,38 @@ interface Props {
children?: React.ReactNode
}
const WebRTCContext = React.createContext<WebRTCValue>(null)
export const useWebRTC = (): WebRTCValue => {
return useContext(WebRTCContext)
}
export const WebRTCProvider: React.FC<Props> = ({
servers = ICE_SERVERS,
children,
}: Props) => {
const [pageLoaded, setPageLoaded] = useState(false)
const peer = useRef<WebRTCValue>(null)
useEffect(() => {
setPageLoaded(true)
const effect = async () => {
peer.current = new Peer(undefined, {
config: servers,
})
setPageLoaded(true)
}
effect()
}, [])
if (!pageLoaded) {
if (!pageLoaded || !peer.current) {
return null
}
return (
<PeerDataProvider
servers={servers}
constraints={constraints}
signaling={signaling}
>
<WebRTCContext.Provider value={peer.current}>
{children}
</PeerDataProvider>
</WebRTCContext.Provider>
)
}

@ -0,0 +1,6 @@
export default {
redisURL: 'redis://localhost:6379/0',
channel: {
ttl: 60 * 60, // 1 hour
},
}

@ -0,0 +1,19 @@
import type { Request, Response } from 'express'
import { channelRepo } from '../../channel'
export default (req: Request, res: Response): void => {
// TODO: validate method and uploaderPeerID
channelRepo
.create(req.body.uploaderPeerID)
.then((channel) => {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(channel))
})
.catch((err) => {
res.statusCode = 500
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ error: err.toString() }))
})
}

@ -0,0 +1,19 @@
import type { Request, Response } from 'express'
import { channelRepo } from '../../channel'
export default (req: Request, res: Response): void => {
// TODO: validate method and slug
channelRepo
.destroy(req.body.slug)
.then((channel) => {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(channel))
})
.catch((err) => {
res.statusCode = 500
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ error: err.toString() }))
})
}

@ -0,0 +1,19 @@
import type { Request, Response } from 'express'
import { channelRepo } from '../../channel'
export default (req: Request, res: Response): void => {
// TODO: validate method and slug
channelRepo
.renew(req.body.slug)
.then((channel) => {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(channel))
})
.catch((err) => {
res.statusCode = 500
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ error: err.toString() }))
})
}

@ -1,26 +0,0 @@
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')
})
})

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