Widget em app (WebView)
O widget da Vyntrica também funciona dentro de aplicativos Android e iOS. Hoje isso é feito por WebView: o app abre uma página com o chat.js e o atendimento roda igual ao do site, com a mesma chave de API e o mesmo histórico no painel.
Ainda não existe SDK nativo para Android, iOS, React Native ou Flutter. O caminho abaixo usa só componentes padrão de cada plataforma.
Como funciona
- Você publica uma página simples com o snippet do widget, no seu próprio domínio (com HTTPS).
- O app abre essa página numa WebView, em tela cheia ou numa aba de atendimento.
- A pessoa toca no botão do chat e conversa normalmente.
Use sempre uma página hospedada em HTTPS. Gravação de áudio pelo navegador só funciona em contexto seguro, e assim você atualiza a página sem publicar nova versão do app.
Página de atendimento
Exemplo de https://seusite.com/atendimento.html:
<!doctype html> <html lang="pt-BR"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Atendimento</title> <style>html, body { margin: 0; height: 100%; background: #ffffff; }</style> </head> <body> <script src="https://api.vyntrica.com/widget-cdn/chat.js" data-api-key="SUA_CHAVE_DE_API" data-api-url="https://api.vyntrica.com" data-color="#8b5cf6" async ></script> </body> </html>
Os atributos são os mesmos da instalação no site.
O que a WebView precisa liberar
| Recurso do widget | O que o app precisa |
|---|---|
| Conversa e histórico | JavaScript e armazenamento local (DOM storage) ligados. O histórico fica no localStorage do aparelho. |
| Envio de arquivo | Tratar o seletor de arquivo da WebView. Sem isso, o botão de anexo não abre nada no Android. |
| Mensagem de áudio | Permissão de microfone no app e liberação da captura de áudio na WebView. |
| Links (anexos, privacidade) | Abrem em nova aba. O app decide se abre no navegador do aparelho ou na própria WebView. |
React Native
Com o pacote react-native-webview:
import { Linking } from 'react-native' import { WebView } from 'react-native-webview' export default function Atendimento() { return ( <WebView source={{ uri: 'https://seusite.com/atendimento.html' }} javaScriptEnabled domStorageEnabled mediaCapturePermissionGrantType="grant" onOpenWindow={(e) => Linking.openURL(e.nativeEvent.targetUrl)} /> ) }
No Android, declare android.permission.RECORD_AUDIO no AndroidManifest.xml e peça a permissão em tempo de execução antes de abrir a tela. No iOS, adicione NSMicrophoneUsageDescription no Info.plist.
Flutter
Com os pacotes webview_flutter, url_launcher e file_picker:
import 'package:file_picker/file_picker.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; const paginaAtendimento = 'https://seusite.com/atendimento.html'; WebViewController criarController() { final controller = WebViewController( onPermissionRequest: (request) => request.grant(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onNavigationRequest: (req) { if (req.url.startsWith(paginaAtendimento)) { return NavigationDecision.navigate; } launchUrl(Uri.parse(req.url), mode: LaunchMode.externalApplication); return NavigationDecision.prevent; }, )) ..loadRequest(Uri.parse(paginaAtendimento)); final plataforma = controller.platform; if (plataforma is AndroidWebViewController) { plataforma.setOnShowFileSelector((params) async { final resultado = await FilePicker.platform.pickFiles(); final caminho = resultado?.files.single.path; return caminho == null ? [] : [Uri.file(caminho).toString()]; }); } return controller; }
Exiba com WebViewWidget(controller: criarController()). As permissões de microfone no Android e no iOS são as mesmas do React Native.
Android nativo (Kotlin)
class AtendimentoActivity : AppCompatActivity() { private var callbackArquivo: ValueCallback<Array<Uri>>? = null private val escolherArquivo = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { r -> callbackArquivo?.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(r.resultCode, r.data)) callbackArquivo = null } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val webView = WebView(this) setContentView(webView) webView.settings.javaScriptEnabled = true webView.settings.domStorageEnabled = true webView.webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: PermissionRequest) { request.grant(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) } override fun onShowFileChooser( view: WebView, callback: ValueCallback<Array<Uri>>, params: FileChooserParams, ): Boolean { callbackArquivo?.onReceiveValue(null) callbackArquivo = callback escolherArquivo.launch(params.createIntent()) return true } } webView.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, req: WebResourceRequest): Boolean { if (req.url.host == "seusite.com") return false startActivity(Intent(Intent.ACTION_VIEW, req.url)) return true } } webView.loadUrl("https://seusite.com/atendimento.html") } }
No AndroidManifest.xml, declare android.permission.INTERNET e android.permission.RECORD_AUDIO. A liberação de áudio na WebView só funciona se o app já tiver recebido a permissão de microfone do usuário.
iOS nativo (Swift)
import UIKit import WebKit class AtendimentoViewController: UIViewController, WKUIDelegate { override func viewDidLoad() { super.viewDidLoad() let config = WKWebViewConfiguration() config.allowsInlineMediaPlayback = true let webView = WKWebView(frame: view.bounds, configuration: config) webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] webView.uiDelegate = self view.addSubview(webView) webView.load(URLRequest(url: URL(string: "https://seusite.com/atendimento.html")!)) } // Links que abrem em nova aba vão para o Safari func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { if let url = navigationAction.request.url { UIApplication.shared.open(url) } return nil } // Microfone para mensagem de áudio (iOS 15 ou superior) func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) { decisionHandler(.grant) } }
No Info.plist, adicione NSMicrophoneUsageDescription. O seletor de arquivo do WKWebView já funciona sem código extra. Se quiser permitir foto pela câmera, adicione também NSCameraUsageDescription.
Boas práticas
- Use a mesma chave de API do site. As conversas do app aparecem no mesmo painel.
- Não coloque a chave em código que roda fora da página de atendimento. Ela já fica no snippet, que é público por natureza.
- Teste envio de texto, arquivo e áudio num aparelho real antes de publicar o app.
- Se o atendimento precisar ficar aberto ao entrar na tela, oriente a pessoa a tocar no botão do chat. O widget não abre sozinho.
Próximos passos
- Instalação do widget: snippet HTML e SDK para sites.
- Personalização: cor, posição, logo e tom de voz.
- FAQ: dúvidas comuns sobre instalação e funcionamento.
