Updatw 1156
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user