SDK iOS

Un paquet Swift pour les apps iOS classiques : un chat de support natif en SwiftUI et UIKit. Les utilisateurs écrivent dans leur langue, votre équipe leur répond dans la leur.

Vous faites un jeu sous Unity ? Prenez le SDK Unity, qui couvre déjà les builds iOS. Ce paquet s'adresse aux apps écrites en Swift.

Installation

Dans Xcode, File → Add Package Dependencies, puis ajoutez cette URL, à partir de la version 0.2.3 :

https://github.com/inngame/sdk-ios

iOS 16 minimum (le paquet compile aussi pour macOS 13). Le paquet s'appelle Inngame. Le jeton de l'appareil est gardé dans le trousseau.

Configurer

Configurez une fois au lancement avec la clé publique de l'app (tableau de bord → votre app → Installation) :

import SwiftUI
import Inngame

@main
struct MyApp: App {
    init() {
        Inngame.configure(appKey: "app_XXXXXXXX")
    }
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

configure(appKey:baseURL:) parle à https://app.inngame.app par défaut ; ne passez baseURL que pour un autre serveur Inngame.

Identifier l'utilisateur

Les utilisateurs commencent anonymes : le chat marche avant toute connexion. Après la connexion, identifiez-les. Passez un token, un JWT signé par votre serveur avec le secret d'identité de l'app, pour les marquer vérifiés : seuls les utilisateurs vérifiés retrouvent leur historique sur tous leurs appareils. Voir Identité des joueurs.

// Unverified
Inngame.identify(userId: user.id, name: user.displayName, email: user.email)

// Verified, with attributes your team sees in the profile
let jwt = try await api.inngameToken()
Inngame.identify(userId: user.id,
                 name: user.displayName,
                 email: user.email,
                 token: jwt,
                 metadata: ["team": "blue", "signupYear": 2024])

metadata : jusqu'à 50 clés ; texte (500 caractères au plus), nombres, booléens. Les champs d'abonnement comme plan, paid ou subscription_status sont posés par le serveur seul et ignorés. Un token invalide ou expiré est refusé ; la raison arrive dans Inngame.shared.lastError.

Afficher le chat

SwiftUI :

struct SettingsView: View {
    @State private var showHelp = false

    var body: some View {
        List {
            Button("Help & support") { showHelp = true }
        }
        .sheet(isPresented: $showHelp) {
            InngameChatView()
        }
    }
}

InngameChatView(showsCloseButton: false) masque le bouton de fermeture, pour une destination de navigation ou un onglet. UIKit (le chat s'ouvre en feuille) :

@objc func helpTapped() {
    Inngame.present(from: self)
}

Le chat prend son allure dans le tableau de bord (Réglages → Apps → votre app → Apparence), sans mise à jour de l'app : votre logo dans la barre de titre à côté du titre, et les couleurs d'accent, de fond, de texte, d'en-tête, de bulle de l'équipe et de bulle de l'utilisateur pour les modes clair et sombre (ou un modèle : Inngame, Dark fantasy, Cartoon, Sci-fi, Minimal). Les couleurs laissées vides gardent l'allure du système.

Les messages écrits sans réseau sont gardés sur l'appareil, affichés « Envoi… » et renvoyés au retour du réseau ou au lancement suivant (une seule fois, grâce à un identifiant client). Un message refusé pour de bon par le serveur (utilisateur bloqué, par exemple) quitte la file au lieu d'être retenté sans fin.

Ce que l'utilisateur trouve dans le chat

Branchez une fois, au lancement, les cartes qui ont besoin de votre code :

Inngame.onOffer = { offerId in paywall.show(offerId) }
Inngame.onScreen = { screen, value in router.open(screen, value) }
Inngame.onItemGranted = { item in inventory.refresh() } // credit it on your server (item.claimed webhook)
Inngame.onRestorePurchases = { Task { try? await AppStore.sync() } } // what happens when you leave it nil

Pastille de non-lus

Inngame.shared est un ObservableObject ; unreadCount est publié :

struct SupportRow: View {
    @ObservedObject var support = Inngame.shared
    @Binding var showHelp: Bool

    var body: some View {
        Button("Help & support") { showHelp = true }
            .badge(support.unreadCount)   // shown in List rows and tab items
    }
}

En UIKit, abonnez-vous avec Combine : Inngame.shared.$unreadCount.sink { … }.

Contexte et événements

Le modèle d'appareil, la version d'iOS, la version de l'app, la langue et la région sont relevés à l'ouverture de la session. Ajoutez vos propres clés ; elles sont jointes aux messages suivants de l'utilisateur et visibles par l'équipe seulement :

Inngame.setContext("screen", "Checkout")
Inngame.setContext("orderId", order.id)
Inngame.setContext("orderId", nil)   // remove

Envoyez des événements pour les automatisations, qui peuvent répondre par un message ou ouvrir le chat :

Inngame.track("checkout_failed", properties: ["step": "payment"])

Quand une automatisation demande d'ouvrir le chat, Inngame.shared.wantsToOpen passe à true : liez-le à votre feuille. Noms d'événements : lettres, chiffres, _, . et -, 64 caractères au plus.

Achats

Chaque utilisateur a un jeton d'achat émis par Inngame. Passez-le à StoreKit pour que les notifications de l'App Store retrouvent l'utilisateur (voir Intégrations) :

if let token = Inngame.purchaseToken {   // UUID, nil before the first session
    let result = try await product.purchase(options: [.appAccountToken(token)])
}

Notifications push

Transmettez le jeton APNs de l'appareil pour que les réponses atteignent l'utilisateur quand le chat est fermé. Envoyez votre clé APNs dans le tableau de bord (voir Intégrations).

func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    #if DEBUG
    Inngame.setPushToken(deviceToken, sandbox: true)
    #else
    Inngame.setPushToken(deviceToken)
    #endif
}

Quand une notification est touchée, laissez Inngame la reconnaître. La fonction renvoie true pour les notifications Inngame et met Inngame.shared.wantsToOpen à vrai, que vous pouvez observer pour afficher le chat :

func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse) async {
    if Inngame.handleNotification(response.notification.request.content.userInfo) {
        // Open the chat (or observe Inngame.shared.wantsToOpen in SwiftUI)
    }
}

Déconnexion et suppression des données

// The user signed out: this device starts over as a new anonymous user
Inngame.logout()

// Right to erasure: deletes the user, their conversations and photos
try await Inngame.shared.deleteMyData()

Référence de l'API

AppelRôle
Inngame.configure(appKey:baseURL:)Démarre le SDK.
Inngame.identify(userId:name:email:token:metadata:)Lie l'appareil à votre compte ; vérifié avec un JWT.
Inngame.purchaseTokenLe jeton d'achat Inngame (UUID?) pour l'appAccountToken de StoreKit.
Inngame.logout()Nouvel utilisateur anonyme sur cet appareil.
Inngame.setContext(_:_:)Contexte personnalisé sur les messages suivants (nil pour retirer).
Inngame.track(_:properties:)Événement pour les automatisations.
Inngame.setPushToken(_:sandbox:)Enregistre le jeton APNs.
Inngame.handleNotification(_:)true si la notification vient d'Inngame ; met wantsToOpen à vrai.
InngameChatView(showsCloseButton:)Vue SwiftUI du chat (bouton de fermeture affiché par défaut).
Inngame.present(from:)Présente le chat depuis un contrôleur de vue UIKit.
Inngame.shared.unreadCountNombre de non-lus, publié.
Inngame.shared.wantsToOpenPublié ; mis à vrai par un push ou une automatisation qui demande d'ouvrir le chat.
Inngame.shared.messages · user · config · isBlocked · lastErrorÉtat publié : messages chargés, l'utilisateur vu par le serveur, réglages de l'inbox (apparence, photos, disponibilité…), blocage, dernière erreur.
Inngame.shared.send(_:imageData:)Envoie un message depuis le code (async).
Inngame.shared.searchFAQ(_:)Cherche dans vos articles d'aide (async).
Inngame.shared.pendingMessages publiés pas encore acceptés par le serveur (file hors ligne).
Inngame.isUnavailabletrue tant que votre support est indisponible (votre espace Inngame est suspendu) ; aussi publié sous Inngame.shared.isUnavailable. Le chat affiche alors « Le support est indisponible pour le moment. Réessayez plus tard. » dans la langue de l'utilisateur et masque la zone de saisie ; les messages non envoyés attendent dans pending et partent au retour du support. Le SDK revérifie toutes les 10 minutes chat ouvert et à chaque ouverture, et revient à la normale tout seul. En attendant, deleteMyData() lève une InngameError avec isSuspension.
Inngame.shared.deleteMyData()Efface les données de l'utilisateur (async throws).