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.
— W3C Web Application Manifest Spec

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.

Diagram

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": "/").