El SDK iOS de Hooky
La conversación de Hooky, nativa en tu app para iPhone, iPad o Mac (Catalyst): escrita en SwiftUI, sin WebView, con los colores de tu web. Tus usuarios escriben y tu equipo responde desde la misma bandeja de entrada que para la burbuja web.
Requisitos
- iOS 15 o posterior, y Mac Catalyst 15 o posterior.
- Xcode 16 o posterior (el paquete está escrito en Swift 6). Tu app puede seguir en Swift 5.
- Ninguna dependencia de terceros: URLSession, WebSocket, PhotosUI y el Llavero, nada más.
- Una cuenta de Hooky y un sitio: su clave pública está en Sitios › Instalar, la misma que el
data-site-keyde la burbuja web.
Instalación
Solo se admite Swift Package Manager. En Xcode: File › Add Package Dependencies…, pega la dirección del paquete, deja la regla Up to Next Major Version a partir de 1.0.0 y luego añade el producto Hooky al target de tu app.
https://github.com/Defdjamel/hooky-iosO en un Package.swift:
dependencies: [
.package(url: "https://github.com/Defdjamel/hooky-ios", from: "1.0.0"),
],
targets: [
.target(name: "MiApp", dependencies: [
.product(name: "Hooky", package: "hooky-ios"),
]),
]El paquete incluye su manifiesto de privacidad (PrivacyInfo.xcprivacy), que Xcode añade al informe de privacidad de tu app. El código fuente tiene licencia MIT: github.com/Defdjamel/hooky-ios.
Empezar en 3 líneas
import Hooky
// 1. Al arrancar (App.init o application(_:didFinishLaunchingWithOptions:))
Hooky.configure(siteKey: "TU_CLAVE_PUBLICA")
// 2. La burbuja flotante, sobre cualquier vista SwiftUI
ContentView().hookyBubble()
// 3. O la conversación desde tu propio botón
Hooky.open()configure carga el nombre, el color y el mensaje de bienvenida del sitio, recupera la conversación guardada en el dispositivo y abre la conexión en vivo. Llámalo una vez, antes que cualquier otra llamada.
@main
struct MiApp: App {
init() { Hooky.configure(siteKey: "TU_CLAVE_PUBLICA") }
var body: some Scene {
WindowGroup { ContentView().hookyBubble() }
}
}| Parámetro | Función |
|---|---|
siteKey | Obligatorio. La clave pública del sitio. |
language | Opcional. Impone el idioma de la conversación: "fr", "en", "es", "pt", "it" o "de". Si no lo pones, el del dispositivo (mira Idioma, modo oscuro, accesibilidad). |
translation | Opcional, true por defecto. false quita la traducción de los mensajes del equipo (mira Traducción de los mensajes). |
apiURL | Opcional, para hacer pruebas contra tu propio servidor. Por defecto, https://api.heyhooky.com. |
La burbuja
Un botón flotante con el color de tu web, el logo de Hooky y un indicador rojo para las respuestas aún no leídas. Al tocarlo se abre la conversación.
SwiftUI
ContentView()
.hookyBubble() // en la esquina inferior, en el lado configurado para el sitio
ContentView()
.hookyBubble(alignment: .bottomLeading, padding: 24, hidden: estaEnPago)
HookyBubble(size: 56) // solo el botón, para colocarlo tú
HookyBubble { mostrarMiHoja = true } // con tu propia acciónalignmentimpone la esquina (si no, el lado izquierdo o derecho configurado en Sitios);padding, la distancia a los bordes (60 puntos de lado y 20 de margen por defecto);hiddenla oculta en una pantalla donde estorbaría.- La burbuja desaparece mientras la conversación está abierta.
UIKit
// En la esquina inferior, respetando el área segura (leading: true para la izquierda)
Hooky.showBubble(in: view)
// O una UIView para colocarla tú
let burbuja = HookyBubbleView(size: 60)Abrir y cerrar la conversación
Hooky.open() // hoja a toda altura, por encima del controlador superior
Hooky.open(from: self) // UIKit: presentada por este controlador
Hooky.close() // la cierraEn iPhone, la conversación se abre en una hoja a toda altura; en iPad y Mac, en una hoja centrada. Tu usuario la cierra con el botón × o deslizándola hacia abajo. Para presentarla tú (en una .sheet, en una navegación…), usa la vista:
.sheet(isPresented: $chat) { HookyConversationView() }Mensajes no leídos
La burbuja ya lleva su indicador. Para tus propios botones (una pestaña Ayuda, un icono de menú), el número de respuestas del equipo aún no vistas se sigue en vivo. Vuelve a 0 cuando se abre la conversación.
// SwiftUI: Hooky.shared es un ObservableObject
@ObservedObject var hooky = Hooky.shared
AyudaView()
.tabItem { Label("Ayuda", systemImage: "questionmark.bubble") }
.badge(hooky.unreadCount)
// UIKit: un AsyncStream (el valor actual y luego cada cambio)
Task {
for await n in Hooky.unreadCountUpdates() {
tabBarItem.badgeValue = n > 0 ? "\(n)" : nil
}
}
// O un closure, llamado en el hilo principal
Hooky.shared.onUnreadCountChange = { n in print(n) }Hooky.shared también publica siteName, isOpen e isConfigured, para tu interfaz. Las respuestas llegan en vivo mientras la app está en primer plano; al volver al primer plano, el SDK recupera lo que se ha perdido. La versión 1.0.0 no envía notificaciones push a tus usuarios.
Identificar al usuario
Tu app sabe quién ha iniciado sesión: díselo a Hooky. En la ficha de la conversación, tu equipo ve el nombre, el e-mail, tu propio identificador del usuario, el dispositivo y los datos que añadas. Con un e-mail, la conversación ya no pide ni nombre ni e-mail.
// Después de iniciar sesión
Hooky.identify(
name: "Lila",
email: "lila@example.com",
userId: "u_1042", // tu propio identificador
attributes: ["plan": "Pro", "carrito": 49.9, "beta": true]
)
// Más tarde: se combinan con los anteriores
Hooky.setAttributes(["carrito": 12, "ultima_pantalla": "Pago"])
Hooky.setAttributes(["beta": nil]) // nil borra una clave
// Al cerrar sesión
Hooky.reset()Los valores son HookyValue: un texto, un número o un booleano. Los literales se escriben tal cual; para una variable, indica el tipo: ["plan": .string(user.plan), "carrito": .number(total), "beta": .bool(beta)].
Cómo funciona
- Si aún no hay conversación, todo se queda en memoria y se envía con el primer mensaje. Después, cada cambio se envía al momento, agrupado (un solo envío para todo lo que cambia en el mismo segundo). Sin conexión, se vuelve a enviar con el siguiente mensaje o cuando la app vuelve.
nameyemailsustituyen a los de la conversación (nillos borra en el dispositivo);userId: nillo deja como está.- El
userIdse guarda con el token de la conversación. UnuserIddistinto significa otra cuenta en el mismo dispositivo: la conversación se olvida, como conreset(). El nuevo usuario no ve el hilo del anterior. Hooky.reset()cierra la conversación y olvida el hilo, la identidad, los datos y el borrador. Llámalo al cerrar sesión.- Ninguna de estas llamadas lanza un error. Llama antes a
configure: si no,identifyysetAttributesno hacen nada (y se detienen en una aserción en Debug).
Reglas de los datos
| Campo | Regla |
|---|---|
| Datos libres | Planos: 50 claves como máximo, 8 KB en total. Las claves nuevas sustituyen a las anteriores; las demás se mantienen. |
| Clave | De 1 a 64 caracteres: letras, cifras, espacios, _, . y -. Si no, se ignora. |
| Valor | Un texto (cortado a 500 caracteres), un número o un booleano. nil borra la clave. |
userId | 200 caracteres como máximo. |
name, email | El nombre: 100 caracteres como máximo. El e-mail: una dirección válida; si no, se ignora. |
Hooky recorta lo que se pasa, sin rechazar nunca el mensaje de tu usuario.
Estos datos son declarativos: la clave del sitio es pública, así que cualquiera puede usarla para enviar el nombre, el e-mail o el userId que quiera. Ayudan a tu equipo a situar a la persona, no demuestran quién es. Nunca pongas en ellos un secreto (contraseña, token, número de tarjeta) ni los uses para dar acceso o actuar sobre una cuenta: compruébalo siempre por tu lado.
Estadísticas
Cuenta lo que pasa en tu app, como Hooky.track en la web: las tarjetas aparecen en Estadísticas, junto a las del sitio. Llámalo justo después de que la acción salga bien.
Hooky.track("registro")
Hooky.track("pedido", value: 49.9, properties: ["plan": "pro", "plazas": 3, "prueba": false])value: un importe o una cantidad, sumado en la tarjeta. Las propiedades son planas (textos, números, booleanos).- Se puede llamar desde cualquier hilo, incluso antes de
configure: los eventos esperan (100 como máximo). Nunca lanza un error; sin conexión, el evento se pierde. - Las mismas reglas que en la web (nombre, 20 propiedades como máximo, límite): mira Estadísticas en la página.
Nunca envíes datos personales (e-mail, nombre, teléfono) en un evento. Cuenta acciones, no personas.
Lo que hace la conversación
- El primer mensaje, con nombre y e-mail opcionales (o los de
identify), y luego un hilo como el de la burbuja web: nombres del equipo, «… está escribiendo», «Leído», enlaces clicables, emojis en grande, mensajes anteriores a petición. - Hasta 4 archivos adjuntos por mensaje, de 10 MB cada uno: fotos (JPEG, PNG, HEIC, WebP, GIF) o PDF. Las fotos de la fototeca pasan por el selector del sistema (no hay que pedir ningún permiso) y se reducen a 2048 px antes del envío; los archivos vienen de la app Archivos.
- Para ofrecer la cámara, añade
NSCameraUsageDescriptionalInfo.plistde tu app. Sin ella, la opción simplemente se oculta. - Las fotos recibidas se abren a pantalla completa (zoom, deslizar, compartir), y los archivos en Vista Rápida.
- El borrador sobrevive al cierre de la conversación y al reinicio de la app. El teclado nunca tapa el campo de texto.
- Errores claros, en el idioma del usuario: sin conexión, archivo demasiado pesado, demasiados archivos, demasiados mensajes seguidos.
Traducción de los mensajes
Tu usuario y tu equipo escriben cada uno en su idioma, como con la burbuja web (mira Traducción de los mensajes).
- Debajo de un mensaje del equipo escrito en otro idioma que el de la conversación (el del dispositivo, o el impuesto por
language), aparece un enlace discreto Traducir. Una vez traducido, el mensaje se muestra en el idioma del usuario, con «Traducido del inglés · Ver el original» para volver al texto original. - El botón de traducción de la cabecera ofrece Traducir siempre: los mensajes del equipo llegan entonces ya traducidos. La elección se guarda en el dispositivo, para cada clave de sitio.
- Si una traducción falla, se muestra un breve error con Reintentar. Los mensajes del usuario nunca se traducen en su pantalla; tu equipo tiene su propio botón Traducir, en el panel y en la app de Hooky.
- Solo se traduce el texto, no los archivos adjuntos. La traducción está activada por defecto; para quitarla de tu app:
Hooky.configure(siteKey: "TU_CLAVE_PUBLICA", translation: false).
Lo que envía el SDK
Lo mismo que envía la burbuja web, y nada más. Sin identificador publicitario, sin seguimiento entre apps, sin bibliotecas de terceros. Para rellenar la sección Privacidad de la app de tu ficha del App Store:
| Dato | Cuándo | Ficha del App Store |
|---|---|---|
| Mensajes, fotos y PDF | Cuando el usuario escribe | Contenido del usuario: Atención al cliente, Fotos o vídeos |
| Nombre y e-mail | Si los da, o si los pasas a identify | Información de contacto: Nombre, Correo electrónico |
Tu userId | Si lo pasas a identify | Identificadores: ID de usuario |
| Datos libres | Lo que pasas a identify y setAttributes | Según lo que pongas (un carrito: Historial de compras…) |
Eventos track | Cuando los envías, sin identificador de persona | Datos de uso: Interacción con el producto (Análisis), no vinculados al usuario |
| Dispositivo, idioma, zona horaria, app | Con la conversación | Información técnica para tu equipo (mira más abajo) |
- Todos estos datos sirven para la Funcionalidad de la app (la atención al cliente), y las estadísticas para tus Análisis. Ninguno se usa para el rastreo en el sentido de Apple: no hay solicitud de App Tracking Transparency.
- El dispositivo es legible: «iPhone 15 Pro · iOS 18.2», «iPad Air 11-inch (M2) · iPadOS 18.1», «Mac · macOS 15.1». Cada petición lleva
X-Hooky-Client: hooky-ios/1.0.0yX-Hooky-App: <bundle id> <version> (<build>): tu equipo ve que la conversación viene de «iOS · com.example.app 2.3 (45)». - En el dispositivo: el token de la conversación (la única clave del usuario) y el
userIden el Llavero (solo en este dispositivo, tras el primer desbloqueo), por clave de sitio; el borrador, el último mensaje visto y la elección «Traducir siempre» enUserDefaults. Una traducción solo envía el identificador del mensaje y el idioma de la conversación. - Hooky trata estos datos por cuenta tuya (encargado del tratamiento según el RGPD): mira la política de privacidad. Tú sigues siendo responsable de tu ficha: declara lo que tu app envía de verdad.
Idioma, modo oscuro, accesibilidad
- Idioma
- La interfaz existe en francés, inglés, español, portugués, italiano y alemán. Sigue los idiomas preferidos del dispositivo, y en inglés por defecto;
Hooky.configure(siteKey:language:)impone uno. El idioma se envía con el primer mensaje, para que tu equipo responda en el idioma adecuado y a la hora adecuada (con la zona horaria). - Modo oscuro
- La conversación sigue el modo claro u oscuro del dispositivo, con el color de tu web. No hay nada que configurar.
- Accesibilidad
- Cada control tiene su etiqueta de VoiceOver (la burbuja: «Abrir el chat con …», y el número de no leídos); Dynamic Type en todas partes, y el teclado nunca tapa el campo de texto.
Solución de problemas
- Mi web ha declarado sus direcciones: ¿se rechaza la app?
- No. Una app no tiene dirección: se presenta con su cabecera
X-Hooky-Clienty funciona aunque el sitio haya declarado sus dominios. No añadas nada en Sitios. - La burbuja se queda con los colores por defecto, o no llega nada
- Revisa la clave: es la clave pública del sitio (no la clave secreta
hks_…, ni una clave APIhka_…). Con una clave desconocida, la consola de Xcode muestra «Hooky: the site couldn’t be loaded (HTTP 404…)». - Sin conexión
- El mensaje muestra «Sin conexión» y se queda en el campo de texto;
identifyysetAttributesse vuelven a enviar más tarde. La apariencia del sitio se recarga al abrir la conversación. - La opción Cámara no aparece
- Añade
NSCameraUsageDescriptiona tuInfo.plist. - Otro usuario ve la conversación anterior
- Llama a
Hooky.reset()al cerrar sesión y pasa unuserIdaidentify: un identificador distinto empieza con una conversación nueva.
Historial de versiones
- 1.0.0
- Primera versión:
configure, la burbuja SwiftUI y UIKit,openyclose,identify(conuserIdy datos libres),setAttributes,track,reset, los no leídos en vivo, fotos y PDF, la traducción de los mensajes del equipo, seis idiomas, modo oscuro, VoiceOver y Dynamic Type.