Skip to content

Implémenter et appeler un service ​

Contrat commun aux iframes SDK d’applis et widgets. Un plugin HTML utilise son helper, pas kapp.services. Un service appelle une fonction d’une exécution déjà présente ; le lanceur kapp.apps crée une exécution dédiée.

Déclaration, disponibilité, nom ​

Déclarer une capacité ne l’enregistre pas au runtime. await kapp.services.implements(name, callback) cherche la déclaration dans le manifeste d’une appli ou la configuration du widget, puis l’enregistre. Sans déclaration, il rejette Service config not defined. Lire services pour le manifeste ; dans l’agent widgets, read_sdk_doc {service: "services"} décrit son blueprint.

Le nom local ne contient que lettres, chiffres, _ ou -. Le nom public d’un service d’appli est préfixé par son slug : mon-appli.status. Celui d’un widget n’a pas ce préfixe ; choisir un nom spécifique au channel. Découvrir le nom effectif via await kapp.services.get(), sans reconstruire un identifiant à partir du titre affiché.

L’enregistrement appartient à l’exécution, pas à l’installation. Elle doit rester présente pour répondre ; dans un widget, enregistrer un callback ne maintient pas la Promise de executeWidgetFunction ouverte. Le retrait explicite utilise await kapp.services.remove(nameLocal) ou le modèle retourné par implements. Le core retire aussi les services quand leur exécution disparaît. Une liste déjà lue peut devenir périmée.

Enregistrement au runtime ​

Déclarer un service dans la configuration ne l'enregistre pas au runtime. await kapp.services.implements(name, callback) relie la fonction locale au service déclaré et attend la confirmation de son enregistrement par le serveur. create(data) attend aussi cette confirmation pour un service défini directement dans le code.

Si l'enregistrement échoue, l'appel rejette avec l'erreur reçue et le SDK V2 retire son entrée locale provisoire. Un nouvel implements() peut alors retenter l'enregistrement. Plusieurs implements() pendant un même enregistrement attendent tous son résultat ; la dernière fonction fournie remplace la précédente. Une fois l'enregistrement réussi, un nouvel implements() remplace seulement la fonction, sans republier la configuration. La fonction peut être appelée dès que le Core publie le service, avant même la réception de la confirmation HTTP par le fournisseur.

Exemple : un fournisseur sans effet externe ​

La déclaration de status avec args: [], domains: ['app', 'widget'], description et rôle null est fournie dans services. Exécuter le bloc après démarrage de l’appli ; il ne termine pas l’appli.

js
const statusService = await kapp.services.implements('status', async () => {
    return {ok: true, state: 'ready'};
});
kapp.onBeforeExit('*', async () => {
    await statusService.remove();
});

L’appelant applique le nom exact découvert (ici le fournisseur a le slug mon-appli) :

js
const services = await kapp.services.get();
const target = services.find(service => service.name === 'mon-appli.status');
if (!target) throw new Error('Fournisseur indisponible');
const result = await kapp.services.call(target.name, {}, {timeoutSeconds: 5});
if (!result || result.ok !== true) throw new Error('Réponse de service invalide');

Pour une appli, le fournisseur déclare app-service:create ; cet appelant déclare app-service:read et app-service:call. Le callback est async, valide ses entrées et renvoie un résultat JSON explicite. Les arguments d’un service sont distincts des arguments de lancement.

Autorisations et erreurs ​

domains filtre les contextes appelants (app, widget, aibot, aiassistant, ou *). N’élargir qu’aux consommateurs voulus. granted_role intervient dans les parcours qui possèdent un acteur et contrôlent son rôle, notamment l’assistant ; ce n’est pas une authentification de viewer universelle pour un appel SDK app→service. Un argument userId fourni par l’appelant n’est pas une preuve d’identité. Voir permissions.

Le backend accepte timeoutSeconds entier de 1 à 1 800, défaut 60 pour les services. L’attente réelle reste bornée aussi par les délais du client et les transports ; ce paramètre ne garantit pas 30 minutes de connexion ouverte. Absence de service, refus de scope, échec du relais ou expiration peuvent rejeter l’appel. Un timeout ne prouve pas l’absence d’effet : ne pas répéter automatiquement une mutation.

Résultats et erreurs d'un appel ​

await kapp.services.call(name, args) renvoie le résultat JSON du callback du fournisseur. Avec le SDK V2 fournisseur et le Core mis à jour, une exception (throw ou Promise rejetée) fait rejeter l'appel avec {status: 500, reason: "..."} : la traiter avec try/catch. Une réponse métier volontaire comme {ok: false, reason: "partie_fermee"} reste un résultat normal que l'appelant doit interpréter. Les résultats de succès gardent leur format.

Limite connue — anciens fournisseurs : leurs exceptions peuvent encore arriver comme des données normales ({} pour un Error). Valider le résultat attendu ; une convention explicite {ok, reason} reste utile entre versions mixtes. La correction nécessite le SDK du fournisseur et le Core mis à jour. Les appelants V2 utilisant kapp.services.call reçoivent le rejet ; l'ancien callService du SDK V1 conserve ses retours historiques. Un échec ou un timeout ne garantit pas l'absence d'effet : ne pas rejouer automatiquement une action qui modifie l'état.

Recette avec deux applis distinctes ​

Utiliser deux applis du même channel, un fournisseur et un consommateur, avec les manifestes propres à chaque rôle ; deux fenêtres de la même appli ne valident pas l'échange. Compiler et servir chaque projet, puis contrôler son état (workflow DEV HUB). Relever le kapp.uuid de chaque exécution pour prouver que l'appel les traverse.

CasComportement attendu
Fournisseur absentService absent de get() ; un appel direct rejette (404).
Fournisseur présentNom public découvert, réponse {ok: true, ...}.
Refus métier expliciteRéponse {ok: false, reason} résolue, à interpréter par l’appelant.
Exception du callbackRejet avec status et reason avec le SDK V2 fournisseur et le Core corrigés ; ancien format possible entre versions mixtes.
Réponse plus lente que timeoutSecondsRejet côté appelant alors que le fournisseur peut terminer : pas de renvoi automatique.
remove puis nouvel implementsLe nom disparaît puis réapparaît ; redécouvrir avant d'appeler.
Arrêt ou relance du fournisseurRejet de l'appel en cours, nouvel uuid ensuite ; redécouvrir.

Cette recette ne couvre ni l'isolation entre channels, ni les appels concurrents, ni les rôles de l'assistant, ni kapp.apps.