feat(live): discord activity — assistir e comentar a live de dentro do discord - #532
Draft
tecrodrigocastro wants to merge 9 commits into
Draft
feat(live): discord activity — assistir e comentar a live de dentro do discord#532tecrodrigocastro wants to merge 9 commits into
tecrodrigocastro wants to merge 9 commits into
Conversation
Resolve User a partir de provider+external_account_id sem criar identidade.
…auth Fluxo de authorize() de Activity não usa redirect_uri, diferente do login web.
… activity TrustProxies local, middleware de sessão e listener que relativiza URLs pra CSP.
…idade vinculada Troca code por token, resolve o User já conectado e loga a sessão da Activity.
LiveActivityPage sem navbar, SDK do Discord no client, rota / decide por frame_id.
CSP do iframe só permite mídia da própria origem; repassa pro mediamtx por trás.
Read/playback já eram sempre liberados; round-trip redundante travava o dev single-thread.
Mesmo app do developer portal serve o login web e a discord activity.
Pedido do Clinton no review — mesmo padrão de outro módulo.
| // Local, testando via túnel (cloudflared/ngrok), a conexão chega via loopback, | ||
| // fora dos IPs publicados da Cloudflare: sem confiar em '*', o Laravel não vê | ||
| // X-Forwarded-Proto e gera url()/asset() em http://, que o túnel recusa. | ||
| if (env('APP_ENV') === 'local') { |
Member
There was a problem hiding this comment.
Suggested change
| if (env('APP_ENV') === 'local') { | |
| if(app()->isLocal()){ |
hefeus
reviewed
Aug 30, 2026
| Route::post('live/ingest/webhook', IngestWebhookController::class)->name('live.ingest-webhook'); | ||
|
|
||
| Route::get('discord-activity/hls/{path}', HlsProxyController::class) | ||
| ->where('path', '.*') |
Contributor
There was a problem hiding this comment.
->where('path', '[A-Za-z0-9._-]+.(m3u8|mp4|ts|m4s)')
Definir um padrão de entrada evita possíveis ataques direto por essa rota.
hefeus
reviewed
Aug 30, 2026
| // sem cookie jar, o Guzzle não carrega o Set-Cookie entre os hops do redirect e | ||
| // o hook de auth do mediamtx rejeita a requisição seguinte. As sub-playlists | ||
| // (áudio/vídeo) vêm com `?session=...` no manifest — repassa a query também. | ||
| $upstream = Http::withOptions(['cookies' => new CookieJar]) |
Contributor
There was a problem hiding this comment.
Esse Http::get() bufferiza o corpo inteiro em memória e segura o worker do FPM durante todo o download do segmento. Dá pra tirar o PHP do caminho quente com um proxy_pass no nginx para /discord-activity/hls/ direto no mediamtx.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contexto
Em cima do MVP de live (#530), abre a mesma experiência (player + chat) como Discord
Activity — o mini-app embutido no painel de voz do Discord. Nenhum arquivo do #530 foi
alterado; tudo aqui é composição por cima dos componentes que já existem.
Duas descobertas moldaram a implementação:
/— o Discord não permite apontar praoutra rota, então a detecção de contexto acontece na própria rota raiz do portal.
discordsays.com(terceiro pro documento top-level doDiscord) com CSP restrita a
'self'— exigiu ajuste de sessão (SameSite=None) ereescrita de URLs absolutas pra relativas.
Alterações
Módulo
identityFindConnectedUser: resolveUsera partir de identidade externa já vinculada, sóleitura — diferente de
ResolveExternalIdentity, nunca cria identidade.Módulo
integration-discordredirectUrideExchangeCodeForTokenvira opcional: oauthorize()do SDK daActivity troca
codepor token semredirect_uri, diferente do login web.Activity/:AuthenticateActivityUser(troca code → token → resolve User vinculado),ActivityAuthResult(DTO),ActivityAuthController+ rotaPOST /discord-activity/auth,isolada das rotas de auth existentes.
Módulo
liveHlsProxyController+ rotaGET /discord-activity/hls/{path}: repassa o HLS domediamtx pelo próprio domínio da app — a CSP do iframe só permite mídia da própria origem.
mediamtx.yml: excluiread/playbackdo hook de auth HTTP — já eram sempre liberadosno código; chamar o hook mesmo assim criava round-trip redundante e, no servidor de dev
single-threaded, um deadlock real.
Módulo
portalLiveActivityPage+live-activity.blade.php+ layoutactivity.blade.php: versãoenxuta da live (player + chat, sem navbar) servida dentro do iframe do Discord.
discord-activity.js: bootstrap do@discord/embedded-app-sdk(ready()→authorize()→ troca de código no backend →authenticate()), com interceptação delinks pra abrir fora do iframe (sandboxed sem
allow-popups)./doPortalServiceProviderdecide entreHomepageeLiveActivityPageconforme o query param
frame_idque o SDK injeta.App (transversal)
PrepareDiscordActivityContext(middleware): ajustasession.same_sitepranonenocontexto da Activity — sem isso o cookie de sessão não sobrevive no iframe de terceiros.
RewriteDiscordActivityAssetUrls(listener deRequestHandled): reescreve URLsabsolutas pro próprio host como relativas na resposta — precisa rodar depois que o
Livewire injeta seu script, que só acontece depois de toda a pipeline de middleware.
bootstrap/app.php:TrustProxiesconfia em qualquer proxy só quandoAPP_ENV=local— necessário pra testar via túnel (cloudflared/ngrok), zero efeito em produção.
Fora desta v1 (follow-up)
mesma CSP — precisaria de um segundo túnel/proxy em produção). Hoje: manda mensagem,
não vê a de quem está fora até recarregar.
via
openExternalLinkdo SDK).discordsays.comvaria por app).
Plano de Testes
vendor/bin/pint --dirty --format agentvendor/bin/phpstan analyse --memory-limit=2G— 0 errosphp artisan test --compact— 1730/1732 testes, 5926 assertions (as 2 falhas sãopré-existentes em
CommunityRetrospectivePageTest, não relacionadas — confirmadorevertendo os arquivos desta PR e reproduzindo a mesma falha)
de chat e vínculo de conta Discord funcionando
Issues Relacionadas
Nenhuma issue vinculada.