Câmera (getUserMedia) trava indefinidamente ao debugar apps mobile no Service Studio/ODC Studio para macOS — causa raiz: falta de entitlement de câmera no app
Produto / Componente
Service Studio para macOS / ODC Studio para macOS — Debug de aplicações mobile (alvo "Chrome (emulated)")
Ambiente
- macOS: 27.0 (build 26A5378j)
- Service Studio: 11.55.78.64991
- ODC Studio: 1.7.14.10418
- Chrome: 150.0.7871.115 (única instalação no sistema, sem Beta/Dev/Canary)
- Máquina: MacBook pessoal (não gerenciada por MDM), câmera integrada (MacBook Pro Camera)
Resumo do problema
Ao iniciar uma sessão de debug no Service Studio/ODC Studio com o alvo "Chrome (emulated)", qualquer chamada a navigator.mediaDevices.getUserMedia() na página fica pendente para sempre — a Promise nunca resolve nem rejeita. Não aparece nenhum prompt de permissão de câmera, nenhum erro no console, e chrome://media-internals → aba "Players" nunca registra sessão de captura alguma (fica "No media player logs" indefinidamente).
O mesmo fluxo/URL funciona perfeitamente quando aberto manualmente em uma janela normal do Chrome (fora do Service Studio) — câmera abre e renderiza sem problemas.
Impacto
Não é possível debugar (breakpoints, inspeção de rede/console via Service Studio) nenhuma tela que dependa de câmera/getUserMedia — inclui fluxos de reconhecimento facial, liveness, upload de foto via captura direta, etc. — usando o Chrome (emulated) integrado ao Service Studio para macOS.
Passos para reproduzir
- Abrir um módulo mobile/reactive com uma tela que chama getUserMedia (ex.: captura de câmera para liveness).
- Na aba Debug Setup, com "Entry Module: (this module)" e browser "Chrome (emulated)" (única opção disponível), clicar em Start debugging.
- Navegar até a tela que solicita a câmera.
- Observar: nenhum prompt de permissão aparece; a tela fica travada (spinner/preto); nenhum erro no console.
Investigação já realizada (causa raiz identificada)
Passamos por um processo de eliminação completo antes de chegar à causa raiz — resumo dos testes:
Verificado que getUserMedia é de fato chamado (interceptado via console) — a Promise fica em estado pending para sempre, sem resolver nem rejeitar.
chrome://media-internals → Players: sempre vazio ("No media player logs") durante o travamento — a chamada nunca chega a criar uma sessão de captura rastreável.
Descartado: constraints de câmera — constraints usadas (640x480 ideal) são suportadas pelo hardware (confirmado em chrome://media-internals → Video Capture: câmera enumerada corretamente via AV Foundation, múltiplas resoluções).
Descartado: iframe / Permissions Policy — elemento de câmera não está em iframe.
Descartado: perfil efêmero / modo Convidado (--bwsi) isoladamente — reproduzimos a linha de comando exata usada pelo Service Studio (capturada via ps aux durante uma sessão de debug ativa):
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome \ --remote-allow-origins=* --ignore-certificate-errors --no-default-browser-check \ --no-first-run --profile-directory=Default --bwsi --test-type \ --remote-debugging-port=9222 --user-data-dir=/ChromiumAdapterTemp/ \ --disable-search-engine-choice-screen
Ao lançar essa mesma linha de comando manualmente pelo Terminal (usuário, não Service Studio) — incluindo --bwsi, --remote-debugging-port (CDP) e perfil novo em $TMPDIR — a câmera funcionou normalmente em todos os testes isolados e combinados. Ou seja, nenhuma flag do Chrome usada pelo Service Studio é, por si só, a causa.
Descartado: política/permissão de câmera — testamos inclusive forçar auto-concessão via política gerenciada do Chrome (VideoCaptureAllowedUrls, aplicada e confirmada em chrome://policy com status OK dentro da própria instância do Service Studio) — não teve efeito; media-internals continuou sem nenhuma sessão registrada.
Causa raiz encontrada — falta de entitlement de câmera no processo pai: Inspecionamos a assinatura de código dos apps instalados:
codesign -d --entitlements :- /Applications/ServiceStudio.appcodesign -d --entitlements :- "/Applications/ODC Studio.app"
Resultado: nenhum dos dois declara NSCameraUsageDescription no Info.plist, e nenhum dos dois tem o entitlement com.apple.security.device.camera. Ambos rodam sob Hardened Runtime (com.apple.security.cs.allow-jit, allow-unsigned-executable-memory, allow-dyld-environment-variables), sem App Sandbox completo.
Quando o Service Studio/ODC Studio lança o Chrome como processo filho para o debug ("Chrome (emulated)"), o macOS TCC (Transparency, Consent and Control) atribui a decisão de autorização de câmera ao processo responsável (o próprio Service Studio/ODC Studio, via o mecanismo de "responsible process"). Como esse app nunca declarou intenção de uso de câmera nem tem o entitlement correspondente, o TCC não tem como resolver o pedido do processo filho — não autoriza, não nega, não mostra prompt algum. A Promise do getUserMedia() fica pendente indefinidamente.
Isso explica por que a mesma URL, com as mesmas flags de Chrome, funciona quando lançada pelo usuário (via Terminal ou Dock) — nesses casos o processo responsável não é o Service Studio, então não há a mesma lacuna de entitlement na cadeia.
Bundle IDs afetados
- com.outsystems.servicestudio.launcher (ServiceStudio.app)
- com.outsystems.odcstudio.launcher (ODC Studio.app)
Correção solicitada
Adicionar ao build macOS do Service Studio / ODC Studio:
- NSCameraUsageDescription (e idealmente NSMicrophoneUsageDescription, caso fluxos de vídeo com áudio também sejam afetados) no Info.plist
- O entitlement com.apple.security.device.camera (e com.apple.security.device.microphone se aplicável) na assinatura de código
para que o processo Chrome filho, lançado para debug ("Chrome (emulated)"), consiga negociar corretamente a autorização de câmera junto ao macOS TCC.
Workaround atual (client-side, sem alteração do produto)
Não há workaround dentro do Service Studio. Para continuar testando telas dependentes de câmera enquanto o fix não sai: usar "Chrome (emulated)" apenas para a lógica antes/depois da tela de câmera; para a tela em si, copiar a URL gerada pelo debug e abrir manualmente em uma janela de Chrome iniciada pelo próprio usuário (fora do Service Studio) — confirmado funcionando em todos os testes, mas perde a sincronia de breakpoints visuais do Service Studio nessa tela específica.