Arquitetura PWA Multi-Tenant Soberana & Ícones Dinâmicos
O ecossistema Yzex Connect opera como uma plataforma multi-tenant white-label onde múltiplos parceiros corporativos (ex.: Rocketz, Yzex) servem a mesma aplicação frontend sob domínios customizados soberanos (app.rocketz.me, connect.yzex.com.br).
Este documento descreve as decisões de arquitetura e mitigação de colisões adotadas para garantir a instalação soberana de Progressive Web Apps (PWA) em dispositivos móveis e desktops.
1. Causa Raiz da Colisão de Aplicações no Android
A especificação W3C Web App Manifest define o campo id como a identidade única global de uma aplicação instalada.
The id member is a string that represents the identity of the web application. When the identity of the web application changes, the user agent might treat the web application as a new application.
Nas implementações estáticas convencionais:
1. O id era fixado globalmente como "com.yzex.connect.app".
2. Como resultado, quando um cliente instalava a aplicação via app.rocketz.me e posteriormente via connect.yzex.com.br, o Android e o Chromium identificavam o mesmo id, provocando a sobreposição do aplicativo anterior ou o bloqueio silencioso da instalação.
3. Além disso, a troca dinâmica de manifesto client-side através de JavaScript (manifestLink.href = '/site-' + theme + '.webmanifest') é ignorada pelo motor de parsing do Chromium durante o ciclo de instalação nativa, além de multiplicar indevidamente as entradas de cache na CDN.
2. Topologia da Solução no Nitro Server
A resolução adota uma rota canônica de primeiro nível em conformidade com W3C e auditorias PWA/Lighthouse, associada a um gerador de ícones blindado contra ataques de esgotamento e SSRF.
3. Decisões Arquiteturais Fundamentais
3.1 Identidade Canônica Soberana ("id": "/")
Ao definir "id": "/", a URL de identidade é resolvida relativamente ao origin da requisição (https://app.rocketz.me/ vs https://connect.yzex.com.br/). Essa estratégia:
* Garante isolamento estrito de instalação no sistema operacional móvel sem colisões.
* Mantém a identidade estável caso o nome comercial da marca parceira sofra atualizações cadastrais.
* Segue a recomendação canônica do MDN Web Docs e W3C para aplicações de raiz de domínio.
3.2 Estabilidade de Tema & Eliminação da Bipolaridade
O manifesto é emitido com cores de casca neutras e fixas (#131314), prevenindo condições de corrida na renderização da splash screen nativa. A alternância entre modos claro e escuro é delegada inteiramente à camada de renderização da aplicação via HTML, atributos data-theme e a metatag <meta name="theme-color">.
3.3 Safe Zone em Ícones Adaptativos (Maskable)
Ícones com purpose="maskable" são submetidos a recortes circulares, squircle ou arredondados dependendo da fabricante do dispositivo (Samsung OneUI, Google Pixel Launcher, Xiaomi MIUI).
Para prevenir mutilação da identidade visual do parceiro:
* O logotipo ou monograma é circunscrito estritamente dentro de um diâmetro de 80% a partir do centro (raio de 40%).
* O fundo é obrigatoriamente preenchido com cor sólida opaca (#1E1F20 ou cor primária corporativa), impedindo transparências indesejadas sob a máscara do launcher.
3.4 Desacoplamento de Cache via Versionamento de Branding
Para evitar que alterações visuais fiquem represadas pelo TTL longo de cache da CDN (s-maxage=86400), os links declarados no manifesto utilizam URLs parametrizadas com a versão do branding (/api/pwa-icon?size=192&v=${branding_version}). Quando a marca altera seu logotipo, o manifesto gera uma nova URL de ícone, contornando o cache antigo de forma determinística e imediata.
4. Resolução em Ambiente de Desenvolvimento Local & Testes Multi-Tenant
Durante o desenvolvimento local (http://localhost:3000), desenvolvedores alternam entre identidades de parceiros utilizando o parâmetro de consulta ?domain=app.rocketz.me.
4.1 O Desafio da Desconexão entre Requisições do Navegador
Diferente de requisições de página comuns, o ciclo de ciclo de vida do PWA envolve requisições secundárias independentes disparadas pelo motor do navegador:
1. O navegador carrega a página HTML em http://localhost:3000/…/?domain=app.rocketz.me.
2. O navegador lê a tag <link rel="manifest"> e dispara uma requisição HTTP isolada para baixar o manifesto.
3. O navegador lê a lista de ícones dentro do manifesto e dispara requisições HTTP isoladas para baixar os PNGs.
Se o <link rel="manifest"> apontar estaticamente para /manifest.webmanifest e os ícones apontarem para /api/pwa-icon?size=…:
* As requisições secundárias chegam ao servidor com cabeçalho Host: localhost:3000 e sem o parâmetro ?domain=.
* O servidor identifica o host como pertencente a CORE_DOMAINS e retorna a identidade padrão da Yzex Connect, quebrando a experiência de teste local.
4.2 Arquitetura de Propagação em Cascata
Para garantir fidelidade absoluta durante testes locais sem alterar o comportamento em produção:
1. Injeção Dinâmica no Shell (app/app.vue):
* O composable detecta route.query.domain.
* Quando presente, o link do manifesto é computado como +/manifest.webmanifest?domain=${encodeURIComponent(domain)}+ e o ícone Apple Touch como +/api/pwa-icon?size=180&purpose=any&domain=${encodeURIComponent(domain)}+.
2. Propagação Interna no Manifesto (server/routes/manifest.webmanifest.ts):
* Em ambiente de desenvolvimento ou testes (isDevOrTest = true), o endpoint extrai query.domain.
* O parâmetro &domain=… é propagado para todos os src de ícones no array manifest.icons.
* O id e o start_url são ajustados para +/?domain=${domain}+, permitindo que o Chromium instale múltiplos PWAs de teste simultaneamente sob localhost:3000 sem conflitos de chave primária no registro local.
3. Imunidade em Produção:
* Em produção, o parâmetro ?domain= é categoricamente ignorado pelo extractOgHost, e a resolução baseia-se estritamente no cabeçalho Host RFC 1123 confiável da requisição, garantindo isolamento total por origem soberana ("id": "/").