Aller au contenu

Le SDK iOS de Hooky

La conversation Hooky, native dans ton app iPhone, iPad ou Mac (Catalyst) : écrite en SwiftUI, sans WebView, aux couleurs de ton site. Tes utilisateurs écrivent, ton équipe répond depuis la même boîte que pour la bulle web.

Un écran d’app iPhone avec la bulle Hooky en bas à droite. La conversation Hooky en mode sombre sur iPhone : messages, photo et champ de saisie.
La bulle dans une app SwiftUI, et la conversation en mode sombre.

Prérequis

  • iOS 15 ou plus, et Mac Catalyst 15 ou plus.
  • Xcode 16 ou plus (le paquet est écrit en Swift 6). Ton app peut rester en Swift 5.
  • Aucune dépendance tierce : URLSession, WebSocket, PhotosUI et le Trousseau, rien d’autre.
  • Un compte Hooky et un site : sa clé publique est dans Sites › Installer, la même que le data-site-key de la bulle web.

Installation

Seul Swift Package Manager est pris en charge. Dans Xcode : File › Add Package Dependencies…, colle l’adresse du paquet, garde la règle Up to Next Major Version à partir de 1.0.0, puis ajoute le produit Hooky à la cible de ton app.

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

Ou dans un Package.swift :

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

Le paquet embarque son manifeste de confidentialité (PrivacyInfo.xcprivacy), qu’Xcode ajoute au rapport de confidentialité de ton app. Le code source est sous licence MIT : github.com/Defdjamel/hooky-ios.

Démarrer en 3 lignes

import Hooky

// 1. Au lancement (App.init ou application(_:didFinishLaunchingWithOptions:))
Hooky.configure(siteKey: "TA_CLE_PUBLIQUE")

// 2. La bulle flottante, sur n’importe quelle vue SwiftUI
ContentView().hookyBubble()

// 3. Ou la conversation depuis ton propre bouton
Hooky.open()

configure charge le nom, la couleur et le message d’accueil du site, reprend la conversation enregistrée sur l’appareil et ouvre la connexion en direct. Appelle-le une fois, avant tout autre appel.

@main
struct MonApp: App {
    init() { Hooky.configure(siteKey: "TA_CLE_PUBLIQUE") }

    var body: some Scene {
        WindowGroup { ContentView().hookyBubble() }
    }
}
ParamètreRôle
siteKeyObligatoire. La clé publique du site.
languageFacultatif. Impose la langue de la conversation : "fr", "en", "es", "pt", "it" ou "de". Sans lui, celle de l’appareil (voir Langue, mode sombre, accessibilité).
translationFacultatif, true par défaut. false retire la traduction des messages de l’équipe (voir Traduction des messages).
apiURLFacultatif, pour tester contre ton propre serveur. Par défaut https://api.heyhooky.com.

La bulle

Un bouton flottant à la couleur de ton site, avec le logo Hooky et une pastille rouge pour les réponses pas encore lues. Le toucher ouvre la conversation.

SwiftUI

ContentView()
    .hookyBubble()                         // dans le coin du bas, du côté réglé pour le site

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

HookyBubble(size: 56)                      // le bouton seul, à placer toi-même
HookyBubble { afficherMaFeuille = true }   // avec ta propre action
  • alignment impose le coin (sinon, le côté gauche ou droit réglé dans Sites) ; padding la distance aux bords (60 points de côté, 20 de marge par défaut) ; hidden la cache sur un écran où elle gênerait.
  • La bulle disparaît pendant que la conversation est ouverte.

UIKit

// Dans le coin du bas, zone sûre respectée (leading: true pour la gauche)
Hooky.showBubble(in: view)

// Ou une UIView à placer toi-même
let bulle = HookyBubbleView(size: 60)

Ouvrir et fermer la conversation

Hooky.open()               // feuille pleine hauteur, par-dessus le contrôleur le plus haut
Hooky.open(from: self)     // UIKit : présentée par ce contrôleur
Hooky.close()              // la referme

Sur iPhone, la conversation s’ouvre en feuille pleine hauteur ; sur iPad et Mac, en feuille centrée. Ton utilisateur la ferme avec le bouton × ou en la faisant glisser vers le bas. Pour la présenter toi-même (dans une .sheet, une navigation…), utilise la vue :

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

Messages non lus

La bulle porte déjà sa pastille. Pour tes propres boutons (un onglet Aide, une icône de menu), le nombre de réponses de l’équipe pas encore vues se suit en direct. Il retombe à 0 quand la conversation s’ouvre.

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

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

// UIKit : un AsyncStream (la valeur actuelle, puis chaque changement)
Task {
    for await n in Hooky.unreadCountUpdates() {
        tabBarItem.badgeValue = n > 0 ? "\(n)" : nil
    }
}

// Ou une fermeture, appelée sur le fil principal
Hooky.shared.onUnreadCountChange = { n in print(n) }

Hooky.shared publie aussi siteName, isOpen et isConfigured, pour ton interface. Les réponses arrivent en direct tant que l’app est au premier plan ; au retour au premier plan, le SDK rattrape ce qu’il a manqué. La version 1.0.0 n’envoie pas de notification push à tes utilisateurs.

Identifier l’utilisateur

Ton app sait qui est connecté : dis-le à Hooky. Ton équipe lit, dans la fiche de la conversation, le nom, l’e-mail, ton identifiant de l’utilisateur, l’appareil et les données que tu y joins. Avec un e-mail, la conversation ne demande plus ni prénom ni e-mail.

// Après la connexion
Hooky.identify(
    name: "Lila",
    email: "lila@exemple.fr",
    userId: "u_1042",                                 // ton identifiant à toi
    attributes: ["offre": "Pro", "panier": 49.9, "beta": true]
)

// Plus tard : fusionnées avec les précédentes
Hooky.setAttributes(["panier": 12, "dernier_ecran": "Paiement"])
Hooky.setAttributes(["beta": nil])                    // nil efface une clé

// À la déconnexion
Hooky.reset()

Les valeurs sont des HookyValue : un texte, un nombre ou un booléen. Les littéraux s’écrivent tels quels ; pour une variable, précise le type : ["offre": .string(user.offre), "panier": .number(total), "beta": .bool(beta)].

Comment ça marche

  • Pas encore de conversation : tout reste en mémoire et part avec le premier message. Ensuite, chaque changement part aussitôt, regroupé (un seul envoi pour tout ce qui change dans la même seconde). Hors ligne, il repart au message suivant ou au retour de l’app.
  • name et email remplacent ceux de la conversation (nil les efface sur l’appareil) ; userId: nil le laisse tel quel.
  • Le userId est gardé avec le jeton de la conversation. Un userId différent, c’est un autre compte sur le même appareil : la conversation est oubliée, comme avec reset(). Le nouvel utilisateur ne voit pas le fil du précédent.
  • Hooky.reset() referme la conversation et oublie le fil, l’identité, les données et le brouillon. Appelle-le à la déconnexion.
  • Aucun de ces appels ne lève d’erreur. Appelle configure avant : sinon, identify et setAttributes ne font rien (et s’arrêtent sur une assertion en Debug).

Les règles des données

ChampRègle
Données libresÀ plat : 50 clés au plus, 8 Ko en tout. Les nouvelles clés remplacent les anciennes, les autres restent.
Clé1 à 64 caractères : lettres, chiffres, espaces, _, . et -. Sinon, elle est ignorée.
ValeurUn texte (coupé à 500 caractères), un nombre ou un booléen. nil efface la clé.
userId200 caractères au plus.
name, emailLe nom : 100 caractères au plus. L’e-mail : une adresse valide, sinon il est ignoré.

Hooky nettoie ce qui dépasse, sans jamais refuser le message de ton utilisateur.

Ces données sont déclaratives : la clé du site est publique, et n’importe qui peut l’utiliser pour envoyer un nom, un e-mail ou un userId de son choix. Elles aident ton équipe à situer la personne, elles ne prouvent pas qui elle est. N’y mets jamais de secret (mot de passe, jeton, numéro de carte) et ne t’en sers jamais pour donner un accès ou agir sur un compte : vérifie toujours de ton côté.

Stats

Compte ce qui se passe dans ton app, comme Hooky.track sur le web : les cartes s’affichent dans Stats, avec celles du site. Appelle-le juste après que l’action a réussi.

Hooky.track("inscription")
Hooky.track("commande", value: 49.9, properties: ["offre": "pro", "places": 3, "essai": false])
  • value : un montant ou une quantité, additionné dans la carte. Les propriétés sont à plat (textes, nombres, booléens).
  • Appelable depuis n’importe quel fil, et même avant configure : les événements attendent (100 au plus). Ne lève jamais d’erreur ; hors ligne, l’événement est perdu.
  • Mêmes règles que sur le web (nom, 20 propriétés au plus, débit) : voir Stats dans la page.

N’envoie jamais de donnée personnelle (e-mail, nom, téléphone) dans un événement. Compte des actions, pas des personnes.

Ce que fait la conversation

  • Le premier message, avec prénom et e-mail facultatifs (ou ceux d’identify), puis un fil comme celui de la bulle web : noms de l’équipe, « … écrit », « Lu », liens cliquables, emoji en grand, messages plus anciens à la demande.
  • Jusqu’à 4 pièces jointes par message, 10 Mo chacune : photos (JPEG, PNG, HEIC, WebP, GIF) ou PDF. Les photos de la photothèque passent par le sélecteur du système (aucune autorisation à demander) et sont réduites à 2048 px avant l’envoi ; les fichiers viennent de l’app Fichiers.
  • Pour proposer l’appareil photo, ajoute NSCameraUsageDescription à l’Info.plist de ton app. Sans elle, l’option est simplement cachée.
  • Les photos reçues s’ouvrent en plein écran (zoom, balayage, partage), les fichiers dans Coup d’œil.
  • Le brouillon survit à la fermeture de la conversation et au relancement de l’app. Le clavier ne cache jamais le champ de saisie.
  • Des erreurs en clair, dans la langue de l’utilisateur : hors ligne, fichier trop lourd, trop de fichiers, trop de messages d’un coup.

Traduction des messages

Ton utilisateur et ton équipe écrivent chacun dans leur langue, comme avec la bulle web (voir Traduction des messages).

  • Sous un message de l’équipe écrit dans une autre langue que celle de la conversation (celle de l’appareil, ou celle imposée par language), un lien discret Traduire. Traduit, le message s’affiche dans la langue de l’utilisateur, avec « Traduit de l’anglais · Voir l’original » pour revenir au texte d’origine.
  • Le bouton de traduction de l’en-tête propose Toujours traduire : les messages de l’équipe arrivent alors déjà traduits. Le choix est gardé sur l’appareil, pour chaque clé de site.
  • Une traduction ratée affiche une courte erreur avec Réessayer. Les messages de l’utilisateur ne sont jamais traduits chez lui ; ton équipe a son propre bouton Traduire, dans le tableau de bord et l’app Hooky.
  • Seul le texte est traduit, pas les pièces jointes. La traduction est active par défaut ; pour la retirer de ton app : Hooky.configure(siteKey: "TA_CLE_PUBLIQUE", translation: false).

Ce que le SDK envoie

Ce qu’envoie la bulle web, et rien d’autre. Sans identifiant publicitaire, sans suivi entre apps, sans bibliothèque tierce. Pour remplir les informations de confidentialité de ta fiche App Store :

DonnéeQuandFiche App Store
Messages, photos et PDFQuand l’utilisateur écritContenu utilisateur : service client, photos
Prénom et e-mailS’il les donne, ou si tu les passes à identifyCoordonnées : nom, adresse e-mail
Ton userIdSi tu le passes à identifyIdentifiants : identifiant de l’utilisateur
Données libresCe que tu passes à identify et setAttributesSelon ce que tu y mets (un panier : historique d’achats…)
Événements trackQuand tu les envoies, sans identifiant de personneDonnées d’utilisation : interactions avec le produit (analyses), non liées à l’utilisateur
Appareil, langue, fuseau horaire, appAvec la conversationInformations techniques pour ton équipe (voir ci-dessous)
  • Toutes ces données servent au fonctionnement de l’app (le support client), et les stats à tes analyses. Aucune ne sert au suivi au sens d’Apple : pas de demande App Tracking Transparency.
  • L’appareil est lisible : « iPhone 15 Pro · iOS 18.2 », « iPad Air 11-inch (M2) · iPadOS 18.1 », « Mac · macOS 15.1 ». Chaque requête porte X-Hooky-Client: hooky-ios/1.0.0 et X-Hooky-App: <bundle id> <version> (<build>) : ton équipe voit la conversation venir de « iOS · com.exemple.app 2.3 (45) ».
  • Sur l’appareil : le jeton de la conversation (la seule clé de l’utilisateur) et le userId dans le Trousseau (cet appareil seulement, après le premier déverrouillage), par clé de site ; le brouillon, le dernier message vu et le choix « Toujours traduire » dans UserDefaults. Une traduction n’envoie que l’identifiant du message et la langue de la conversation.
  • Hooky traite ces données pour ton compte (sous-traitant au sens du RGPD) : voir la politique de confidentialité. Tu restes responsable de ta fiche : déclare ce que ton app envoie vraiment.

Langue, mode sombre, accessibilité

Langue
L’interface existe en français, anglais, espagnol, portugais, italien et allemand. Elle suit les langues préférées de l’appareil, l’anglais par défaut ; Hooky.configure(siteKey:language:) en impose une. La langue part avec le premier message, pour que ton équipe réponde dans la bonne langue et à la bonne heure (avec le fuseau horaire).
Mode sombre
La conversation suit le mode clair ou sombre de l’appareil, avec la couleur de ton site. Rien à régler.
Accessibilité
Chaque contrôle a son libellé VoiceOver (la bulle : « Ouvrir le chat avec … », et le nombre de non-lus) ; Dynamic Type partout, et le clavier ne cache jamais la saisie.

Dépannage

Mon site a déclaré ses adresses : l’app est-elle refusée ?
Non. Une app n’a pas d’adresse : elle se présente par son en-tête X-Hooky-Client et passe même quand le site a déclaré ses domaines. N’ajoute rien dans Sites.
La bulle garde des couleurs par défaut, ou rien n’arrive
Vérifie la clé : c’est la clé publique du site (pas la clé secrète hks_…, ni une clé API hka_…). Avec une clé inconnue, la console Xcode affiche « Hooky: the site couldn’t be loaded (HTTP 404…) ».
Hors ligne
Le message affiche « Pas de connexion » et reste dans le champ ; identify et setAttributes repartent plus tard. L’apparence du site se recharge à l’ouverture de la conversation.
L’option Appareil photo n’apparaît pas
Ajoute NSCameraUsageDescription à ton Info.plist.
Un autre utilisateur voit l’ancienne conversation
Appelle Hooky.reset() à la déconnexion, et passe un userId à identify : un identifiant différent repart d’une conversation vierge.

Journal des versions

1.0.0
Première version : configure, la bulle SwiftUI et UIKit, open et close, identify (avec userId et données libres), setAttributes, track, reset, les non-lus en direct, photos et PDF, la traduction des messages de l’équipe, six langues, mode sombre, VoiceOver et Dynamic Type.