Vai al contenuto

L’SDK iOS di Hooky

La conversazione Hooky, nativa nella tua app per iPhone, iPad o Mac (Catalyst): scritta in SwiftUI, senza WebView, con i colori del tuo sito. I tuoi utenti scrivono, il tuo team risponde dalla stessa inbox della bolla web.

Una schermata di un’app per iPhone con la bolla Hooky in basso a destra. La conversazione Hooky in modalità scura su iPhone: messaggi, foto e campo di testo.
La bolla in un’app SwiftUI, e la conversazione in modalità scura.

Requisiti

  • iOS 15 o successivo, e Mac Catalyst 15 o successivo.
  • Xcode 16 o successivo (il pacchetto è scritto in Swift 6). La tua app può restare in Swift 5.
  • Nessuna dipendenza di terze parti: URLSession, WebSocket, PhotosUI e il Portachiavi, nient’altro.
  • Un account Hooky e un sito: la sua chiave pubblica è in Siti › Installa, la stessa del data-site-key della bolla web.

Installazione

È supportato solo Swift Package Manager. In Xcode: File › Add Package Dependencies…, incolla l’indirizzo del pacchetto, mantieni la regola Up to Next Major Version a partire da 1.0.0, poi aggiungi il prodotto Hooky al target della tua app.

https://github.com/Defdjamel/hooky-ios

Oppure in un Package.swift:

dependencies: [
    .package(url: "https://github.com/Defdjamel/hooky-ios", from: "1.0.0"),
],
targets: [
    .target(name: "MiaApp", dependencies: [
        .product(name: "Hooky", package: "hooky-ios"),
    ]),
]

Il pacchetto include il suo manifesto sulla privacy (PrivacyInfo.xcprivacy), che Xcode aggiunge al report sulla privacy della tua app. Il codice sorgente è sotto licenza MIT: github.com/Defdjamel/hooky-ios.

Iniziare in 3 righe

import Hooky

// 1. All’avvio (App.init o application(_:didFinishLaunchingWithOptions:))
Hooky.configure(siteKey: "LA_TUA_CHIAVE_PUBBLICA")

// 2. La bolla fluttuante, su qualsiasi vista SwiftUI
ContentView().hookyBubble()

// 3. O la conversazione dal tuo pulsante
Hooky.open()

configure carica il nome, il colore e il messaggio di benvenuto del sito, riprende la conversazione salvata sul dispositivo e apre la connessione in diretta. Chiamalo una volta, prima di qualsiasi altra chiamata.

@main
struct MiaApp: App {
    init() { Hooky.configure(siteKey: "LA_TUA_CHIAVE_PUBBLICA") }

    var body: some Scene {
        WindowGroup { ContentView().hookyBubble() }
    }
}
ParametroRuolo
siteKeyObbligatorio. La chiave pubblica del sito.
languageFacoltativo. Impone la lingua della conversazione: "fr", "en", "es", "pt", "it" o "de". Senza, quella del dispositivo (vedi Lingua, modalità scura, accessibilità).
translationFacoltativo, true per impostazione predefinita. false rimuove la traduzione dei messaggi del team (vedi Traduzione dei messaggi).
apiURLFacoltativo, per fare test sul tuo server. Per impostazione predefinita https://api.heyhooky.com.

La bolla

Un pulsante fluttuante del colore del tuo sito, con il logo Hooky e un badge rosso per le risposte non ancora lette. Toccandolo si apre la conversazione.

SwiftUI

ContentView()
    .hookyBubble()                         // nell’angolo in basso, dal lato impostato per il sito

ContentView()
    .hookyBubble(alignment: .bottomLeading, padding: 24, hidden: inPagamento)

HookyBubble(size: 56)                      // il pulsante da solo, da posizionare tu
HookyBubble { mostraMioFoglio = true }     // con la tua azione
  • alignment impone l’angolo (altrimenti, il lato sinistro o destro impostato in Siti); padding la distanza dai bordi (60 punti di lato, 20 di margine per impostazione predefinita); hidden la nasconde su una schermata dove darebbe fastidio.
  • La bolla scompare mentre la conversazione è aperta.

UIKit

// Nell’angolo in basso, rispettando l’area sicura (leading: true per la sinistra)
Hooky.showBubble(in: view)

// O una UIView da posizionare tu
let bolla = HookyBubbleView(size: 60)

Aprire e chiudere la conversazione

Hooky.open()               // foglio a tutta altezza, sopra il controller più in alto
Hooky.open(from: self)     // UIKit: presentata da questo controller
Hooky.close()              // la richiude

Su iPhone, la conversazione si apre in un foglio a tutta altezza; su iPad e Mac, in un foglio centrato. Il tuo utente la chiude con il pulsante × o trascinandola verso il basso. Per presentarla tu (in uno .sheet, in una navigazione…), usa la vista:

.sheet(isPresented: $chat) { HookyConversationView() }

Messaggi non letti

La bolla mostra già il suo badge. Per i tuoi pulsanti (una scheda Aiuto, un’icona di menu), il numero di risposte del team non ancora viste si segue in diretta. Torna a 0 quando la conversazione si apre.

// SwiftUI: Hooky.shared è un ObservableObject
@ObservedObject var hooky = Hooky.shared

AiutoView()
    .tabItem { Label("Aiuto", systemImage: "questionmark.bubble") }
    .badge(hooky.unreadCount)

// UIKit: un AsyncStream (il valore attuale, poi ogni modifica)
Task {
    for await n in Hooky.unreadCountUpdates() {
        tabBarItem.badgeValue = n > 0 ? "\(n)" : nil
    }
}

// O una closure, chiamata sul thread principale
Hooky.shared.onUnreadCountChange = { n in print(n) }

Hooky.shared pubblica anche siteName, isOpen e isConfigured, per la tua interfaccia. Le risposte arrivano in diretta finché l’app è in primo piano; quando torna in primo piano, l’SDK recupera ciò che si è perso. La versione 1.0.0 non invia notifiche push ai tuoi utenti.

Identificare l’utente

La tua app sa chi ha effettuato l’accesso: dillo a Hooky. Nella scheda della conversazione, il tuo team legge il nome, l’email, il tuo identificativo dell’utente, il dispositivo e i dati che aggiungi. Con un’email, la conversazione non chiede più né il nome né l’email.

// Dopo l’accesso
Hooky.identify(
    name: "Lila",
    email: "lila@example.com",
    userId: "u_1042",                                 // il tuo identificativo
    attributes: ["piano": "Pro", "carrello": 49.9, "beta": true]
)

// Più tardi: uniti ai precedenti
Hooky.setAttributes(["carrello": 12, "ultima_schermata": "Pagamento"])
Hooky.setAttributes(["beta": nil])                    // nil cancella una chiave

// All’uscita
Hooky.reset()

I valori sono degli HookyValue: un testo, un numero o un booleano. I letterali si scrivono così come sono; per una variabile, specifica il tipo: ["piano": .string(user.piano), "carrello": .number(totale), "beta": .bool(beta)].

Come funziona

  • Nessuna conversazione ancora: tutto resta in memoria e parte con il primo messaggio. Dopo, ogni modifica parte subito, raggruppata (un solo invio per tutto ciò che cambia nello stesso secondo). Offline, riparte con il messaggio successivo o quando l’app torna attiva.
  • name ed email sostituiscono quelli della conversazione (nil li cancella sul dispositivo); userId: nil lo lascia invariato.
  • Lo userId viene conservato con il token della conversazione. Uno userId diverso significa un altro account sullo stesso dispositivo: la conversazione viene dimenticata, come con reset(). Il nuovo utente non vede la conversazione del precedente.
  • Hooky.reset() chiude la conversazione e dimentica i messaggi, l’identità, i dati e la bozza. Chiamalo all’uscita.
  • Nessuna di queste chiamate genera errori. Chiama prima configure: altrimenti, identify e setAttributes non fanno nulla (e si fermano su un’asserzione in Debug).

Le regole dei dati

CampoRegola
Dati liberiPiatti: 50 chiavi al massimo, 8 KB in tutto. Le nuove chiavi sostituiscono le vecchie, le altre restano.
ChiaveDa 1 a 64 caratteri: lettere, cifre, spazi, _, . e -. Altrimenti viene ignorata.
ValoreUn testo (tagliato a 500 caratteri), un numero o un booleano. nil cancella la chiave.
userId200 caratteri al massimo.
name, emailIl nome: 100 caratteri al massimo. L’email: un indirizzo valido, altrimenti viene ignorata.

Hooky ripulisce ciò che supera i limiti, senza mai rifiutare il messaggio del tuo utente.

Questi dati sono dichiarativi: la chiave del sito è pubblica, e chiunque può usarla per inviare un nome, un’email o uno userId a sua scelta. Aiutano il tuo team a inquadrare la persona, non provano chi è. Non metterci mai segreti (password, token, numero di carta) e non usarli mai per concedere un accesso o agire su un account: verifica sempre dalla tua parte.

Statistiche

Conta quello che succede nella tua app, come Hooky.track sul web: le schede compaiono in Statistiche, insieme a quelle del sito. Chiamalo subito dopo che l’azione è andata a buon fine.

Hooky.track("iscrizione")
Hooky.track("ordine", value: 49.9, properties: ["piano": "pro", "posti": 3, "prova": false])
  • value: un importo o una quantità, sommato nella scheda. Le proprietà sono piatte (testi, numeri, booleani).
  • Si può chiamare da qualsiasi thread, e anche prima di configure: gli eventi aspettano (100 al massimo). Non genera mai errori; offline, l’evento va perso.
  • Stesse regole del web (nome, 20 proprietà al massimo, limite): vedi Statistiche nella pagina.

Non inviare mai dati personali (email, nome, telefono) in un evento. Conta le azioni, non le persone.

Cosa fa la conversazione

  • Il primo messaggio, con nome ed email facoltativi (o quelli di identify), poi una conversazione come quella della bolla web: nomi del team, «… sta scrivendo…», «Letto», link cliccabili, emoji in grande, messaggi precedenti su richiesta.
  • Fino a 4 allegati per messaggio, 10 MB ciascuno: foto (JPEG, PNG, HEIC, WebP, GIF) o PDF. Le foto della libreria passano dal selettore di sistema (nessuna autorizzazione da chiedere) e vengono ridotte a 2048 px prima dell’invio; i file arrivano dall’app File.
  • Per proporre la fotocamera, aggiungi NSCameraUsageDescription all’Info.plist della tua app. Senza, l’opzione è semplicemente nascosta.
  • Le foto ricevute si aprono a schermo intero (zoom, scorrimento, condivisione), i file in Visualizzazione rapida.
  • La bozza sopravvive alla chiusura della conversazione e al riavvio dell’app. La tastiera non copre mai il campo di testo.
  • Errori chiari, nella lingua dell’utente: offline, file troppo pesante, troppi file, troppi messaggi insieme.

Traduzione dei messaggi

Il tuo utente e il tuo team scrivono ognuno nella propria lingua, come con la bolla web (vedi Traduzione dei messaggi).

  • Sotto un messaggio del team scritto in una lingua diversa da quella della conversazione (quella del dispositivo, o quella imposta da language), un link discreto Traduci. Una volta tradotto, il messaggio compare nella lingua dell’utente, con «Tradotto dall’inglese · Mostra l’originale» per tornare al testo di partenza.
  • Il pulsante di traduzione nell’intestazione propone Traduci sempre: i messaggi del team arrivano allora già tradotti. La scelta viene conservata sul dispositivo, per ogni chiave del sito.
  • Una traduzione non riuscita mostra un breve errore con Riprova. I messaggi dell’utente non vengono mai tradotti dalla sua parte; il tuo team ha il suo pulsante Traduci, nella dashboard e nell’app Hooky.
  • Si traduce solo il testo, non gli allegati. La traduzione è attiva per impostazione predefinita; per toglierla dalla tua app: Hooky.configure(siteKey: "LA_TUA_CHIAVE_PUBBLICA", translation: false).

Cosa invia l’SDK

Quello che invia la bolla web, e nient’altro. Nessun identificativo pubblicitario, nessun tracciamento tra app, nessuna libreria di terze parti. Per compilare la sezione Privacy dell’app della tua scheda App Store:

DatoQuandoScheda App Store
Messaggi, foto e PDFQuando l’utente scriveContenuti utente: Assistenza clienti, Foto o video
Nome ed emailSe li fornisce, o se li passi a identifyInformazioni di contatto: Nome, Indirizzo email
Il tuo userIdSe lo passi a identifyIdentificativi: ID utente
Dati liberiQuello che passi a identify e setAttributesSecondo quello che ci metti (un carrello: Acquisti › Cronologia acquisti…)
Eventi trackQuando li invii, senza identificativo della personaDati di utilizzo: Interazione con il prodotto (Analisi), non collegati all’utente
Dispositivo, lingua, fuso orario, appCon la conversazioneInformazioni tecniche per il tuo team (vedi sotto)
  • Tutti questi dati servono alla Funzionalità dell’app (l’assistenza clienti), e le statistiche alla tua Analisi. Nessuno serve al tracciamento nel senso di Apple: nessuna richiesta App Tracking Transparency.
  • Il dispositivo è leggibile: «iPhone 15 Pro · iOS 18.2», «iPad Air 11-inch (M2) · iPadOS 18.1», «Mac · macOS 15.1». Ogni richiesta porta X-Hooky-Client: hooky-ios/1.0.0 e X-Hooky-App: <bundle id> <version> (<build>): il tuo team vede che la conversazione arriva da «iOS · com.example.app 2.3 (45)».
  • Sul dispositivo: il token della conversazione (l’unica chiave dell’utente) e lo userId nel Portachiavi (solo questo dispositivo, dopo il primo sblocco), per chiave del sito; la bozza, l’ultimo messaggio visto e la scelta «Traduci sempre» in UserDefaults. Una traduzione invia solo l’identificativo del messaggio e la lingua della conversazione.
  • Hooky tratta questi dati per tuo conto (responsabile del trattamento ai sensi del GDPR): vedi l’informativa sulla privacy. Resti responsabile della tua scheda: dichiara ciò che la tua app invia davvero.

Lingua, modalità scura, accessibilità

Lingua
L’interfaccia esiste in francese, inglese, spagnolo, portoghese, italiano e tedesco. Segue le lingue preferite del dispositivo, l’inglese per impostazione predefinita; Hooky.configure(siteKey:language:) ne impone una. La lingua parte con il primo messaggio, perché il tuo team risponda nella lingua giusta e all’ora giusta (con il fuso orario).
Modalità scura
La conversazione segue la modalità chiara o scura del dispositivo, con il colore del tuo sito. Niente da impostare.
Accessibilità
Ogni controllo ha la sua etichetta VoiceOver (la bolla: «Apri la chat con …», e il numero di non letti); Dynamic Type ovunque, e la tastiera non copre mai il campo di testo.

Risoluzione dei problemi

Il mio sito ha dichiarato i suoi indirizzi: l’app viene rifiutata?
No. Un’app non ha un indirizzo: si presenta con la sua intestazione X-Hooky-Client e funziona anche quando il sito ha dichiarato i suoi domini. Non aggiungere nulla in Siti.
La bolla mantiene i colori predefiniti, o non arriva nulla
Controlla la chiave: è la chiave pubblica del sito (non la chiave segreta hks_…, né una chiave API hka_…). Con una chiave sconosciuta, la console di Xcode mostra «Hooky: the site couldn’t be loaded (HTTP 404…)».
Offline
Il messaggio mostra «Nessuna connessione» e resta nel campo; identify e setAttributes ripartono più tardi. L’aspetto del sito si ricarica all’apertura della conversazione.
L’opzione Fotocamera non compare
Aggiungi NSCameraUsageDescription al tuo Info.plist.
Un altro utente vede la vecchia conversazione
Chiama Hooky.reset() all’uscita, e passa uno userId a identify: un identificativo diverso riparte da una conversazione vuota.

Cronologia delle versioni

1.0.0
Prima versione: configure, la bolla SwiftUI e UIKit, open e close, identify (con userId e dati liberi), setAttributes, track, reset, i non letti in diretta, foto e PDF, la traduzione dei messaggi del team, sei lingue, modalità scura, VoiceOver e Dynamic Type.