pull/164/head
Alex Kern 12 months ago
parent 9e7d78c755
commit de881ae4fb
No known key found for this signature in database
GPG Key ID: EF051FACCACBEE25

@ -5,33 +5,28 @@ import { setTurnCredentials } from '../../../coturn'
const turnHost = process.env.TURN_HOST || '127.0.0.1' const turnHost = process.env.TURN_HOST || '127.0.0.1'
export async function POST(): Promise<NextResponse> { export async function POST(): Promise<NextResponse> {
if (!process.env.COTURN_ENABLED) { if (!process.env.COTURN_ENABLED) {
return NextResponse.json({ return NextResponse.json({
iceServers: [ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
{ urls: 'stun:stun.l.google.com:19302' } })
] }
})
}
// Generate ephemeral credentials // Generate ephemeral credentials
const username = crypto.randomBytes(8).toString('hex') const username = crypto.randomBytes(8).toString('hex')
const password = crypto.randomBytes(8).toString('hex') const password = crypto.randomBytes(8).toString('hex')
const ttl = 86400 // 24 hours const ttl = 86400 // 24 hours
// Store credentials in Redis // Store credentials in Redis
await setTurnCredentials(username, password, ttl) await setTurnCredentials(username, password, ttl)
return NextResponse.json({ return NextResponse.json({
iceServers: [ iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun.l.google.com:19302' },
{ {
urls: [ urls: [`turn:${turnHost}:3478`, `turns:${turnHost}:5349`],
`turn:${turnHost}:3478`, username,
`turns:${turnHost}:5349` credential: password,
], },
username, ],
credential: password })
}
]
})
} }

@ -32,7 +32,7 @@ let globalPeer: Peer | null = null
async function getOrCreateGlobalPeer(): Promise<Peer> { async function getOrCreateGlobalPeer(): Promise<Peer> {
if (!globalPeer) { if (!globalPeer) {
const response = await fetch('/api/ice', { const response = await fetch('/api/ice', {
method: 'POST' method: 'POST',
}) })
const { iceServers } = await response.json() const { iceServers } = await response.json()
console.log('[WebRTCProvider] ICE servers:', iceServers) console.log('[WebRTCProvider] ICE servers:', iceServers)
@ -40,8 +40,8 @@ async function getOrCreateGlobalPeer(): Promise<Peer> {
globalPeer = new Peer({ globalPeer = new Peer({
debug: 3, debug: 3,
config: { config: {
iceServers iceServers,
} },
}) })
} }

@ -1,30 +1,34 @@
import crypto from 'crypto' import crypto from 'crypto'
import { getRedisClient } from './redisClient' import { getRedisClient } from './redisClient'
function generateHMACKey(username: string, realm: string, password: string): string { function generateHMACKey(
const str = `${username}:${realm}:${password}` username: string,
return crypto.createHash('md5').update(str).digest('hex') realm: string,
password: string,
): string {
const str = `${username}:${realm}:${password}`
return crypto.createHash('md5').update(str).digest('hex')
} }
export async function setTurnCredentials( export async function setTurnCredentials(
username: string, username: string,
password: string, password: string,
ttl: number ttl: number,
): Promise<void> { ): Promise<void> {
if (!process.env.COTURN_ENABLED) { if (!process.env.COTURN_ENABLED) {
return return
} }
const realm = process.env.TURN_REALM || 'file.pizza' const realm = process.env.TURN_REALM || 'file.pizza'
if (!realm) { if (!realm) {
throw new Error('TURN_REALM environment variable not set') throw new Error('TURN_REALM environment variable not set')
} }
const redis = getRedisClient() const redis = getRedisClient()
const hmacKey = generateHMACKey(username, realm, password) const hmacKey = generateHMACKey(username, realm, password)
const key = `turn/realm/${realm}/user/${username}/key` const key = `turn/realm/${realm}/user/${username}/key`
await redis.setex(key, ttl, hmacKey) await redis.setex(key, ttl, hmacKey)
} }

@ -188,7 +188,10 @@ export function useDownloader(uploaderPeerID: string): {
let nextFileIndex = 0 let nextFileIndex = 0
const startNextFileOrFinish = () => { const startNextFileOrFinish = () => {
if (nextFileIndex >= filesInfo.length) return if (nextFileIndex >= filesInfo.length) return
console.log('[Downloader] starting next file:', filesInfo[nextFileIndex].fileName) console.log(
'[Downloader] starting next file:',
filesInfo[nextFileIndex].fileName,
)
dataConnection.send({ dataConnection.send({
type: MessageType.Start, type: MessageType.Start,
fileName: filesInfo[nextFileIndex].fileName, fileName: filesInfo[nextFileIndex].fileName,

@ -24,20 +24,26 @@ export function useUploaderChannel(
const { isLoading, error, data } = useQuery({ const { isLoading, error, data } = useQuery({
queryKey: ['uploaderChannel', uploaderPeerID], queryKey: ['uploaderChannel', uploaderPeerID],
queryFn: async () => { queryFn: async () => {
console.log('[UploaderChannel] creating new channel for peer', uploaderPeerID) console.log(
'[UploaderChannel] creating new channel for peer',
uploaderPeerID,
)
const response = await fetch('/api/create', { const response = await fetch('/api/create', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ uploaderPeerID }), body: JSON.stringify({ uploaderPeerID }),
}) })
if (!response.ok) { if (!response.ok) {
console.error('[UploaderChannel] failed to create channel:', response.status) console.error(
'[UploaderChannel] failed to create channel:',
response.status,
)
throw new Error('Network response was not ok') throw new Error('Network response was not ok')
} }
const data = await response.json() const data = await response.json()
console.log('[UploaderChannel] channel created successfully:', { console.log('[UploaderChannel] channel created successfully:', {
longSlug: data.longSlug, longSlug: data.longSlug,
shortSlug: data.shortSlug shortSlug: data.shortSlug,
}) })
return data return data
}, },
@ -62,7 +68,10 @@ export function useUploaderChannel(
body: JSON.stringify({ slug: shortSlug, secret: s }), body: JSON.stringify({ slug: shortSlug, secret: s }),
}) })
if (!response.ok) { if (!response.ok) {
console.error('[UploaderChannel] failed to renew channel', response.status) console.error(
'[UploaderChannel] failed to renew channel',
response.status,
)
throw new Error('Network response was not ok') throw new Error('Network response was not ok')
} }
const data = await response.json() const data = await response.json()
@ -78,7 +87,11 @@ export function useUploaderChannel(
const run = (): void => { const run = (): void => {
timeout = setTimeout(() => { timeout = setTimeout(() => {
console.log('[UploaderChannel] scheduling channel renewal in', renewInterval, 'ms') console.log(
'[UploaderChannel] scheduling channel renewal in',
renewInterval,
'ms',
)
renewMutation.mutate({ secret }) renewMutation.mutate({ secret })
run() run()
}, renewInterval) }, renewInterval)

@ -34,14 +34,20 @@ export function useUploaderConnections(
const [connections, setConnections] = useState<Array<UploaderConnection>>([]) const [connections, setConnections] = useState<Array<UploaderConnection>>([])
useEffect(() => { useEffect(() => {
console.log('[UploaderConnections] initializing with', files.length, 'files') console.log(
'[UploaderConnections] initializing with',
files.length,
'files',
)
const cleanupHandlers: Array<() => void> = [] const cleanupHandlers: Array<() => void> = []
const listener = (conn: DataConnection) => { const listener = (conn: DataConnection) => {
console.log('[UploaderConnections] new connection from peer', conn.peer) console.log('[UploaderConnections] new connection from peer', conn.peer)
// If the connection is a report, we need to hard-redirect the uploader to the reported page to prevent them from uploading more files. // If the connection is a report, we need to hard-redirect the uploader to the reported page to prevent them from uploading more files.
if (conn.metadata?.type === 'report') { if (conn.metadata?.type === 'report') {
console.log('[UploaderConnections] received report connection, redirecting') console.log(
'[UploaderConnections] received report connection, redirecting',
)
// Broadcast report message to all connections // Broadcast report message to all connections
connections.forEach((c) => { connections.forEach((c) => {
c.dataConnection.send({ c.dataConnection.send({
@ -85,7 +91,9 @@ export function useUploaderConnections(
console.log('[UploaderConnections] client info:', { console.log('[UploaderConnections] client info:', {
browser: `${message.browserName} ${message.browserVersion}`, browser: `${message.browserName} ${message.browserVersion}`,
os: `${message.osName} ${message.osVersion}`, os: `${message.osName} ${message.osVersion}`,
mobile: message.mobileVendor ? `${message.mobileVendor} ${message.mobileModel}` : 'N/A' mobile: message.mobileVendor
? `${message.mobileVendor} ${message.mobileModel}`
: 'N/A',
}) })
const newConnectionState = { const newConnectionState = {
browserName: message.browserName, browserName: message.browserName,
@ -97,7 +105,9 @@ export function useUploaderConnections(
} }
if (password) { if (password) {
console.log('[UploaderConnections] password required, requesting authentication') console.log(
'[UploaderConnections] password required, requesting authentication',
)
const request: Message = { const request: Message = {
type: MessageType.PasswordRequired, type: MessageType.PasswordRequired,
} }
@ -206,7 +216,12 @@ export function useUploaderConnections(
case MessageType.Start: { case MessageType.Start: {
const fileName = message.fileName const fileName = message.fileName
let offset = message.offset let offset = message.offset
console.log('[UploaderConnections] starting transfer of', fileName, 'from offset', offset) console.log(
'[UploaderConnections] starting transfer of',
fileName,
'from offset',
offset,
)
const file = validateOffset(files, fileName, offset) const file = validateOffset(files, fileName, offset)
const sendNextChunkAsync = () => { const sendNextChunkAsync = () => {
@ -226,7 +241,14 @@ export function useUploaderConnections(
updateConnection((draft) => { updateConnection((draft) => {
offset = end offset = end
if (final) { if (final) {
console.log('[UploaderConnections] completed file', fileName, '- file', draft.completedFiles + 1, 'of', draft.totalFiles) console.log(
'[UploaderConnections] completed file',
fileName,
'- file',
draft.completedFiles + 1,
'of',
draft.totalFiles,
)
return { return {
...draft, ...draft,
status: UploaderConnectionStatus.Ready, status: UploaderConnectionStatus.Ready,
@ -288,7 +310,9 @@ export function useUploaderConnections(
} }
case MessageType.Done: { case MessageType.Done: {
console.log('[UploaderConnections] transfer completed successfully') console.log(
'[UploaderConnections] transfer completed successfully',
)
updateConnection((draft) => { updateConnection((draft) => {
if (draft.status !== UploaderConnectionStatus.Ready) { if (draft.status !== UploaderConnectionStatus.Ready) {
return draft return draft

@ -5,8 +5,8 @@ export { Redis }
let redisClient: Redis.Redis | null = null let redisClient: Redis.Redis | null = null
export function getRedisClient(): Redis.Redis { export function getRedisClient(): Redis.Redis {
if (!redisClient) { if (!redisClient) {
redisClient = new Redis(process.env.REDIS_URL) redisClient = new Redis(process.env.REDIS_URL)
} }
return redisClient return redisClient
} }
Loading…
Cancel
Save