O SDK iOS do Hooky
A conversa do Hooky, nativa no seu app para iPhone, iPad ou Mac (Catalyst): escrita em SwiftUI, sem WebView, com as cores do seu site. Os seus usuários escrevem, a sua equipe responde na mesma caixa de entrada do balão da web.
Requisitos
- iOS 15 ou mais recente, e Mac Catalyst 15 ou mais recente.
- Xcode 16 ou mais recente (o pacote é escrito em Swift 6). O seu app pode continuar em Swift 5.
- Nenhuma dependência de terceiros: URLSession, WebSocket, PhotosUI e o Keychain, nada mais.
- Uma conta Hooky e um site: a chave pública dele fica em Sites › Instalar, a mesma do
data-site-keydo balão na web.
Instalação
Só o Swift Package Manager é suportado. No Xcode: File › Add Package Dependencies…, cole o endereço do pacote, mantenha a regra Up to Next Major Version a partir da 1.0.0 e adicione o produto Hooky ao target do seu app.
https://github.com/Defdjamel/hooky-iosOu em um Package.swift:
dependencies: [
.package(url: "https://github.com/Defdjamel/hooky-ios", from: "1.0.0"),
],
targets: [
.target(name: "MeuApp", dependencies: [
.product(name: "Hooky", package: "hooky-ios"),
]),
]O pacote traz o próprio manifesto de privacidade (PrivacyInfo.xcprivacy), que o Xcode adiciona ao relatório de privacidade do seu app. O código-fonte está sob licença MIT: github.com/Defdjamel/hooky-ios.
Comece em 3 linhas
import Hooky
// 1. Na inicialização (App.init ou application(_:didFinishLaunchingWithOptions:))
Hooky.configure(siteKey: "SUA_CHAVE_PUBLICA")
// 2. O balão flutuante, em qualquer view SwiftUI
ContentView().hookyBubble()
// 3. Ou a conversa a partir do seu próprio botão
Hooky.open()configure carrega o nome, a cor e a mensagem de boas-vindas do site, retoma a conversa salva no aparelho e abre a conexão ao vivo. Chame uma vez, antes de qualquer outra chamada.
@main
struct MeuApp: App {
init() { Hooky.configure(siteKey: "SUA_CHAVE_PUBLICA") }
var body: some Scene {
WindowGroup { ContentView().hookyBubble() }
}
}| Parâmetro | Função |
|---|---|
siteKey | Obrigatório. A chave pública do site. |
language | Opcional. Força o idioma da conversa: "fr", "en", "es", "pt", "it" ou "de". Sem ele, vale o do aparelho (veja Idioma, modo escuro, acessibilidade). |
translation | Opcional, true por padrão. false remove a tradução das mensagens da equipe (veja Tradução das mensagens). |
apiURL | Opcional, para testar com o seu próprio servidor. Por padrão, https://api.heyhooky.com. |
O balão
Um botão flutuante com a cor do seu site, o logo do Hooky e um contador vermelho para as respostas ainda não lidas. Um toque abre a conversa.
SwiftUI
ContentView()
.hookyBubble() // no canto inferior, do lado definido para o site
ContentView()
.hookyBubble(alignment: .bottomLeading, padding: 24, hidden: estaNoPagamento)
HookyBubble(size: 56) // só o botão, para você posicionar
HookyBubble { mostrarMinhaFolha = true } // com a sua própria açãoalignmentforça o canto (senão, o lado esquerdo ou direito definido em Sites);padding, a distância das bordas (60 pontos de lado, 20 de margem por padrão);hiddeno esconde em uma tela onde ele atrapalharia.- O balão some enquanto a conversa está aberta.
UIKit
// No canto inferior, respeitando a área segura (leading: true para a esquerda)
Hooky.showBubble(in: view)
// Ou uma UIView para você posicionar
let balao = HookyBubbleView(size: 60)Abrir e fechar a conversa
Hooky.open() // folha de altura total, por cima do controlador mais alto
Hooky.open(from: self) // UIKit: apresentada por este controlador
Hooky.close() // fecha a conversaNo iPhone, a conversa abre em uma folha de altura total; no iPad e no Mac, em uma folha centralizada. O seu usuário fecha com o botão × ou deslizando para baixo. Para apresentá-la você mesmo (em um .sheet, em uma navegação…), use a view:
.sheet(isPresented: $chat) { HookyConversationView() }Mensagens não lidas
O balão já mostra o próprio contador. Para os seus botões (uma aba Ajuda, um ícone de menu), o número de respostas da equipe ainda não vistas pode ser acompanhado ao vivo. Ele volta a 0 quando a conversa é aberta.
// SwiftUI: Hooky.shared é um ObservableObject
@ObservedObject var hooky = Hooky.shared
AjudaView()
.tabItem { Label("Ajuda", systemImage: "questionmark.bubble") }
.badge(hooky.unreadCount)
// UIKit: um AsyncStream (o valor atual, depois cada mudança)
Task {
for await n in Hooky.unreadCountUpdates() {
tabBarItem.badgeValue = n > 0 ? "\(n)" : nil
}
}
// Ou um closure, chamado na thread principal
Hooky.shared.onUnreadCountChange = { n in print(n) }Hooky.shared também publica siteName, isOpen e isConfigured, para a sua interface. As respostas chegam ao vivo enquanto o app está em primeiro plano; quando ele volta ao primeiro plano, o SDK recupera o que perdeu. A versão 1.0.0 não envia notificações push aos seus usuários.
Identificar o usuário
O seu app sabe quem está logado: conte isso ao Hooky. Nos detalhes da conversa, a sua equipe vê o nome, o e-mail, o seu próprio identificador do usuário, o aparelho e os dados que você anexa. Com um e-mail, a conversa não pede mais nome nem e-mail.
// Depois do login
Hooky.identify(
name: "Lila",
email: "lila@example.com",
userId: "u_1042", // o seu próprio identificador
attributes: ["plano": "Pro", "carrinho": 49.9, "beta": true]
)
// Mais tarde: mesclados com os anteriores
Hooky.setAttributes(["carrinho": 12, "ultima_tela": "Pagamento"])
Hooky.setAttributes(["beta": nil]) // nil apaga uma chave
// No logout
Hooky.reset()Os valores são HookyValue: um texto, um número ou um booleano. Os literais são escritos como estão; para uma variável, indique o tipo: ["plano": .string(user.plano), "carrinho": .number(total), "beta": .bool(beta)].
Como funciona
- Ainda sem conversa: tudo fica na memória e é enviado com a primeira mensagem. Depois, cada mudança é enviada na hora, agrupada (um único envio para tudo o que muda no mesmo segundo). Sem conexão, ela vai junto com a próxima mensagem ou quando o app voltar.
nameeemailsubstituem os da conversa (nilos apaga no aparelho);userId: nilo deixa como está.- O
userIdé guardado com o token da conversa. UmuserIddiferente é outra conta no mesmo aparelho: a conversa é esquecida, como comreset(). O novo usuário não vê a conversa do anterior. Hooky.reset()fecha a conversa e esquece o histórico, a identidade, os dados e o rascunho. Chame no logout.- Nenhuma dessas chamadas gera erro. Chame
configureantes: senão,identifyesetAttributesnão fazem nada (e param em uma asserção no modo Debug).
Regras dos dados
| Campo | Regra |
|---|---|
| Dados livres | Sem aninhamento: 50 chaves no máximo, 8 KB no total. As chaves novas substituem as antigas, as outras ficam. |
| Chave | De 1 a 64 caracteres: letras, algarismos, espaços, _, . e -. Senão, é ignorada. |
| Valor | Um texto (cortado em 500 caracteres), um número ou um booleano. nil apaga a chave. |
userId | 200 caracteres no máximo. |
name, email | O nome: 100 caracteres no máximo. O e-mail: um endereço válido, senão é ignorado. |
O Hooky limpa o que passar do limite, sem nunca recusar a mensagem do seu usuário.
Esses dados são declarativos: a chave do site é pública, então qualquer pessoa pode usá-la para enviar o nome, o e-mail ou o userId que quiser. Eles ajudam a sua equipe a situar a pessoa, não provam quem ela é. Nunca coloque um segredo neles (senha, token, número de cartão) e nunca use esses dados para liberar um acesso ou agir sobre uma conta: sempre confira do seu lado.
Estatísticas
Conte o que acontece no seu app, como com Hooky.track na web: os cards aparecem em Estatísticas, junto com os do site. Chame logo depois que a ação der certo.
Hooky.track("cadastro")
Hooky.track("pedido", value: 49.9, properties: ["plano": "pro", "assentos": 3, "teste": false])value: um valor em dinheiro ou uma quantidade, somado no card. As propriedades não têm aninhamento (textos, números, booleanos).- Pode ser chamado de qualquer thread, e até antes de
configure: os eventos ficam esperando (100 no máximo). Nunca gera erro; sem conexão, o evento se perde. - As mesmas regras da web (nome, 20 propriedades no máximo, limite): veja Estatísticas na página.
Nunca envie dados pessoais (e-mail, nome, telefone) em um evento. Conte ações, não pessoas.
O que a conversa faz
- A primeira mensagem, com nome e e-mail opcionais (ou os do
identify), depois uma conversa como a do balão na web: nomes da equipe, “… está digitando”, “Lida”, links clicáveis, emojis grandes, mensagens anteriores sob demanda. - Até 4 anexos por mensagem, 10 MB cada: fotos (JPEG, PNG, HEIC, WebP, GIF) ou PDF. As fotos da galeria passam pelo seletor do sistema (nenhuma permissão a pedir) e são reduzidas a 2048 px antes do envio; os arquivos vêm do app Arquivos.
- Para oferecer a câmera, adicione
NSCameraUsageDescriptionaoInfo.plistdo seu app. Sem ela, a opção simplesmente fica escondida. - As fotos recebidas abrem em tela cheia (zoom, deslizar, compartilhar), os arquivos no Visualização Rápida.
- O rascunho sobrevive ao fechamento da conversa e à reabertura do app. O teclado nunca esconde o campo de texto.
- Erros claros, no idioma do usuário: sem conexão, arquivo grande demais, arquivos demais, mensagens demais de uma vez.
Tradução das mensagens
O seu usuário e a sua equipe escrevem cada um no seu idioma, como com o balão na web (veja Tradução das mensagens).
- Abaixo de uma mensagem da equipe escrita em outro idioma que o da conversa (o do aparelho, ou o definido por
language), aparece um link discreto Traduzir. Traduzida, a mensagem aparece no idioma do usuário, com “Traduzido do inglês · Ver o original” para voltar ao texto original. - O botão de tradução do cabeçalho oferece Traduzir sempre: as mensagens da equipe passam a chegar já traduzidas. A escolha fica salva no aparelho, para cada chave de site.
- Uma tradução que falha mostra um erro curto com Tentar de novo. As mensagens do usuário nunca são traduzidas do lado dele; a sua equipe tem o próprio botão Traduzir, no painel e no app Hooky.
- Só o texto é traduzido, não os anexos. A tradução vem ativada por padrão; para removê-la do seu app:
Hooky.configure(siteKey: "SUA_CHAVE_PUBLICA", translation: false).
O que o SDK envia
O que o balão da web envia, e nada mais. Sem identificador de publicidade, sem rastreamento entre apps, sem biblioteca de terceiros. Para preencher a seção Privacidade do app da sua página na App Store:
| Dado | Quando | Privacidade do app (App Store) |
|---|---|---|
| Mensagens, fotos e PDFs | Quando o usuário escreve | Conteúdo do usuário: Atendimento ao cliente, Fotos ou vídeos |
| Nome e e-mail | Se ele os informar, ou se você os passar para identify | Informações de contato: Nome, Endereço de e-mail |
O seu userId | Se você o passar para identify | Identificadores: ID do usuário |
| Dados livres | O que você passa para identify e setAttributes | Depende do que você coloca (um carrinho: Compras › Histórico de compras…) |
Eventos track | Quando você os envia, sem identificador de pessoa | Dados de uso: Interação com o produto (Análise), não vinculados ao usuário |
| Aparelho, idioma, fuso horário, app | Com a conversa | Informações técnicas para a sua equipe (veja abaixo) |
- Todos esses dados servem à Funcionalidade do app (o atendimento ao cliente), e as estatísticas à sua Análise. Nenhum deles serve ao rastreamento no sentido da Apple: nada de pedido do App Tracking Transparency.
- O aparelho é legível: “iPhone 15 Pro · iOS 18.2”, “iPad Air 11-inch (M2) · iPadOS 18.1”, “Mac · macOS 15.1”. Cada requisição leva
X-Hooky-Client: hooky-ios/1.0.0eX-Hooky-App: <bundle id> <version> (<build>): a sua equipe vê que a conversa vem de “iOS · com.example.app 2.3 (45)”. - No aparelho: o token da conversa (a única chave do usuário) e o
userIdno Keychain (só neste aparelho, depois do primeiro desbloqueio), por chave de site; o rascunho, a última mensagem vista e a escolha “Traduzir sempre” emUserDefaults. Uma tradução só envia o identificador da mensagem e o idioma da conversa. - O Hooky trata esses dados em seu nome (como operador, nos termos do RGPD e da LGPD): veja a política de privacidade. A sua página na loja continua sendo responsabilidade sua: declare o que o seu app realmente envia.
Idioma, modo escuro, acessibilidade
- Idioma
- A interface existe em francês, inglês, espanhol, português, italiano e alemão. Ela segue os idiomas preferidos do aparelho, com inglês por padrão;
Hooky.configure(siteKey:language:)força um deles. O idioma vai com a primeira mensagem, para que a sua equipe responda no idioma certo e na hora certa (com o fuso horário). - Modo escuro
- A conversa segue o modo claro ou escuro do aparelho, com a cor do seu site. Nada a configurar.
- Acessibilidade
- Cada controle tem o seu rótulo do VoiceOver (o balão: “Abrir o chat com …”, e o número de não lidas); Dynamic Type em todo lugar, e o teclado nunca esconde o campo de texto.
Solução de problemas
- O meu site cadastrou os endereços dele: o app é recusado?
- Não. Um app não tem endereço: ele se apresenta pelo cabeçalho
X-Hooky-Cliente funciona mesmo quando o site cadastrou os domínios dele. Não adicione nada em Sites. - O balão fica com as cores padrão, ou nada chega
- Confira a chave: é a chave pública do site (não a chave secreta
hks_…, nem uma chave de APIhka_…). Com uma chave desconhecida, o console do Xcode mostra “Hooky: the site couldn’t be loaded (HTTP 404…)”. - Sem conexão
- A mensagem mostra “Sem conexão” e fica no campo;
identifyesetAttributessão enviados mais tarde. A aparência do site é recarregada ao abrir a conversa. - A opção Câmera não aparece
- Adicione
NSCameraUsageDescriptionao seuInfo.plist. - Outro usuário vê a conversa antiga
- Chame
Hooky.reset()no logout e passe umuserIdparaidentify: um identificador diferente começa uma conversa do zero.
Histórico de versões
- 1.0.0
- Primeira versão:
configure, o balão SwiftUI e UIKit,openeclose,identify(comuserIde dados livres),setAttributes,track,reset, as não lidas ao vivo, fotos e PDFs, a tradução das mensagens da equipe, seis idiomas, modo escuro, VoiceOver e Dynamic Type.