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' version: '3'
services: services:
filepizza: redis:
image: kern/filepizza:latest image: redis:latest
restart: always
build:
context: .
ports: ports:
- 3333:3333 - 6379:6379
environment: # coturn:
- PORT=3333 # image: instrumentisto/coturn:latest
- EXTRA_ICE_SERVERS=turn:localhost:3478 # network_mode: host
- WEBTORRENT_TRACKERS=ws://localhost:8000 # ports:
coturn: # - 3478:3478
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

@ -7,7 +7,7 @@
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"homepage": "https://github.com/kern/filepizza", "homepage": "https://github.com/kern/filepizza",
"scripts": { "scripts": {
"dev": "node src/server.js", "dev": "next",
"build": "next build", "build": "next build",
"start": "next start" "start": "next start"
}, },
@ -19,38 +19,24 @@
"url": "https://github.com/kern/filepizza/issues" "url": "https://github.com/kern/filepizza/issues"
}, },
"dependencies": { "dependencies": {
"alt": "^0.14.4",
"classnames": "^1.2.0",
"debug": "^4.2.0", "debug": "^4.2.0",
"express": "^4.12.0", "express": "^4.12.0",
"express-force-ssl": "^0.3.1", "ioredis": "^4.17.3",
"express-winston": "^0.3.1",
"filepizza-socket": "^1.0.0",
"newrelic": "^1.21.1",
"next": "^9.5.3", "next": "^9.5.3",
"nib": "^1.1.0",
"node-uuid": "^1.4.3",
"nodemon": "^1.4.1", "nodemon": "^1.4.1",
"peer-data": "^3.2.5", "peer": "^0.5.3",
"peer-data-server": "^1.0.10", "peerjs": "^1.3.1",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^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-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", "styled-components": "^5.2.0",
"stylus": "^0.52.4",
"twilio": "^2.9.1", "twilio": "^2.9.1",
"webrtcsupport": "^2.2.0", "webrtcsupport": "^2.2.0",
"winston": "^1.0.1",
"xkcd-password": "^1.2.0" "xkcd-password": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/debug": "^4.1.5", "@types/debug": "^4.1.5",
"@types/ioredis": "^4.17.4",
"@types/node": "^14.11.1", "@types/node": "^14.11.1",
"@types/react": "^16.9.49", "@types/react": "^16.9.49",
"@types/styled-components": "^5.1.3", "@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 React, { useRef, useEffect } from 'react'
import { usePeerData } from 'react-peer-data'
import { UploadedFile } from '../types' import { UploadedFile } from '../types'
import { Room } from 'peer-data' import { useWebRTC } from './WebRTCProvider'
interface Props { interface Props {
roomName: string roomName: string
@ -10,42 +9,43 @@ interface Props {
const Uploader: React.FC<Props> = ({ roomName, files }: Props) => { const Uploader: React.FC<Props> = ({ roomName, files }: Props) => {
const room = useRef<Room | null>(null) const room = useRef<Room | null>(null)
const peerData = usePeerData() const peer = useWebRTC()
useEffect(() => { // useEffect(() => {
room.current = peerData.connect(roomName) // room.current = peerData.connect(roomName)
room.current // room.current
.on('participant', (participant) => { // .on('participant', (participant) => {
console.log(participant.getId() + ' joined') // console.log(participant.getId() + ' joined')
// participant.newDataChannel()
participant //
.on('connected', () => { // participant
console.log('connected', participant.id) // .on('connected', () => {
}) // console.log('connected', participant.id)
.on('disconnected', () => { // })
console.log('disconnected', participant.id) // .on('disconnected', () => {
}) // console.log('disconnected', participant.id)
.on('track', (event) => { // })
console.log('stream', participant.id, event.streams[0]) // .on('track', (event) => {
}) // console.log('stream', participant.id, event.streams[0])
.on('message', (payload) => { // })
console.log(participant.id, payload) // .on('message', (payload) => {
}) // console.log(participant.id, payload)
.on('error', (event) => { // })
console.error('peer', participant.id, event) // .on('error', (event) => {
participant.renegotiate() // console.error('peer', participant.id, event)
}) // participant.renegotiate()
participant.send(`hello there, I'm the uploader`) // })
}) // participant.send(`hello there, I'm the uploader`)
.on('error', (event) => { // })
console.error('room', roomName, event) // .on('error', (event) => {
}) // console.error('room', roomName, event)
// })
return () => { //
room.current.disconnect() // return () => {
room.current = null // room.current.disconnect()
} // room.current = null
}, [peerData]) // }
// }, [peerData])
const items = files.map((f) => <li key={f.fullPath}>{f.fullPath}</li>) const items = files.map((f) => <li key={f.fullPath}>{f.fullPath}</li>)
return <ul>{items}</ul> return <ul>{items}</ul>

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