## 1. Modelo de nomes do bridge
O app expõe três estilos de bridge:
1. Objetos nativos `Cake*`. São os objetos reais registrados pelo Android com `addJavascriptInterface`.
2. Aliases `Dt*`. Existem para compatibilidade com temas já existentes.
3. Helpers `CakeApp.*`. São pequenos wrappers JavaScript injetados pelo app.
Para novos temas, você pode usar qualquer estilo, mas a abordagem recomendada é:
- Use `Dt*` quando quiser máxima compatibilidade com temas existentes.
- Use `CakeApp.*` quando quiser código mais limpo.
- Sempre proteja as chamadas, porque o tema também pode ser aberto no navegador durante testes.
Exemplo de helper seguro:
```js
function bridgeExec(name, fallback = '') {
try {
const obj = window[name];
if (obj && typeof obj.execute === 'function') return obj.execute();
} catch (e) {}
return fallback;
}
function bridgeCommand(name) {
try {
const obj = window[name];
if (obj && typeof obj.execute === 'function') obj.execute();
} catch (e) {}
}
function bridgeSet(name, value) {
try {
const obj = window[name];
if (obj && typeof obj.set === 'function') obj.set(String(value ?? ''));
} catch (e) {}
}
function bridgeGet(name, fallback = '') {
try {
const obj = window[name];
if (obj && typeof obj.get === 'function') return obj.get();
} catch (e) {}
return fallback;
}
```
---
## 2. Comportamento importante do WebView
O app ativa JavaScript e DOM storage no WebView do OnlineConfig. O HTML é carregado usando a URL do painel como URL base, então URLs relativas são resolvidas a partir do domínio do painel.
Antes de carregar o HTML, o app sanitiza o layout:
- Corrige alguns padrões quebrados de regex envolvendo quebras de linha.
- Remove qualquer bloco de script que comece neste marcador:
```js
// --- AMBIENTE MOCK PARA TESTES ---
```
Não coloque código de produção abaixo desse marcador. O app trata isso como uma seção local de mock/teste e remove essa parte do tema carregado.
O app injeta o bridge de compatibilidade antes de `` quando o HTML contém uma tag `head`. Se o HTML não tiver ``, o bridge é colocado no começo do documento.
---
## 3. Esqueleto rápido de tema
```html
Tema OnlineConfig
Desconectado
```
---
## 4. Helpers principais `CakeApp`
O app injeta esse objeto para facilitar o código dos temas.
### `CakeApp.vpn`
```js
CakeApp.vpn.getState(); // retorna string
CakeApp.vpn.start(); // inicia/dispara o comando da VPN
CakeApp.vpn.stop(); // para a VPN
```
Aliases equivalentes:
```js
DtGetVpnState.execute();
DtExecuteVpnStart.execute();
DtExecuteVpnStop.execute();
```
### `CakeApp.configs`
```js
CakeApp.configs.list(); // retorna string JSON com grupos/items
CakeApp.configs.current(); // retorna string JSON do perfil selecionado
CakeApp.configs.select(id); // seleciona um perfil pelo id
```
Aliases equivalentes:
```js
DtGetConfigs.execute();
DtGetDefaultConfig.execute();
DtSetConfig.execute(id);
```
### `CakeApp.auth`
```js
CakeApp.auth.getUsername();
CakeApp.auth.setUsername(value);
CakeApp.auth.getPassword();
CakeApp.auth.setPassword(value);
CakeApp.auth.getUuid();
CakeApp.auth.setUuid(value);
```
Aliases equivalentes:
```js
DtUsername.get();
DtUsername.set(value);
DtPassword.get();
DtPassword.set(value);
DtUuid.get();
DtUuid.set(value);
```
### `CakeApp.system`
```js
CakeApp.system.openUrl(url);
CakeApp.system.getStatusBarHeight();
CakeApp.system.getNavigationBarHeight();
```
Aliases equivalentes:
```js
DtStartWebViewActivity.execute(url);
DtGetStatusBarHeight.execute();
DtGetNavigationBarHeight.execute();
```
---
## 5. Tabela completa das APIs do bridge
### 5.1 APIs de config e perfil
| API | Assinatura | Retorno | Finalidade |
|---|---:|---|---|
| `DtGetConfigs.execute()` | sem argumentos | string JSON | Retorna todos os grupos e perfis do OnlineConfig para montar a lista de servidores. |
| `DtGetDefaultConfig.execute()` | sem argumentos | string JSON | Retorna o perfil atualmente selecionado. Retorna `{}` se nada estiver selecionado. |
| `DtSetConfig.execute(id)` | string id | void | Seleciona um perfil/servidor pelo id e aplica nativamente. |
| `DtStartAppUpdate.execute()` | sem argumentos | void | Atualiza o catálogo/layout/cache de configs a partir do painel. |
| `DtStartUpdatePayload.execute()` | sem argumentos | void | Mesmo comportamento de `DtStartAppUpdate` nesta versão do app. |
| `DtGetLocalConfigVersion.execute()` | sem argumentos | string numérica | Retorna o último valor de timestamp/sincronização do OnlineConfig. Útil como marcador de versão. |
### 5.2 APIs de autenticação
| 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. |
Importante: limpe `Locked` antes de colocar valores em inputs.
```js
function cleanBridgeValue(value) {
const text = String(value ?? '').trim();
return text.toLowerCase() === 'locked' ? '' : text;
}
```
### 5.3 APIs de controle e estado da VPN
| API | Assinatura | Retorno | Finalidade |
|---|---:|---|---|
| `DtGetVpnState.execute()` | sem argumentos | string de estado | Lê o estado atual da VPN mapeado para o HTML. |
| `DtExecuteVpnStart.execute()` | sem argumentos | void | Inicia/dispara o comando nativo da VPN. |
| `DtExecuteVpnStop.execute()` | sem argumentos | void | Para a VPN. |
| `DtGetNetworkDownloadBytes.execute()` | sem argumentos | string numérica | Retorna bytes baixados atuais da rede/app para verificação de tráfego. |
| `DtGetLogs.execute()` | sem argumentos | string JSON | Retorna logs recentes de status/banner como array de objetos com timestamp. |
| `DtShowLoggerDialog.execute()` | sem argumentos | void | Abre o diálogo nativo de logs. |
Possíveis estados de `DtGetVpnState` / `DtVpnStateListener`:
| Estado | Significado |
|---|---|
| `DISCONNECTED` | VPN parada/inativa. |
| `CONNECTING` | App iniciando, conectando, resolvendo, atribuindo IP, adicionando rotas ou reconectando. |
| `AUTH` | Fase de autenticação SSH/VPN. |
| `CONNECTED` | Túnel conectado. |
| `DISCONNECTING` | Túnel parando. |
| `NO_NETWORK` | Aguardando rede. |
| `AUTH_FAILED` | Falha de autenticação. |
### 5.4 APIs de consulta de usuário/conta
| API | Assinatura | Retorno | Finalidade |
|---|---:|---|---|
| `DtStartCheckUser.execute()` | sem argumentos | void | Chama o `urlCheckUser` do perfil selecionado e envia o resultado para `dtCheckUserModelListener`. |
A URL de check-user suporta estes placeholders:
```txt
{username}
{user}
{password}
{uuid}
{hwid}
```
Exemplo de campo no perfil:
```json
{
"urlCheckUser": "https://example.com/checkuser.php?user={username}&uuid={uuid}&hwid={hwid}"
}
```
O payload esperado no callback é o que o seu servidor retornar. O app não força um schema fixo. Se a requisição falhar ou se não existir URL, o app envia um JSON fallback parecido com:
```json
{
"username": "current_user",
"expiration_date": "-",
"expiration_days": 0,
"count_connections": 0,
"limit_connections": 0,
"message": ""
}
```
### 5.5 APIs de dispositivo/sistema
| API | Assinatura | Retorno | Finalidade |
|---|---:|---|---|
| `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. |
| `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. |
| `DtCleanApp.execute()` | sem argumentos | void | Abre o fluxo nativo de limpar dados/configurações. |
| `DtAirplaneState.execute()` | sem argumentos | string | Retorna o estado do modo avião. |
| `DtAirplaneActivate.execute()` | sem argumentos | void | Abre configurações do modo avião. Não ativa silenciosamente. |
| `DtAirplaneDeactivate.execute()` | sem argumentos | void | Abre configurações do modo avião. Não desativa silenciosamente. |
### 5.6 APIs de proxy local / hotspot
O bridge OnlineConfig expõe o serviço de proxy local usando nomes antigos de hotspot para compatibilidade com temas.
| API | Assinatura | Retorno | Finalidade |
|---|---:|---|---|
| `DtGetStatusHotSpotService.execute()` | sem argumentos | `RUNNING` ou `STOPPED` | Lê o estado do serviço de proxy local. |
| `DtStartHotSpotService.execute()` | sem argumentos | void | Inicia o serviço de proxy local se estiver parado. |
| `DtStopHotSpotService.execute()` | sem argumentos | void | Para o serviço de proxy local se estiver rodando. |
A porta padrão de proxy usada pelo tema existente é `6821`, mas o bridge em si só expõe iniciar/parar/estado e IP local. Se o tema mostrar um endereço de proxy, monte assim:
```js
const proxyAddress = `${DtGetLocalIP.execute()}:6821`;
```
### 5.7 APIs de config visual/app
| API | Assinatura | Retorno | Finalidade |
|---|---:|---|---|
| `DtGetAppConfig.execute(label)` | string label | string JSON ou null | Lê labels simples de configuração visual do app. |
Labels atuais nesta versão do app:
| Label | Retorno atual |
|---|---|
| `APP_LOGO` | `null` nesta versão do source. |
| `APP_BACKGROUND_TYPE` | `{"value":"COLOR"}` |
| `APP_BACKGROUND_COLOR` | `{"value":"#121212"}` |
| `APP_BACKGROUND_IMAGE` | `{"value":""}` |
Uso seguro no tema:
```js
function getAppConfigValue(label, fallback = '') {
try {
const raw = DtGetAppConfig.execute(label);
if (!raw) return fallback;
const data = JSON.parse(raw);
return data && data.value ? data.value : fallback;
} catch (e) {
return fallback;
}
}
```
---
## 6. Callbacks nativos que o tema pode definir
Defina estes callbacks em `window` antes ou durante o script principal. O app chama eles com `evaluateJavascript`.
### `window.DtVpnStateListener = function(state) {}`
Chamado quando o status nativo da VPN muda e depois que a página termina de carregar.
```js
window.DtVpnStateListener = function(state) {
updateStatusBadge(state);
};
```
### `window.dtVpnStoppedSuccessListener = function() {}`
Chamado depois que uma parada foi solicitada e o estado mapeado vira `DISCONNECTED`.
```js
window.dtVpnStoppedSuccessListener = function() {
showToast('Desconectado');
};
```
### `window.DtUpdatePayloadSuccessListener = function(message) {}`
Chamado depois que o app atualiza o catálogo/layout/cache de configs com sucesso. Nesta versão, a mensagem de sucesso normalmente é `OK`.
```js
window.DtUpdatePayloadSuccessListener = function(message) {
reloadServerList();
};
```
### `window.DtUpdatePayloadErrorListener = function(message) {}`
Chamado quando a atualização do catálogo falha.
```js
window.DtUpdatePayloadErrorListener = function(message) {
alert(message || 'Falha ao atualizar');
};
```
### `window.dtCheckUserStartedListener = function() {}`
Chamado antes da requisição nativa de check-user começar.
```js
window.dtCheckUserStartedListener = function() {
showProfileLoading();
};
```
### `window.dtCheckUserModelListener = function(modelJson) {}`
Chamado quando os dados do check-user estão prontos. `modelJson` é uma string. Faça parse se o endpoint retornar JSON.
```js
window.dtCheckUserModelListener = function(modelJson) {
let data = {};
try { data = JSON.parse(modelJson); } catch (e) { data = { message: modelJson }; }
renderProfile(data);
};
```
---
## 7. JSON de config retornado para o HTML
### 7.1 Estrutura de `DtGetConfigs.execute()`
Retorna um array JSON de grupos. Cada grupo tem `items`.
```json
[
{
"id": "group_1",
"name": "TIM",
"color": "",
"sorter": 1,
"items": [
{
"id": "profile_1",
"name": "TIM Principal",
"description": "WebSocket SSL",
"icon": "https://example.com/tim.png",
"mode": "SSH_PROXY",
"methodName": "",
"sorter": 1,
"status": "ACTIVE",
"auth": {
"username": true,
"password": true,
"v2ray_uuid": false,
"manual_username": true,
"manual_password": true,
"manual_v2ray_uuid": false
}
}
]
}
]
```
### 7.2 Estrutura de `DtGetDefaultConfig.execute()`
Retorna o objeto do perfil selecionado, ou `{}` se nada estiver selecionado.
```json
{
"id": "profile_1",
"name": "TIM Principal",
"description": "WebSocket SSL",
"icon": "https://example.com/tim.png",
"mode": "SSH_PROXY",
"methodName": "",
"auth": {
"username": true,
"password": true,
"v2ray_uuid": false,
"manual_username": true,
"manual_password": true,
"manual_v2ray_uuid": false
}
}
```
### 7.3 Significado do `auth`
O objeto `auth` informa ao HTML quais inputs manuais o perfil selecionado espera.
| Campo auth | Significado |
|---|---|
| `auth.username` | Mostrar/salvar input de usuário para perfis tipo SSH. |
| `auth.password` | Mostrar/salvar input de senha para perfis tipo SSH. |
| `auth.v2ray_uuid` | Mostrar/salvar input de UUID para perfis V2Ray/Xray. |
| `auth.manual_username` | Duplicado de compatibilidade de `auth.username`. |
| `auth.manual_password` | Duplicado de compatibilidade de `auth.password`. |
| `auth.manual_v2ray_uuid` | Duplicado de compatibilidade de `auth.v2ray_uuid`. |
---
## 8. Selecionando servidores
Monte sua lista de servidores a partir de `DtGetConfigs.execute()`, depois chame `DtSetConfig.execute(id)` quando o usuário selecionar um.
```js
function loadConfigs() {
try {
return JSON.parse(DtGetConfigs.execute() || '[]');
} catch (e) {
return [];
}
}
function selectServer(profile) {
DtSetConfig.execute(String(profile.id));
refreshCurrentServer();
}
function refreshCurrentServer() {
const current = JSON.parse(DtGetDefaultConfig.execute() || '{}');
document.querySelector('#serverName').textContent = current.name || 'Selecionar servidor';
updateAuthVisibility(current);
}
```
Depois da seleção, o app nativo também chama `CakeApp.applyAuthVisibility()` para esconder/mostrar automaticamente os campos de autenticação.
---
## 9. Visibilidade dos campos de autenticação
O app injeta `CakeApp.applyAuthVisibility()` e executa automaticamente no carregamento e depois da seleção de config.
Ele varre todos os elementos `input`, `textarea` e `select`, e classifica eles por `id`, `name`, `class`, `placeholder` e `type`.
Ele detecta:
- Campos de UUID quando os metadados contêm `v2ray`, `uuid` ou `xray`.
- Campos de senha quando os metadados contêm `password`, `senha` ou `pass`.
- Campos de usuário quando os metadados contêm `username`, `usuario`, `login` ou tokens parecidos com usuário.
Ele esconde o container pai mais próximo que bata com:
```css
[data-auth-field], .form-group, .input-group, .field, .item, .row, .col,
[class*=field], [class*=input], [class*=form]
```
HTML recomendado:
```html
```
### Limitação importante desta versão
Nesta versão do `VOIDPRO.zip`, a função injetada de visibilidade considera V2Ray/Xray somente quando `mode` começa com `v2ray` ou `xray`.
Isto funciona:
```json
{ "mode": "V2RAY" }
```
Isto funciona:
```json
{ "mode": "XRAY" }
```
Estes podem não ser detectados pela função injetada, a não ser que o tema tenha sua própria lógica de visibilidade ou você aplique patch no Java:
```json
{ "mode": "VMESS" }
{ "mode": "VLESS" }
{ "mode": "V2RAY - VMESS" }
```
Para builds atuais, prefira este formato de perfil no catálogo para configs baseadas em UUID:
```json
{
"id": "v2ray_1",
"name": "Servidor V2Ray",
"mode": "V2RAY",
"auth": {
"username": false,
"password": false,
"v2ray_uuid": true
}
}
```
### Helper forte de visibilidade pelo lado do tema
Use isto no tema se quiser exibição confiável do UUID para nomes `vmess`, `vless`, `xray` e `v2ray`.
```js
function isLocked(value) {
return String(value ?? '').trim().toLowerCase() === 'locked';
}
function isUuidProfile(config) {
const mode = String(config?.mode || '').toLowerCase();
const auth = config?.auth || {};
return auth.v2ray_uuid === true ||
auth.manual_v2ray_uuid === true ||
mode.includes('v2ray') ||
mode.includes('xray') ||
mode.includes('vmess') ||
mode.includes('vless');
}
function updateAuthVisibility(config = null) {
if (!config) {
try { config = JSON.parse(DtGetDefaultConfig.execute() || '{}'); }
catch (e) { config = {}; }
}
const uuidRequired = isUuidProfile(config) && !isLocked(DtUuid.get?.());
const sshRequired = !uuidRequired;
document.querySelector('#uuid-group').style.display = uuidRequired ? '' : 'none';
document.querySelector('#username-group').style.display = sshRequired ? '' : 'none';
document.querySelector('#password-group').style.display = sshRequired ? '' : 'none';
}
```
---
## 10. Iniciando e parando a conexão
Padrão básico:
```js
function saveVisibleInputs() {
const username = document.querySelector('#username')?.value || '';
const password = document.querySelector('#password')?.value || '';
const uuid = document.querySelector('#v2ray_uuid')?.value || '';
if (username) DtUsername.set(username);
if (password) DtPassword.set(password);
if (uuid) DtUuid.set(uuid);
}
function connectOrDisconnect() {
const state = DtGetVpnState.execute();
if (state === 'CONNECTED' || state === 'CONNECTING' || state === 'AUTH') {
DtExecuteVpnStop.execute();
return;
}
const current = JSON.parse(DtGetDefaultConfig.execute() || '{}');
if (!current.id) {
alert('Selecione um servidor primeiro.');
return;
}
saveVisibleInputs();
DtExecuteVpnStart.execute();
}
```
Desenvolvedores de temas devem controlar o estado da UI com `DtVpnStateListener`, não apenas com eventos de clique. Mudanças nativas de estado podem acontecer fora do clique do botão.
---
## 11. Logs
`DtGetLogs.execute()` retorna um array JSON. Cada item é um objeto com uma chave de timestamp e um valor de mensagem.
Exemplo:
```json
[
{ "14:03:55": "Connecting" },
{ "14:03:56": "Authenticating" },
{ "14:03:57": "Server Message: welcome" }
]
```
Helper de renderização:
```js
function readLogs() {
let rows = [];
try { rows = JSON.parse(DtGetLogs.execute() || '[]'); } catch (e) {}
return rows.map(row => {
const time = Object.keys(row)[0] || '';
const message = row[time] || '';
return `${time} ${message}`;
}).join('\n');
}
```
O buffer de logs nativo exposto ao HTML é limitado a entradas recentes de status e mensagens de banner do servidor. Ele não é o logcat/debug completo.
---
## 12. Modal de perfil / check-user
Fluxo recomendado de perfil:
```js
function openProfile() {
showProfileLoading();
DtStartCheckUser.execute();
}
window.dtCheckUserStartedListener = function() {
showProfileLoading();
};
window.dtCheckUserModelListener = function(modelJson) {
let data;
try { data = JSON.parse(modelJson); }
catch (e) { data = { message: modelJson }; }
renderProfile({
username: data.username || '-',
expirationDate: data.expiration_date || '-',
expirationDays: data.expiration_days ?? '-',
connections: `${data.count_connections ?? 0}/${data.limit_connections ?? '-'}`,
message: data.message || ''
});
};
```
Formato comum de resposta do seu painel/API check:
```json
{
"username": "utest678",
"count_connections": 1,
"expiration_date": "25/04/2026",
"expiration_days": 0,
"limit_connections": 1
}
```
---
## 13. Botão de atualizar configs
Use:
```js
function updateConfigs() {
DtStartAppUpdate.execute();
}
window.DtUpdatePayloadSuccessListener = function(message) {
reloadServerList();
alert('Configs atualizadas');
};
window.DtUpdatePayloadErrorListener = function(message) {
alert(message || 'Falha ao atualizar configs');
};
```
Depois de uma atualização bem-sucedida, o app recarrega o layout atual. Não guarde estado crítico ainda não salvo apenas em memória.
---
## 14. Dimensões de layout e áreas seguras
Use as alturas nativas das barras junto com variáveis CSS de safe area para melhor compatibilidade entre dispositivos.
```js
function updateInsets() {
const status = Number(DtGetStatusBarHeight?.execute?.() || 0);
const nav = Number(DtGetNavigationBarHeight?.execute?.() || 0);
document.documentElement.style.setProperty('--status-bar-height', `${status}px`);
document.documentElement.style.setProperty('--nav-bar-height', `${Math.min(nav, 48)}px`);
}
window.addEventListener('resize', updateInsets);
document.addEventListener('DOMContentLoaded', updateInsets);
```
Exemplo CSS:
```css
.header {
padding-top: max(var(--status-bar-height, 0px), env(safe-area-inset-top));
}
.footer {
padding-bottom: max(12px, env(safe-area-inset-bottom));
}
```
---
## 15. Campos do catálogo OnlineConfig relevantes para temas
O endpoint de catálogo do painel é consumido pelo app e depois normalizado para o HTML. Autores de temas normalmente não chamam esse endpoint diretamente, mas conhecer a estrutura de origem ajuda a evitar UI quebrada.
O catálogo de nível superior pode conter:
```json
{
"title": "My VPN",
"selectedGroupId": "tim",
"selectedProfileId": "tim_ssl",
"appLogoUrl": "https://example.com/logo.png",
"globalCustomLayoutHtml": "...",
"groups": []
}
```
Grupos contêm perfis:
```json
{
"id": "tim",
"name": "TIM",
"color": "#0055ff",
"sorter": 1,
"profiles": []
}
```
Perfis contêm:
```json
{
"id": "tim_ssl",
"name": "TIM SSL",
"description": "SSL + payload",
"methodName": "SSL",
"mode": "SSL_PROXY",
"icon": "https://example.com/tim.png",
"sorter": 1,
"auth": {
"username": true,
"password": true,
"v2ray_uuid": false
},
"urlCheckUser": "https://example.com/check?user={username}",
"configUrl": "https://example.com/profile/tim_ssl",
"configBase64": "...",
"cachedSettingsJson": "..."
}
```
O HTML recebe os perfis como `items`, não como `profiles`, ao chamar `DtGetConfigs.execute()`.
---
## 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.
Os campos de settings abaixo importam para o que o bridge HTML vai reportar depois:
| Campo settings | Aliases | Usado para |
|---|---|---|
| `mode` | `tunnelType` | Detecção nativa do tipo de túnel. |
| `serverHost` | `sshServer`, `server_host` | Host SSH/servidor. |
| `serverPort` | `sshPort`, `server_port` | Porta SSH/servidor. |
| `username` | `sshUser`, `user` | Usuário SSH. Vazio significa que credencial manual pode ser necessária. |
| `password` | `sshPass`, `pass` | Senha SSH. Vazio significa que credencial manual pode ser necessária. |
| `xrayUuid` | `v2ray_uuid`, `uuid` | UUID V2Ray/Xray. Vazio significa que UUID manual pode ser necessário. |
| `xrayConfig` | `config_v2ray`, `v2rayConfig` | Config JSON V2Ray/Xray. |
| `payload` | `customPayload`, `proxyPayload` | Payload SSH customizado. |
| `proxyHost` | `proxy_host` | Host do proxy. |
| `proxyPort` | `proxy_port` | Porta do proxy. |
| `sni` | `sslSni`, `customSni` | TLS/SNI. |
| `slowNameServer` | `nameServer`, `ns`, `nameserver` | Nameserver SlowDNS. |
| `slowDnsServer` | `dns`, `dnsServer`, `slowDns` | Servidor DNS SlowDNS. |
| `slowDnsKey` | `key`, `slowKey` | Chave SlowDNS. |
Valores atuais de tipo de túnel reconhecidos pelo parser de settings do app:
| Valor | Significado |
|---|---|
| `1`, `SSH_DIRECT` | SSH direto. |
| `2`, `SSH_PROXY` | SSH proxy. |
| `3`, `SSL_DIRECT`, `SSH_SSL` | SSH SSL. |
| `4`, `SLOW_DNS`, `DNSTT` | SlowDNS/DNSTT. |
| `5`, `SSL_PROXY`, `SSH_SSL_PROXY` | SSH SSL proxy. |
| `6`, `V2RAY`, `XRAY` | Xray/V2Ray. |
Para perfis V2Ray com UUID, use `mode: "V2RAY"` ou `tunnelType: 6` no payload de settings.
Evite enviar boolean `true` como `auth.v2ray_uuid` dentro do payload real de settings. Em settings, `v2ray_uuid` é tratado como alias de valor do UUID. No auth do catálogo, `auth.v2ray_uuid` é boolean. Mantenha os dois conceitos separados:
Auth do perfil no catálogo:
```json
"auth": { "v2ray_uuid": true }
```
Valor UUID no payload de settings:
```json
"xrayUuid": ""
```
---
## 17. Teste no navegador sem bridge Android
Use mocks somente enquanto desenvolve no navegador. Como o app remove a seção mock depois do marcador `// --- AMBIENTE MOCK PARA TESTES ---`, coloque mocks no final do HTML abaixo desse marcador exato, ou use um arquivo local separado somente para teste.
Mock mínimo para navegador:
```js
if (!window.DtGetVpnState) {
window.DtGetVpnState = { execute: () => 'DISCONNECTED' };
window.DtGetConfigs = { execute: () => JSON.stringify([]) };
window.DtGetDefaultConfig = { execute: () => '{}' };
window.DtSetConfig = { execute: (id) => console.log('select', id) };
window.DtUsername = { get: () => '', set: (v) => console.log('username', v) };
window.DtPassword = { get: () => '', set: (v) => console.log('password', v) };
window.DtUuid = { get: () => '', set: (v) => console.log('uuid', v) };
window.DtExecuteVpnStart = { execute: () => console.log('start') };
window.DtExecuteVpnStop = { execute: () => console.log('stop') };
}
```
---
## 18. Erros comuns em temas
### Erro: ler JSON sem `try/catch`
Ruim:
```js
const configs = JSON.parse(DtGetConfigs.execute());
```
Bom:
```js
let configs = [];
try { configs = JSON.parse(DtGetConfigs.execute() || '[]'); } catch (e) {}
```
### Erro: mostrar `Locked` nos inputs
Ruim:
```js
usernameInput.value = DtUsername.get();
```
Bom:
```js
usernameInput.value = cleanBridgeValue(DtUsername.get());
```
### Erro: assumir que todos os modos V2Ray são detectados automaticamente pelo código de visibilidade injetado
Use `mode: "V2RAY"` no catálogo/settings para este build, ou adicione um helper de visibilidade no tema que também verifique `vmess` e `vless`.
### Erro: salvar autenticação somente no conectar
O usuário pode trocar de tela ou usar conexão automática. Salve também quando o input mudar:
```js
usernameInput.addEventListener('input', () => DtUsername.set(usernameInput.value));
passwordInput.addEventListener('input', () => DtPassword.set(passwordInput.value));
uuidInput.addEventListener('input', () => DtUuid.set(uuidInput.value));
```
### Erro: depender do estado do clique em vez do estado nativo
Sempre escute:
```js
window.DtVpnStateListener = function(state) {
renderVpnState(state);
};
```
### Erro: usar objetos nativos sem verificar se existem
Temas podem ser abertos fora do Android para preview. Sempre proteja chamadas do bridge.
---
## 19. Wrapper recomendado para APIs do tema
Coloque isto perto do começo de todo layout customizado.
```js
const OnlineBridge = {
exec(name, fallback = '') {
try {
const obj = window[name];
if (obj && typeof obj.execute === 'function') return obj.execute();
} catch (e) {}
return fallback;
},
command(name, value) {
try {
const obj = window[name];
if (obj && typeof obj.execute === 'function') {
if (arguments.length >= 2) obj.execute(String(value));
else obj.execute();
}
} catch (e) {}
},
get(name, fallback = '') {
try {
const obj = window[name];
if (obj && typeof obj.get === 'function') return obj.get();
} catch (e) {}
return fallback;
},
set(name, value) {
try {
const obj = window[name];
if (obj && typeof obj.set === 'function') obj.set(String(value ?? ''));
} catch (e) {}
},
jsonFromExec(name, fallback) {
try {
return JSON.parse(this.exec(name, '') || '');
} catch (e) {
return fallback;
}
},
clean(value) {
const text = String(value ?? '').trim();
return text.toLowerCase() === 'locked' ? '' : text;
},
getConfigs() {
return this.jsonFromExec('DtGetConfigs', []);
},
getCurrentConfig() {
return this.jsonFromExec('DtGetDefaultConfig', {});
},
selectConfig(id) {
this.command('DtSetConfig', id);
},
getState() {
return this.exec('DtGetVpnState', 'DISCONNECTED');
},
start() {
this.command('DtExecuteVpnStart');
},
stop() {
this.command('DtExecuteVpnStop');
},
getAuth() {
return {
username: this.clean(this.get('DtUsername')),
password: this.clean(this.get('DtPassword')),
uuid: this.clean(this.get('DtUuid'))
};
},
saveAuth({ username, password, uuid }) {
this.set('DtUsername', username || '');
this.set('DtPassword', password || '');
this.set('DtUuid', uuid || '');
}
};
```
---
## 20. Checklist mínimo para um novo tema HTML OnlineConfig
Antes de publicar um tema, verifique:
- Funciona quando `DtGetDefaultConfig.execute()` retorna `{}`.
- Não mostra `Locked` para o usuário.
- Consegue fazer parse seguro de JSON vazio ou inválido.
- Escuta `DtVpnStateListener`.
- Chama `DtSetConfig.execute(id)` antes de iniciar um perfil selecionado.
- Salva usuário/senha/UUID antes de conectar.
- Trata `auth.username`, `auth.password` e `auth.v2ray_uuid`.
- O campo UUID aparece para configs `mode: "V2RAY"` / `mode: "XRAY"`.
- Não depende de `DtGetPingResult` retornar ping real.
- Não coloca código de produção abaixo do marcador de mock.
- Usa APIs nativas de altura das barras ou CSS safe areas para a navegação inferior não ficar cortada.