2026-04-26 12:51:15 -03:00
2026-04-26 12:51:15 -03:00

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:

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:
// --- 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 </head> quando o HTML contém uma tag head. Se o HTML não tiver </head>, o bridge é colocado no começo do documento.


3. Esqueleto rápido de tema

<!doctype html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
  <title>Tema OnlineConfig</title>
</head>
<body>
  <button id="serverButton">Selecionar servidor</button>

  <div id="usernameBox" data-auth-field>
    <input id="username" placeholder="Usuário">
  </div>

  <div id="passwordBox" data-auth-field>
    <input id="password" type="password" placeholder="Senha">
  </div>

  <div id="uuidBox" data-auth-field style="display:none">
    <input id="uuid" placeholder="UUID V2Ray">
  </div>

  <button id="connectButton">Conectar</button>
  <div id="status">Desconectado</div>

  <script>
    const $ = (id) => document.getElementById(id);

    function safeJson(text, fallback) {
      try { return JSON.parse(text || ''); } catch (e) { return fallback; }
    }

    function getState() {
      return window.DtGetVpnState?.execute?.() || 'DISCONNECTED';
    }

    function getCurrentConfig() {
      return safeJson(window.DtGetDefaultConfig?.execute?.() || '{}', {});
    }

    function loadSavedAuth() {
      const username = window.DtUsername?.get?.() || '';
      const password = window.DtPassword?.get?.() || '';
      const uuid = window.DtUuid?.get?.() || '';

      if (username.toLowerCase() !== 'locked') $('username').value = username;
      if (password.toLowerCase() !== 'locked') $('password').value = password;
      if (uuid.toLowerCase() !== 'locked') $('uuid').value = uuid;
    }

    function saveAuth() {
      window.DtUsername?.set?.($('username').value);
      window.DtPassword?.set?.($('password').value);
      window.DtUuid?.set?.($('uuid').value);
    }

    function updateStatus(state = getState()) {
      $('status').textContent = state;
      $('connectButton').textContent = state === 'CONNECTED' ? 'Desconectar' : 'Conectar';
    }

    $('connectButton').onclick = () => {
      saveAuth();
      if (getState() === 'CONNECTED') {
        window.DtExecuteVpnStop?.execute?.();
      } else {
        window.DtExecuteVpnStart?.execute?.();
      }
    };

    window.DtVpnStateListener = function(state) {
      updateStatus(state);
    };

    document.addEventListener('DOMContentLoaded', () => {
      loadSavedAuth();
      updateStatus();
    });
  </script>
</body>
</html>

4. Helpers principais CakeApp

O app injeta esse objeto para facilitar o código dos temas.

CakeApp.vpn

CakeApp.vpn.getState(); // retorna string
CakeApp.vpn.start();    // inicia/dispara o comando da VPN
CakeApp.vpn.stop();     // para a VPN

Aliases equivalentes:

DtGetVpnState.execute();
DtExecuteVpnStart.execute();
DtExecuteVpnStop.execute();

CakeApp.configs

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:

DtGetConfigs.execute();
DtGetDefaultConfig.execute();
DtSetConfig.execute(id);

CakeApp.auth

CakeApp.auth.getUsername();
CakeApp.auth.setUsername(value);
CakeApp.auth.getPassword();
CakeApp.auth.setPassword(value);
CakeApp.auth.getUuid();
CakeApp.auth.setUuid(value);

Aliases equivalentes:

DtUsername.get();
DtUsername.set(value);
DtPassword.get();
DtPassword.set(value);
DtUuid.get();
DtUuid.set(value);

CakeApp.system

CakeApp.system.openUrl(url);
CakeApp.system.getStatusBarHeight();
CakeApp.system.getNavigationBarHeight();

Aliases equivalentes:

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.

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:

{username}
{user}
{password}
{uuid}
{hwid}

Exemplo de campo no perfil:

{
  "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:

{
  "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:

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:

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.

window.DtVpnStateListener = function(state) {
  updateStatusBadge(state);
};

window.dtVpnStoppedSuccessListener = function() {}

Chamado depois que uma parada foi solicitada e o estado mapeado vira DISCONNECTED.

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.

window.DtUpdatePayloadSuccessListener = function(message) {
  reloadServerList();
};

window.DtUpdatePayloadErrorListener = function(message) {}

Chamado quando a atualização do catálogo falha.

window.DtUpdatePayloadErrorListener = function(message) {
  alert(message || 'Falha ao atualizar');
};

window.dtCheckUserStartedListener = function() {}

Chamado antes da requisição nativa de check-user começar.

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.

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.

[
  {
    "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.

{
  "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.

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:

[data-auth-field], .form-group, .input-group, .field, .item, .row, .col,
[class*=field], [class*=input], [class*=form]

HTML recomendado:

<div class="input-group" data-auth-field id="username-group">
  <input id="username" name="username" placeholder="Usuário">
</div>

<div class="input-group" data-auth-field id="password-group">
  <input id="password" name="password" type="password" placeholder="Senha">
</div>

<div class="input-group" data-auth-field id="uuid-group">
  <input id="v2ray_uuid" name="v2ray_uuid" placeholder="UUID V2Ray">
</div>

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:

{ "mode": "V2RAY" }

Isto funciona:

{ "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:

{ "mode": "VMESS" }
{ "mode": "VLESS" }
{ "mode": "V2RAY - VMESS" }

Para builds atuais, prefira este formato de perfil no catálogo para configs baseadas em UUID:

{
  "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.

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:

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:

[
  { "14:03:55": "Connecting" },
  { "14:03:56": "Authenticating" },
  { "14:03:57": "Server Message: welcome" }
]

Helper de renderização:

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:

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:

{
  "username": "utest678",
  "count_connections": 1,
  "expiration_date": "25/04/2026",
  "expiration_days": 0,
  "limit_connections": 1
}

13. Botão de atualizar configs

Use:

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.

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:

.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:

{
  "title": "My VPN",
  "selectedGroupId": "tim",
  "selectedProfileId": "tim_ssl",
  "appLogoUrl": "https://example.com/logo.png",
  "globalCustomLayoutHtml": "<!doctype html>...",
  "groups": []
}

Grupos contêm perfis:

{
  "id": "tim",
  "name": "TIM",
  "color": "#0055ff",
  "sorter": 1,
  "profiles": []
}

Perfis contêm:

{
  "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:

"auth": { "v2ray_uuid": true }

Valor UUID no payload de settings:

"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:

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:

const configs = JSON.parse(DtGetConfigs.execute());

Bom:

let configs = [];
try { configs = JSON.parse(DtGetConfigs.execute() || '[]'); } catch (e) {}

Erro: mostrar Locked nos inputs

Ruim:

usernameInput.value = DtUsername.get();

Bom:

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:

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:

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.

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.
Description
No description provided
Readme 85 KiB