Updatw 1156

This commit is contained in:
2026-07-30 17:32:20 -03:00
parent 7bc146421a
commit dd7e2dd060
+359 -27
View File
@@ -65,6 +65,16 @@ Não coloque código de produção abaixo desse marcador. O app trata isso como
O app injeta o bridge de compatibilidade antes de `</head>` quando o HTML contém uma tag `head`. Se o HTML não tiver `</head>`, o bridge é colocado no começo do documento.
### 2.1 Cada objeto nativo pode não existir
Nesta versão, todo objeto `Cake*` só é registrado no WebView se a camada nativa autorizar aquele nome (`SecureOnlineNative.isBridgeAllowed`). Um APK pode ser publicado com parte do bridge desativada.
Consequências práticas para o tema:
- Um objeto bloqueado fica `undefined`, e o alias `Dt*` correspondente também, porque o alias é criado como `window.DtX = window.DtX || window.CakeX`.
- `CakeApp.*` é injetado sempre, mas os métodos dele chamam os objetos nativos diretamente. Se o objeto estiver bloqueado, a chamada lança erro.
- Por isso, sempre use `try/catch` e checagem de tipo, como nos helpers da seção 1, tanto para `Dt*` quanto para `CakeApp.*`.
---
## 3. Esqueleto rápido de tema
@@ -300,14 +310,25 @@ O helper `CakeApp` injetado nesta versão expõe somente `CakeApp.vpn`, `CakeApp
| API | Assinatura | Retorno | Finalidade |
|---|---:|---|---|
| `DtUsername.get()` | sem argumentos | string | Retorna o usuário manual salvo, ou `Locked` quando a config selecionada não precisa de credenciais SSH manuais. |
| `DtUsername.set(value)` | string | void | Salva o usuário somente se credenciais manuais forem necessárias. Ignorado se estiver bloqueado. |
| `DtPassword.get()` | sem argumentos | string | Retorna a senha manual salva, ou `Locked` quando não for necessária. |
| `DtPassword.set(value)` | string | void | Salva a senha somente se credenciais manuais forem necessárias. Ignorado se estiver bloqueado. |
| `DtUuid.get()` | sem argumentos | string | Retorna o UUID manual V2Ray/Xray salvo, ou `Locked` quando a config selecionada não precisa de UUID manual. |
| `DtUuid.set(value)` | string | void | Salva o UUID. Nesta versão do app, escreve o valor do UUID e mantém cache para futuras seleções de config. |
| `DtUsername.get()` | sem argumentos | string | Retorna o usuário manual salvo, ou **string vazia** quando a config selecionada não aceita usuário manual. |
| `DtUsername.set(value)` | string | void | Sempre guarda o valor no cache manual do OnlineConfig. Só grava nas preferências do perfil se credenciais manuais forem aceitas. |
| `DtPassword.get()` | sem argumentos | string | Retorna a senha manual salva, ou **string vazia** quando não for aceita. |
| `DtPassword.set(value)` | string | void | Mesmo comportamento de `DtUsername.set`: cache sempre, gravação somente se aceito. |
| `DtUuid.get()` | sem argumentos | string | Retorna o UUID manual V2Ray/Xray salvo, ou **string vazia** quando a config selecionada não aceita UUID manual. |
| `DtUuid.set(value)` | string | void | Sempre guarda o UUID no cache manual. Só grava nas preferências se o perfil aceitar UUID manual. |
Importante: limpe `Locked` antes de colocar valores em inputs.
Importante nesta versão: **o bridge não retorna mais o texto `Locked`**. Quando o input manual não é aceito, `get()` retorna string vazia (`""`).
O que decide se o input manual é aceito:
| Getter/Setter | Aceito quando |
|---|---|
| `DtUsername`, `DtPassword` | O payload do perfil veio sem `username`/`password` (o app marca credenciais manuais como obrigatórias) **ou** o perfil do catálogo declara `auth.username: true` ou `auth.password: true`. |
| `DtUuid` | O perfil é Xray/V2Ray (`tunnelType 6`) sem UUID no payload **ou** o perfil do catálogo declara `auth.v2ray_uuid: true`. |
Como o cache manual é sempre gravado, `DtUsername.set()` / `DtPassword.set()` / `DtUuid.set()` chamados antes da seleção do servidor continuam valendo: quando o perfil for aplicado e pedir credencial manual, o app reaproveita o último valor em cache.
O helper de limpeza abaixo continua recomendado, porque trata string vazia, `null` e também o antigo `Locked` de temas migrados de outros apps:
```js
function cleanBridgeValue(value) {
@@ -341,6 +362,8 @@ Possíveis estados de `DtGetVpnState` / `DtVpnStateListener`:
| `NO_NETWORK` | Aguardando rede. |
| `AUTH_FAILED` | Falha de autenticação. |
Atenção: o estado nativo `RECONECTANDO` também é mapeado para `CONNECTING`. Nesta versão, a reconexão automática é silenciosa e mantém a VPN/TUN ativa, então `CONNECTING` depois de um `CONNECTED` **não** significa que o usuário perdeu a sessão nem que o tema deve limpar a tela. Veja a seção 23.
#### 5.3.1 Erros de conexão/autenticação/proxy
O patch expõe um bridge específico para o tema entender por que a conexão falhou, sem precisar depender apenas de texto solto no botão ou no status.
@@ -460,8 +483,8 @@ O payload esperado no callback é o que o seu servidor retornar. O app não for
| `DtGetLocalIP.execute()` | sem argumentos | string | Retorna o IPv4 local, ou `0.0.0.0`/vazio quando indisponível. |
| `DtGetNetworkName.execute()` | sem argumentos | string | Retorna o nome exibido da rede/operadora ativa. |
| `DtGetPingResult.execute()` | sem argumentos | string | A versão atual retorna `N/A`. Temas não devem depender disso para ping real. |
| `DtGetStatusBarHeight.execute()` | sem argumentos | string numérica | Altura da status bar nativa do Android em pixels. |
| `DtGetNavigationBarHeight.execute()` | sem argumentos | string numérica | Altura da navigation bar nativa do Android em pixels. |
| `DtGetStatusBarHeight.execute()` | sem argumentos | string numérica | Altura da status bar nativa **já convertida para pixels CSS** (dp). Use direto em `px` no CSS, sem dividir por `devicePixelRatio`. Retorna `0` se o Android não expor a dimensão. |
| `DtGetNavigationBarHeight.execute()` | sem argumentos | string numérica | Altura da navigation bar nativa **em pixels CSS** (dp), mesma regra acima. |
| `DtStartWebViewActivity.execute(url)` | string URL | void | Abre uma URL externamente/atividade WebView nativa. |
| `DtIgnoreBatteryOptimizations.execute()` | sem argumentos | void | Abre as configurações de otimização de bateria. |
| `DtStartApnActivity.execute()` | sem argumentos | void | Abre as configurações de APN. |
@@ -759,8 +782,9 @@ Depois da seleção, o tema deve atualizar a própria UI lendo `DtGetDefaultConf
O bridge atual não injeta `CakeApp.applyAuthVisibility()`. O tema deve decidir a visibilidade usando:
- `auth.username`, `auth.password` e `auth.v2ray_uuid` vindos de `DtGetDefaultConfig.execute()`.
- `DtUsername.get()`, `DtPassword.get()` e `DtUuid.get()`, que retornam `Locked` quando aquele input manual não deve ser editado.
- `auth.username`, `auth.password` e `auth.v2ray_uuid` vindos de `DtGetDefaultConfig.execute()`. **Esta é a fonte principal nesta versão.**
- `mode` do perfil, para perfis baseados em UUID (`V2RAY`, `XRAY`, `VMESS`, `VLESS`).
- Os getters `DtUsername.get()`, `DtPassword.get()` e `DtUuid.get()` apenas como fonte de valor inicial. Eles retornam string vazia quando o input não é aceito, e string vazia também é o retorno normal de um input aceito mas ainda não preenchido, então **getter vazio não serve como sinal de "campo bloqueado"**.
HTML recomendado:
@@ -813,9 +837,13 @@ Mesmo assim, o formato mais limpo para configs baseadas em UUID continua sendo:
Use isto no tema se quiser exibição confiável do UUID para nomes `vmess`, `vless`, `xray` e `v2ray`.
Cuidado com XHTTP: não use nomes como `XRAY XHTTP` no `mode` do catálogo para perfis XHTTP + SSH, porque o helper abaixo detecta `xray` no texto e mostraria o campo UUID em um perfil que na verdade usa usuário/senha. Use `XHTTP`, `SSH_XHTTP` ou `XHTTP_SSH`. Veja a seção 22.
```js
function isLocked(value) {
return String(value ?? '').trim().toLowerCase() === 'locked';
// Compatibilidade: esta versão retorna string vazia, versões/apps antigos retornavam 'Locked'.
const text = String(value ?? '').trim();
return text === '' || text.toLowerCase() === 'locked';
}
function bridgeGet(name) {
@@ -838,6 +866,7 @@ function bridgeExec(name, fallback = '') {
function isUuidCatalogMode(config) {
const mode = String(config?.mode || '').toLowerCase();
if (mode.includes('xhttp')) return false; // XHTTP + SSH usa usuário/senha
return mode.includes('v2ray') ||
mode.includes('xray') ||
mode.includes('vmess') ||
@@ -851,24 +880,23 @@ function updateAuthVisibility(config = null) {
}
const auth = config?.auth || {};
const uuidLocked = isLocked(bridgeGet('DtUuid'));
const usernameLocked = isLocked(bridgeGet('DtUsername'));
const passwordLocked = isLocked(bridgeGet('DtPassword'));
const uuidRequired = (
// Decida pela config, não pelos getters: nesta versão eles retornam string
// vazia tanto para campo não aceito quanto para campo aceito e ainda vazio.
const uuidRequired =
auth.v2ray_uuid === true ||
auth.manual_v2ray_uuid === true ||
isUuidCatalogMode(config)
) && !uuidLocked;
isUuidCatalogMode(config);
const sshRequired = (
const sshRequired = !uuidRequired && (
auth.username === true ||
auth.manual_username === true ||
auth.password === true ||
auth.manual_password === true ||
!usernameLocked ||
!passwordLocked
) && !uuidRequired;
// Fallback: perfil sem bloco auth mas com valor manual já salvo/cacheado.
!isLocked(bridgeGet('DtUsername')) ||
!isLocked(bridgeGet('DtPassword'))
);
const uuidGroup = document.querySelector('#uuid-group');
const usernameGroup = document.querySelector('#username-group');
@@ -880,6 +908,8 @@ function updateAuthVisibility(config = null) {
}
```
Se o perfil não tem bloco `auth` no catálogo e nada foi salvo ainda, nenhum dos dois sinais aparece. Nesse caso, prefira mostrar usuário/senha por padrão para modos SSH (`SSH_DIRECT`, `SSH_PROXY`, `SSH_SSL`, `SSL_PROXY`, `DNSTT`, `XHTTP`) em vez de esconder tudo. Um tema que esconde todos os inputs deixa o usuário sem como conectar.
---
## 10. Iniciando e parando a conexão
@@ -987,8 +1017,36 @@ window.dtLogsUpdatedListener = function(logsJson) {
O buffer de logs nativo exposto ao HTML é limitado a entradas recentes de status, mensagens de banner do servidor e mensagens úteis de proxy. Ele não é o logcat/debug completo.
### 11.1 Limites e o que não aparece em `DtGetLogs`
### 11.1 Como extrair o código HTTP do proxy no tema
| Buffer | Tamanho nesta versão | Conteúdo |
|---|---|---|
| Feed exposto ao tema (`DtGetLogs.execute()`) | 200 entradas mais recentes | Mudanças de estado, banner do servidor e linhas de proxy/HTTP. |
| Buffer nativo de log do serviço | 10.000 entradas (antes 1.000) | Log completo do túnel, visível em `DtShowLoggerDialog.execute()` e no logcat. |
O corte do buffer nativo também mudou: antes o app apagava um bloco grande de entradas de uma vez, o que parecia log truncado. Agora ele remove uma entrada por vez ao passar do limite.
Linhas que **não** entram no feed do tema, mesmo estando no log nativo:
- Mensagens de transporte XHTTP, como `Iniciando XHTTP (TLS)...`, `XHTTP proto=h2` e `XHTTP downlink stopped: ...`.
- `VPN lost. Reconnecting... <motivo>` e as mensagens de reconexão automática.
- `UDP forward is off: only TCP and DNS are carried by the tunnel`.
- `Ignoring unsupported DNS resolver (IPv4 required): <valor>`.
Para essas, o tema deve usar o estado (`DtVpnStateListener`) e o diálogo nativo (`DtShowLoggerDialog.execute()`). Se você precisa mostrar progresso de XHTTP na UI do tema, baseie-se em `CONNECTING``AUTH``CONNECTED`, não em texto de log.
### 11.2 Depurando um tema com logcat
Como a tela do app é um WebView, o log interno nem sempre é acessível durante o desenvolvimento. Nesta versão, **toda entrada do log nativo também é espelhada no logcat** com a tag `VOIDPRO`, já sem as tags HTML de formatação:
```txt
adb logcat -s VOIDPRO
```
O nível é mapeado assim: erro do túnel vira `E`, aviso vira `W`, debug/verbose vira `D` e o restante vira `I`. Isso é útil para conferir se a sua chamada de bridge realmente disparou a ação nativa que você esperava.
### 11.3 Como extrair o código HTTP do proxy no tema
Quando o app recebe uma linha `HTTP/...`, o classificador do bridge também atualiza `DtGetLastConnectionError.execute()` e dispara `dtConnectionErrorListener(error)` quando o status for relevante para o tema.
@@ -1202,12 +1260,24 @@ Perfis contêm:
O HTML recebe os perfis como `items`, não como `profiles`, ao chamar `DtGetConfigs.execute()`.
O catálogo também aceita um formato sem grupos. Se o JSON não tiver `groups` mas tiver `profiles` na raiz, o app cria automaticamente um grupo único com `id: "default"` e nome vindo de `groupName`. O tema continua recebendo grupos normalmente em `DtGetConfigs.execute()`.
```json
{
"title": "My VPN",
"groupName": "Servidores",
"profiles": []
}
```
---
## 16. Campos de payload/settings do perfil que afetam o bridge
Quando um perfil é selecionado, o app aplica um arquivo/base64 de config ou um payload JSON de settings.
O app reconhece um JSON como payload de settings quando ele contém pelo menos uma destas chaves: `serverHost`, `server_host`, `sshServer`, `mode`, `tunnelType`, `config_v2ray`, `xrayConfig`, `xhttpPath`, `xhttp_path`, `xhttp` ou `payload`. Um payload XHTTP que só traga `xhttp: { ... }` já é reconhecido.
Os campos de settings abaixo importam para o que o bridge HTML vai reportar depois:
| Campo settings | Aliases | Usado para |
@@ -1226,6 +1296,38 @@ Os campos de settings abaixo importam para o que o bridge HTML vai reportar depo
| `slowNameServer` | `nameServer`, `ns`, `nameserver` | Nameserver SlowDNS. |
| `slowDnsServer` | `dns`, `dnsServer`, `slowDns` | Servidor DNS SlowDNS. |
| `slowDnsKey` | `key`, `slowKey` | Chave SlowDNS. |
| `slowDnsMode` | `dnsMode` | Modo SlowDNS. Padrão `udp` quando ausente. |
| `localPort` | `sshLocalPort`, `local_port` | Porta SOCKS local. Padrão `1080` quando ausente. |
| `useDefaultPayload` | `defaultPayload`, `use_default_payload` | Usar payload padrão. Padrão `true`. |
| `tlsMode` | `tlsVersion`, `tls12` | Modo/versão TLS. |
| `publicKey` | `pubKey`, `customPubkey` | Chave pública customizada. |
| `xhttpPath` | `xhttp_path`, `xhttp.path` | Path XHTTP. Padrão `/xhttp` quando ausente. |
| `xhttpHost` | `xhttp_host`, `xhttp.host` | Host/`:authority` HTTP do XHTTP. |
| `xhttpTls` | `xhttp_tls`, `xhttp.tls` | TLS do XHTTP. Padrão `true`. |
Campos de VPN/DNS/UDP também aceitos no payload de settings:
| Campo settings | Aliases | Padrão | Usado para |
|---|---|---|---|
| `dnsForward` | `vpn.dnsForward` | `true` | Encaminhar DNS pelo túnel. |
| `dnsResolver` | `dns1`, `vpn.dnsResolver` | `8.8.8.8` | Resolver DNS primário. |
| `dnsResolverSecondary` | `dns2`, `vpn.dnsResolverSecondary` | `8.8.4.4` | Resolver DNS secundário. |
| `udpForward` | `vpn.udpForward` | `true` | Encaminhar UDP pelo túnel. Com `false`, o túnel carrega só TCP e DNS. |
| `udpResolver` | `udpResolverHost`, `vpn.udpResolver` | `127.0.0.1:7300` | Endpoint do resolvedor UDP. |
| `disableIpv6Tunnel` | `ipv6Disabled`, `vpn.disableIpv6Tunnel` | `true` | Desativar IPv6 no TUN. |
Os resolvers precisam ser IPv4. Um valor IPv6 é ignorado e o app registra `Ignoring unsupported DNS resolver (IPv4 required): <valor>` no log nativo.
O payload de settings aceita objetos aninhados, que são achatados para os campos acima. O valor de raiz tem prioridade sobre o aninhado:
| Objeto aninhado | Mapeia para |
|---|---|
| `server: { host, port }` | `serverHost`, `serverPort` |
| `auth: { username, password, v2ray_uuid, uuid }` | `username`, `password`, `xrayUuid` |
| `proxy: { host, port }` | `proxyHost`, `proxyPort` |
| `xhttp: { path, host, tls }` | `xhttpPath`, `xhttpHost`, `xhttpTls` |
| `slowDns: { ns, nameserver, dns, dnsServer, key, dnsMode }` | campos SlowDNS |
| `vpn: { dnsForward, dnsResolver, dnsResolverSecondary, udpForward, udpResolver, disableIpv6Tunnel }` | campos de VPN/DNS/UDP |
Valores atuais de tipo de túnel reconhecidos pelo parser de settings do app:
@@ -1237,6 +1339,9 @@ Valores atuais de tipo de túnel reconhecidos pelo parser de settings do app:
| `4`, `SLOW_DNS`, `DNSTT` | SlowDNS/DNSTT. |
| `5`, `SSL_PROXY`, `SSH_SSL_PROXY` | SSH SSL proxy. |
| `6`, `V2RAY`, `XRAY` | Xray/V2Ray. |
| `7`, `XHTTP`, `SSH_XHTTP`, `XHTTP_SSH` | XHTTP + SSH. Novo nesta versão, veja a seção 22. |
Qualquer outro valor cai em `SSH_DIRECT` (`1`). Um `mode` escrito errado no painel não gera erro: o perfil simplesmente conecta como SSH direto.
Para perfis V2Ray com UUID, use `mode: "V2RAY"` ou `tunnelType: 6` no payload de settings.
@@ -1301,7 +1406,7 @@ let configs = [];
try { configs = JSON.parse(DtGetConfigs.execute() || '[]'); } catch (e) {}
```
### Erro: mostrar `Locked` nos inputs
### Erro: jogar o retorno cru dos getters nos inputs
Ruim:
@@ -1315,6 +1420,25 @@ Bom:
usernameInput.value = cleanBridgeValue(DtUsername.get());
```
`cleanBridgeValue` cobre os três casos: `null`, string vazia desta versão e o antigo `Locked` de temas migrados.
### Erro: usar getter vazio como "campo bloqueado"
Nesta versão, `DtUsername.get()`, `DtPassword.get()` e `DtUuid.get()` retornam string vazia tanto quando o input não é aceito quanto quando ele é aceito e ainda está vazio.
Ruim:
```js
if (!DtUuid.get()) hideUuidField();
```
Bom:
```js
const current = OnlineBridge.getCurrentConfig();
if (!(current.auth?.v2ray_uuid || isUuidCatalogMode(current))) hideUuidField();
```
### Erro: não tratar erros classificados do bridge
Ruim:
@@ -1481,7 +1605,8 @@ const OnlineBridge = {
Antes de publicar um tema, verifique:
- Funciona quando `DtGetDefaultConfig.execute()` retorna `{}`.
- Não mostra `Locked` para o usuário.
- Funciona quando um objeto do bridge não existe, porque o APK pode ter aquele nome desativado.
- Não mostra `Locked` nem string vazia crua para o usuário.
- Consegue fazer parse seguro de JSON vazio ou inválido.
- Escuta `DtVpnStateListener`.
- Escuta `dtLogsUpdatedListener` se o tema mostra logs/proxy-status em tempo real.
@@ -1490,6 +1615,9 @@ Antes de publicar um tema, verifique:
- Salva usuário/senha/UUID antes de conectar.
- Trata `auth.username`, `auth.password` e `auth.v2ray_uuid`.
- O campo UUID aparece para configs com `auth.v2ray_uuid: true`; se o catálogo usar `mode: "VMESS"` ou `"VLESS"`, o helper do tema trata esses nomes.
- Perfis XHTTP mostram usuário/senha, não UUID.
- Não trata `CONNECTING` depois de `CONNECTED` como sessão perdida, porque a reconexão automática é silenciosa.
- Não depende de texto de log para mostrar progresso de XHTTP.
- Se usar proxy local, confirma `DtGetStatusHotSpotService.execute()` depois de chamar start/stop.
- Se usar proxy local em Android 13+, considera que a notificação pode depender da permissão `POST_NOTIFICATIONS`.
- Não depende de `DtGetPingResult` retornar ping real.
@@ -1498,7 +1626,7 @@ Antes de publicar um tema, verifique:
---
## 21. Resumo dos bridges novos do patch V2/V3/V4/V5/V6
## 21. Resumo dos bridges novos do patch V2/V3/V4/V5/V6/V7
Esta versão do patch adiciona/atualiza estes pontos para temas HTML OnlineConfig:
@@ -1516,7 +1644,22 @@ Esta versão do patch adiciona/atualiza estes pontos para temas HTML OnlineConfi
| SSH_DIRECT com payload/proxy | logs + erro classificado | Mesmo em SSH direto, o app tenta detectar resposta HTTP inicial do proxy sem quebrar banner SSH. |
| Proxy local foreground | `DtStartHotSpotService.execute()` | Inicia `ProxyService` com notificação foreground em vez de ligar apenas o socket interno. |
| Permissão de notificação | abertura do `OnlineConfigWebActivity` e start do proxy | Android 13+ pede `POST_NOTIFICATIONS`; se negar, o tema deve confirmar o status do proxy. |
| Detecção UUID no tema | `DtGetDefaultConfig.execute()` + `DtUuid.get()` | O bridge expõe `auth.v2ray_uuid` e `Locked`; o tema decide a visibilidade, incluindo `v2ray`, `xray`, `vmess` e `vless` quando necessário. |
| Detecção UUID no tema | `DtGetDefaultConfig.execute()` + `DtUuid.get()` | O bridge expõe `auth.v2ray_uuid`; o tema decide a visibilidade, incluindo `v2ray`, `xray`, `vmess` e `vless` quando necessário. |
Novidades específicas da V7:
| Item | Bridge/callback | O que muda para o tema |
|---|---|---|
| Modo XHTTP + SSH | `DtGetConfigs.execute()` / `DtGetDefaultConfig.execute()` (`mode`) | Novo `tunnelType 7`. O catálogo pode trazer `mode: "XHTTP"`, `"SSH_XHTTP"` ou `"XHTTP_SSH"`. Pede usuário/senha, nunca UUID. Veja a seção 22. |
| Campos XHTTP no payload | payload de settings | `xhttpPath` (padrão `/xhttp`), `xhttpHost`, `xhttpTls` (padrão ligado), além do objeto `xhttp: { path, host, tls }`. |
| Reconexão silenciosa | `DtVpnStateListener` | Reconexão automática mantém VPN/TUN/porta SOCKS local ativas e reporta `CONNECTING`. O tema não deve limpar a tela nem tratar como desconexão. Veja a seção 23. |
| Trava de sessão | nenhum bridge novo | Servidor, porta, SNI, Host, Path e TLS do XHTTP são congelados no início da sessão. Mudança de perfil durante o túnel ativo não afeta a reconexão. |
| `dtVpnStoppedSuccessListener` | callback | Continua disparando só quando a parada foi pedida pelo tema, nunca em reconexão automática. |
| Getters de auth sem `Locked` | `DtUsername.get()`, `DtPassword.get()`, `DtUuid.get()` | Retornam string vazia quando o input manual não é aceito. A visibilidade deve vir de `auth.*` e do `mode`. |
| Alturas de barra em px CSS | `DtGetStatusBarHeight`, `DtGetNavigationBarHeight` | Já vêm divididas pela densidade da tela. Use direto em `px` no CSS. |
| Campos de UDP/DNS no payload | payload de settings | `udpForward`, `udpResolver`, `dnsForward`, `dnsResolver`, `dnsResolverSecondary`, `disableIpv6Tunnel`, com o objeto `vpn: { ... }`. |
| Log maior + logcat | `DtGetLogs.execute()`, `DtShowLoggerDialog.execute()` | Feed do tema com as 200 entradas recentes, buffer nativo com 10.000, e todo o log espelhado no logcat com a tag `VOIDPRO`. |
| Bridge opcional por APK | todos os objetos `Cake*`/`Dt*` | Um objeto pode não ser registrado. Sempre proteja as chamadas. Veja a seção 2.1. |
Exemplo completo para tema tratar logs e erros:
@@ -1560,3 +1703,192 @@ function restoreLastErrorOnLoad() {
if (error.code) window.dtConnectionErrorListener(error);
}
```
---
## 22. Modo XHTTP + SSH (novo)
O app passou a suportar um transporte XHTTP (SplitHTTP) sobre HTTP/2 para o túnel SSH. Ele é o `tunnelType 7` e aparece na tela nativa com o rótulo `XHTTP + SSH`.
Para o tema, XHTTP é **um modo SSH**: usa usuário e senha, e não usa UUID.
### 22.1 Como o painel declara um perfil XHTTP
No perfil do catálogo, use um destes valores de `mode`:
```json
{ "mode": "XHTTP" }
{ "mode": "SSH_XHTTP" }
{ "mode": "XHTTP_SSH" }
{ "tunnelType": 7 }
```
E declare o `auth` normalmente:
```json
{
"id": "xhttp_1",
"name": "Servidor XHTTP",
"description": "XHTTP + SSH via CDN",
"mode": "XHTTP",
"methodName": "XHTTP",
"auth": {
"username": true,
"password": true,
"v2ray_uuid": false
}
}
```
Não escreva `mode` misturando nomes de Xray, como `XRAY XHTTP`. Helpers de tema que procuram `xray`/`v2ray` no texto do `mode` mostrariam o campo UUID em um perfil que precisa de usuário/senha.
### 22.2 Campos de payload usados pelo XHTTP
| Campo | Aliases | Padrão | Significado |
|---|---|---|---|
| `serverHost` | `sshServer`, `server_host` | — | IP/host que o app realmente conecta. |
| `serverPort` | `sshPort`, `server_port` | — | Porta do servidor XHTTP. |
| `xhttpPath` | `xhttp_path`, `xhttp.path` | `/xhttp` | Path do inbound XHTTP. |
| `xhttpHost` | `xhttp_host`, `xhttp.host` | vazio | Cabeçalho `Host`/`:authority` HTTP. Normalmente a borda da CDN. |
| `xhttpTls` | `xhttp_tls`, `xhttp.tls` | `true` | TLS ligado. Envie `false` para XHTTP em texto claro. |
| `sni` | `sslSni`, `customSni` | vazio | SNI TLS. Com TLS ligado, é também o host da URL, o que permite domain fronting com bug host. |
Exemplo de payload de settings:
```json
{
"mode": "XHTTP",
"serverHost": "203.0.113.10",
"serverPort": 443,
"username": "",
"password": "",
"sni": "bughost.operadora.com.br",
"xhttp": {
"path": "/xhttp",
"host": "edge.cdn.exemplo.com",
"tls": true
}
}
```
Com `username` e `password` vazios, o app marca credenciais manuais como obrigatórias, então o tema deve mostrar os inputs de usuário/senha.
### 22.3 O que o tema vê durante uma conexão XHTTP
O transporte XHTTP não publica linhas no feed de logs do tema. O que o tema recebe é a sequência normal de estados:
```txt
CONNECTING -> AUTH -> CONNECTED
```
As mensagens de diagnóstico do XHTTP ficam no log nativo e no logcat (`adb logcat -s VOIDPRO`):
| Mensagem | Significado |
|---|---|
| `Iniciando XHTTP (TLS)...` / `Iniciando XHTTP (sem TLS)...` | Abertura da sessão XHTTP. |
| `XHTTP proto=h2` | Protocolo negociado. `http/1.1` aqui indica que a CDN não passou HTTP/2 e o handshake tende a travar. |
| `XHTTP download rejected: HTTP <código>` | O servidor/CDN recusou o downlink. |
| `XHTTP downlink stopped: <motivo>` | O stream de download terminou. |
| `XHTTP uplink stopped: <motivo>` | O canal de upload terminou. |
Limitação conhecida nesta versão: essas mensagens de rejeição do XHTTP usam o formato `HTTP <código>`, sem o prefixo `HTTP/1.1`, então o classificador de erro não as converte em códigos `PROXY_*`. Uma recusa de XHTTP normalmente chega ao tema como falha de conexão genérica, sem `code` específico em `DtGetLastConnectionError.execute()`. Se o tema precisa distinguir esse caso, oriente o usuário pelo estado e pelo diálogo nativo de logs.
### 22.4 Configuração travada durante o túnel
Servidor, porta, SNI, Host, Path e TLS do XHTTP são fotografados no início da sessão do serviço. Enquanto o túnel estiver ativo:
- Trocar de perfil com `DtSetConfig.execute(id)` altera a seleção salva, mas **não** muda o transporte da sessão em andamento nem o que a reconexão automática vai usar.
- Para aplicar um perfil novo de verdade, pare a VPN (`DtExecuteVpnStop.execute()`), espere `DISCONNECTED` e só então inicie de novo.
Padrão recomendado no tema:
```js
function applyProfileAndReconnect(profileId) {
const state = OnlineBridge.getState();
OnlineBridge.selectConfig(profileId);
if (state === 'DISCONNECTED') {
refreshCurrentServer();
return;
}
window.dtVpnStoppedSuccessListener = function() {
window.dtVpnStoppedSuccessListener = null;
refreshCurrentServer();
OnlineBridge.start();
};
OnlineBridge.stop();
}
```
---
## 23. Reconexão silenciosa
Quando a conexão SSH/XHTTP cai sozinha, o app não derruba mais a VPN. Ele mantém ativos o `VpnService`, a interface TUN, as rotas, o processo tun2socks e a porta SOCKS local, e substitui apenas a conexão que falhou.
O que isso significa para o tema:
| Situação | Estado reportado | O que o tema deve fazer |
|---|---|---|
| Queda e reconexão automática | `CONNECTING` (estado nativo `RECONECTANDO`) | Mostrar "reconectando" e manter a tela conectada. Não limpar dados, não pedir login de novo. |
| Rede indisponível durante a reconexão | `NO_NETWORK` | Mostrar aviso de rede. A sessão pode voltar sozinha quando a rede retornar. |
| Reconexão concluída | `CONNECTED` | Voltar ao estado conectado normal. |
| Usuário parou pelo tema | `DISCONNECTING` e depois `DISCONNECTED`, mais `dtVpnStoppedSuccessListener()` | Aí sim resetar a UI. |
Regras práticas:
- `dtVpnStoppedSuccessListener` só dispara quando a parada foi pedida pelo bridge. Use esse callback, e não `DISCONNECTED` puro, como sinal de "o usuário desconectou".
- Não use `CONNECTING` como gatilho para zerar contador de tempo, limpar log ou reabrir a tela de login. Guarde o estado anterior e trate a transição `CONNECTED -> CONNECTING` como reconexão.
- Notificação, vibração e som ficam suprimidos durante a reconexão automática. O tema também não deve emitir alerta a cada tentativa.
- Callbacks atrasados de conexões antigas são descartados pelo app, então o tema não recebe eventos duplicados de uma sessão já substituída.
Exemplo de tratamento:
```js
let lastState = 'DISCONNECTED';
window.DtVpnStateListener = function(state) {
const wasOnline = lastState === 'CONNECTED' || lastState === 'AUTH';
const reconnecting = wasOnline && state === 'CONNECTING';
if (reconnecting) {
renderStatus('Reconectando...');
} else {
renderVpnState(state);
}
if (state === 'CONNECTED') {
renderStatus('Conectado');
}
lastState = state;
};
window.dtVpnStoppedSuccessListener = function() {
lastState = 'DISCONNECTED';
resetUi();
};
```
---
## 24. UDP, DNS e IPv6
O payload do perfil controla o que o túnel carrega. Os padrões, quando o campo não vem no payload:
| Campo | Padrão |
|---|---|
| `dnsForward` | `true` |
| `dnsResolver` | `8.8.8.8` |
| `dnsResolverSecondary` | `8.8.4.4` |
| `udpForward` | `true` |
| `udpResolver` | `127.0.0.1:7300` |
| `disableIpv6Tunnel` | `true` |
Pontos que aparecem em suporte de tema:
- Com `udpForward: false`, o túnel carrega só TCP e DNS. Jogos e chamadas de voz que dependem de UDP não funcionam nesse perfil, e o log nativo registra `UDP forward is off: only TCP and DNS are carried by the tunnel`.
- Os resolvers precisam ser IPv4. Um endereço IPv6 é ignorado, com a linha `Ignoring unsupported DNS resolver (IPv4 required): <valor>` no log nativo.
- O tema não tem bridge para ler ou alterar esses campos. Eles vêm do painel. Se o tema mostra "UDP ativo", isso precisa vir de informação do seu próprio painel, não do bridge.