Ir al contenido

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.

Una pantalla de app para iPhone con la burbuja de Hooky abajo a la derecha. La conversación de Hooky en modo oscuro en un iPhone: mensajes, foto y campo de texto.
La burbuja en una app SwiftUI, y la conversación en modo oscuro.

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-key de 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-ios

O 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ámetroFunción
siteKeyObligatorio. La clave pública del sitio.
languageOpcional. 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).
translationOpcional, true por defecto. false quita la traducción de los mensajes del equipo (mira Traducción de los mensajes).
apiURLOpcional, 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ón
  • alignment impone 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); hidden la 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 cierra

En 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.
  • name y email sustituyen a los de la conversación (nil los borra en el dispositivo); userId: nil lo deja como está.
  • El userId se guarda con el token de la conversación. Un userId distinto significa otra cuenta en el mismo dispositivo: la conversación se olvida, como con reset(). 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, identify y setAttributes no hacen nada (y se detienen en una aserción en Debug).

Reglas de los datos

CampoRegla
Datos libresPlanos: 50 claves como máximo, 8 KB en total. Las claves nuevas sustituyen a las anteriores; las demás se mantienen.
ClaveDe 1 a 64 caracteres: letras, cifras, espacios, _, . y -. Si no, se ignora.
ValorUn texto (cortado a 500 caracteres), un número o un booleano. nil borra la clave.
userId200 caracteres como máximo.
name, emailEl 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 NSCameraUsageDescription al Info.plist de 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:

DatoCuándoFicha del App Store
Mensajes, fotos y PDFCuando el usuario escribeContenido del usuario: Atención al cliente, Fotos o vídeos
Nombre y e-mailSi los da, o si los pasas a identifyInformación de contacto: Nombre, Correo electrónico
Tu userIdSi lo pasas a identifyIdentificadores: ID de usuario
Datos libresLo que pasas a identify y setAttributesSegún lo que pongas (un carrito: Historial de compras…)
Eventos trackCuando los envías, sin identificador de personaDatos de uso: Interacción con el producto (Análisis), no vinculados al usuario
Dispositivo, idioma, zona horaria, appCon la conversaciónInformació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.0 y X-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 userId en 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» en UserDefaults. 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-Client y 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 API hka_…). 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; identify y setAttributes se 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 NSCameraUsageDescription a tu Info.plist.
Otro usuario ve la conversación anterior
Llama a Hooky.reset() al cerrar sesión y pasa un userId a identify: un identificador distinto empieza con una conversación nueva.

Historial de versiones

1.0.0
Primera versión: configure, la burbuja SwiftUI y UIKit, open y close, identify (con userId y 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.