diff --git a/docker/Caddyfile b/docker/Caddyfile index db0432a..779ff0c 100644 --- a/docker/Caddyfile +++ b/docker/Caddyfile @@ -1,5 +1,6 @@ { auto_https off + https_port 8443 } :80 { diff --git a/docker/Dockerfile b/docker/Dockerfile index 64349f2..3e135fe 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,9 +1,10 @@ -FROM golang:1.24-alpine AS go-builder +FROM golang:1.26.4-alpine AS go-builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY cmd/ cmd/ COPY internal/ internal/ +COPY docs/ docs/ RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/dumpster-server ./cmd/server FROM node:22-alpine AS web-builder diff --git a/docker/compose.yml b/docker/compose.yml index ce5c14a..385536d 100644 --- a/docker/compose.yml +++ b/docker/compose.yml @@ -94,25 +94,29 @@ services: image: coturn/coturn:latest container_name: dumpster-coturn command: - - "-n" - "--log-file=stdout" - "--min-port=50101" - "--max-port=50200" - "--realm=${DUMPSTER_HOST:-localhost}" - "--user=${LIVEKIT_API_KEY:-devkey}:${LIVEKIT_API_SECRET:-secret}" + - "--cert=/etc/coturn/certs/turn.crt" + - "--pkey=/etc/coturn/certs/turn.key" + - "--tls-listening-port=5349" ports: - "3478:3478/tcp" - "3478:3478/udp" - "5349:5349/tcp" - "5349:5349/udp" - "50101-50200:50101-50200/udp" + volumes: + - ./certs:/etc/coturn/certs:ro caddy: image: caddy:2 container_name: dumpster-caddy ports: - "80:80" - - "443:443" + - "8443:8443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - web_assets:/srv/web:ro diff --git a/dumpster b/dumpster new file mode 100755 index 0000000..f0acb8b Binary files /dev/null and b/dumpster differ diff --git a/internal/middleware/security.go b/internal/middleware/security.go index e242189..d81207b 100644 --- a/internal/middleware/security.go +++ b/internal/middleware/security.go @@ -13,7 +13,7 @@ func SecurityHeaders(next http.Handler) http.Handler { w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; "+ "img-src 'self' https: data:; connect-src 'self' wss: ws:; font-src 'self';") - w.Header().Set("Permissions-Policy", "camera=(), microphone=(self), geolocation=()") + w.Header().Set("Permissions-Policy", "camera=(self), microphone=(self), geolocation=()") next.ServeHTTP(w, r) }) } diff --git a/web/package-lock.json b/web/package-lock.json index 404f184..a3deaf1 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1003,9 +1003,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1020,9 +1017,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1037,9 +1031,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1054,9 +1045,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1071,9 +1059,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1088,9 +1073,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1105,9 +1087,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1122,9 +1101,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1139,9 +1115,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1156,9 +1129,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1173,9 +1143,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1190,9 +1157,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1207,9 +1171,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/web/src/components/AudioRenderers.tsx b/web/src/components/AudioRenderers.tsx new file mode 100644 index 0000000..355b1a4 --- /dev/null +++ b/web/src/components/AudioRenderers.tsx @@ -0,0 +1,73 @@ +import { useEffect, useRef } from 'react'; +import { useVoiceStore } from '../stores/voice.ts'; +import { Track } from 'livekit-client'; +import type { Participant, TrackPublication, Room } from 'livekit-client'; + +function RemoteAudioTrack({ participant, room }: { participant: Participant; room: Room }) { + const audioRef = useRef(null); + + useEffect(() => { + const el = audioRef.current; + if (!el) return; + + let pub: TrackPublication | undefined; + + const attachTrack = () => { + pub = participant.getTrackPublication(Track.Source.Microphone); + if (pub?.track && el) { + pub.track.attach(el); + el.play().catch(() => {}); + } + }; + + const detachTrack = () => { + if (pub?.track && el) { + pub.track.detach(el); + } + }; + + attachTrack(); + + const handlePublished = (publication: TrackPublication) => { + if (publication.source === Track.Source.Microphone && publication.track) { + publication.track.attach(el); + el.play().catch(() => {}); + } + }; + + room.on('trackPublished' as any, handlePublished); + + // If a track is subscribed later + const handleSubscribed = (track: Track, publication: TrackPublication, trackParticipant: Participant) => { + if (trackParticipant.identity === participant.identity && publication.source === Track.Source.Microphone) { + track.attach(el); + el.play().catch(() => {}); + } + }; + room.on('trackSubscribed' as any, handleSubscribed); + + return () => { + detachTrack(); + room.off('trackPublished' as any, handlePublished); + room.off('trackSubscribed' as any, handleSubscribed); + }; + }, [participant, room]); + + return