import { useEffect, useMemo, useState } from 'react'; import { useDTunnelError, useDTunnelEvent, useDTunnelSDK, } from 'dtunnel-sdk/react'; import type { DTunnelAction, DTunnelBridgeObjectName, DTunnelVPNState, } from 'dtunnel-sdk'; type LogItem = { timestamp: string; type: string; message: string; data?: unknown; }; type MetricState = { calls: number; events: number; errors: number; }; const REQUIRED_BRIDGE: DTunnelBridgeObjectName[] = [ 'DtGetVpnState', 'DtExecuteVpnStart', 'DtExecuteVpnStop', ]; export function App() { const sdk = useDTunnelSDK(); const [vpnState, setVpnState] = useState( sdk.main.getVpnState(), ); const [lastEvent, setLastEvent] = useState('Ultimo evento: nenhum'); const [metrics, setMetrics] = useState({ calls: 0, events: 0, errors: 0, }); const [logs, setLogs] = useState([ { timestamp: new Date().toISOString(), type: 'INFO', message: `SDK inicializado (v${sdk.version})`, }, ]); const [appConfigKey, setAppConfigKey] = useState('support_url'); const [externalUrl, setExternalUrl] = useState('https://dtunnel.com'); const [translateLabel, setTranslateLabel] = useState('vpn_connected'); const [actionValue, setActionValue] = useState('CDN_UPDATE'); const [notifyTitle, setNotifyTitle] = useState('DTunnel SDK'); const [notifyMessage, setNotifyMessage] = useState( 'Mensagem enviada pelo exemplo React', ); useEffect(() => { const statusBarHeight = normalizeInsetPx(sdk.android.getStatusBarHeight()); const navigationBarHeight = normalizeInsetPx( sdk.android.getNavigationBarHeight(), ); const rootStyle = document.documentElement.style; rootStyle.setProperty('--dt-status-bar-height', `${statusBarHeight}px`); rootStyle.setProperty( '--dt-navigation-bar-height', `${navigationBarHeight}px`, ); const applyOffsets = () => { const isLandscape = window.innerWidth > window.innerHeight; const widthOffset = isLandscape ? navigationBarHeight : 0; const heightOffset = statusBarHeight + (isLandscape ? 0 : navigationBarHeight); rootStyle.setProperty('--dt-index-width-offset', `${widthOffset}px`); rootStyle.setProperty('--dt-index-height-offset', `${heightOffset}px`); }; applyOffsets(); window.addEventListener('resize', applyOffsets); return () => { window.removeEventListener('resize', applyOffsets); }; }, [sdk]); const bridgeStatus = useMemo(() => { const availability = sdk.getBridgeAvailability(); const missing = REQUIRED_BRIDGE.filter((name) => !availability[name]); if (missing.length === 0) { return { text: 'Bridge: pronta', mode: 'ok' as const }; } return { text: `Bridge: parcial (${missing.join(', ')})`, mode: 'warn' as const, }; }, [sdk, logs.length]); useDTunnelEvent('vpnState', (event) => { setVpnState(event.payload); }); useDTunnelEvent('nativeEvent', (event) => { bumpMetric('events'); setLastEvent(`Ultimo evento: ${event.callbackName}`); appendLog('EVENT', event.callbackName, event.payload); }); useDTunnelEvent('newDefaultConfig', () => { setLastEvent('Ultimo evento: newDefaultConfig'); }); useDTunnelError((event) => { bumpMetric('errors'); setLastEvent(`Ultimo erro: ${event.error.code}`); appendLog('SDK_ERROR', event.error.message, event.error.details); }); function bumpMetric(name: keyof MetricState) { setMetrics((prev) => ({ ...prev, [name]: prev[name] + 1, })); } function appendLog(type: string, message: string, data?: unknown) { setLogs((prev) => [ ...prev, { timestamp: new Date().toISOString(), type, message, data, }, ]); } function runCall(label: string, fn: () => T): T { bumpMetric('calls'); const result = fn(); appendLog('CALL', label, result); return result; } function runCallVoid(label: string, fn: () => void) { bumpMetric('calls'); fn(); appendLog('CALL', label); } function clearOutput() { setLogs([ { timestamp: new Date().toISOString(), type: 'INFO', message: 'Output limpo', }, ]); } async function copySnapshot() { const snapshot = JSON.stringify(sdk.createDebugSnapshot(), null, 2); if (!navigator.clipboard?.writeText) { appendLog('WARN', 'Clipboard API indisponivel neste ambiente'); return; } try { await navigator.clipboard.writeText(snapshot); appendLog('INFO', 'Debug snapshot copiado para a area de transferencia'); } catch (error) { appendLog('ERROR', 'Falha ao copiar snapshot', String(error)); } } return (

DTunnel SDK Console - React + TypeScript

Exemplo completo com DTunnelSDKProvider, {' '} hooks React e painel de diagnostico para WebView.

{`SDK: pronto (v${sdk.version})`} {bridgeStatus.text} {`VPN: ${vpnState ?? 'desconhecido'}`} {lastEvent}
{metrics.calls}
{metrics.events}
{metrics.errors}

Operacoes Rapidas

Operacoes Avancadas

setAppConfigKey(event.target.value)} />
setExternalUrl(event.target.value)} />
setTranslateLabel(event.target.value)} />
setActionValue(event.target.value)} />
setNotifyTitle(event.target.value)} />
setNotifyMessage(event.target.value)} />

Output

          {logs
            .map((entry) => {
              const suffix =
                entry.data === undefined ? '' : ` ${safeStringify(entry.data)}`;
              return `[${entry.timestamp}] [${entry.type}] ${entry.message}${suffix}`;
            })
            .join('\n')}
        
); } function safeStringify(value: unknown): string { try { if (typeof value === 'string') return value; return JSON.stringify(value, null, 2); } catch { return String(value); } } function normalizeInsetPx(value: number | null): number { if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) { return 0; } return Math.round(value); }