Développer l'application de la borne
Développez l'application de la borne autour d'une expérience OmniLab : URLs, messages, identité et réinitialisation.
Ce guide explique les aspects techniques du maintien d'OmniLab dans un wrapper borne ou une application wrapper. Utilisez-le avec les articles d'intégration publics lorsque votre équipe contrôle la page hôte, le conteneur WebView ou l'environnement d'exécution de l'appareil autour d'OmniLab.
Choisir le bon modèle de wrapper
| Modèle de wrapper | Bon usage | Ce que votre équipe gère |
|---|---|---|
| Iframe manuel dans une page web | Une page borne navigateur ou un wrapper de site existant | La page hôte, le dimensionnement de l'iframe, les permissions et l'UI environnante |
| Tag JavaScript player | Une page hôte réutilisable doit charger différentes campagnes depuis l'URL | Le template de page hôte et la stratégie de paramètre de page |
| WebView native ou managée | Une app mobile ou un wrapper borne contrôle tout l'écran | L'environnement WebView, le bridge, les permissions et le comportement de session |
Le modèle de wrapper détermine si la connexion fonctionne
Si votre borne charge l'expérience directement comme page dans une WebView, OmniLab est la page de premier niveau et ses cookies sont first-party — connexion, fci et continuité de session fonctionnent normalement.
Si en revanche vous construisez une page HTML de borne sur votre propre domaine et y placez l'expérience dans une iframe, OmniLab devient une iframe tierce. Les navigateurs bloquent ou isolent ses cookies, et l'aller-retour de connexion perd son état en cours de route — une carte de fidélité scannée arrive donc jusqu'à la vérification d'identité et pas plus loin.
Chargez l'expérience comme page de premier niveau, ou servez-la depuis un sous-domaine du domaine qui sert la page de la borne. C'est la différence entre un scan de carte qui fonctionne et un qui échoue seulement une fois sur site.
Utiliser des URLs prêtes pour l'intégration
Partez des mêmes URLs OmniLab documentées dans les guides d'intégration publics :
- page d'accueil de campagne lorsque le wrapper doit afficher plusieurs expériences
- URL directe de point de contact lorsque le wrapper doit lancer immédiatement une expérience
embedded=1lorsque vous utilisez un chemin d'intégration iframe manuel ou WebView
Messages parent-enfant
Une borne occupe tout l'écran : l'essentiel de ce que dit le guide d'intégration sur la hauteur ne s'applique donc pas ici. C'est votre wrapper qui décide de la taille de l'iframe, et rien de ce qu'envoie OmniLab ne doit la modifier. Deux points restent importants.
Mode de dimensionnement
L'expérience annonce à chaque chargement de page comment elle veut être dimensionnée :
{ type: "sizing", mode: "content" | "viewport" }Les jeux annoncent viewport : ils remplissent l'iframe qu'on leur donne et ne rapportent volontairement aucune hauteur. Les pages d'accueil et les listings annoncent content, puis envoient des messages resize portant une hauteur mesurée.
Sur une borne en plein écran, vous pouvez garder l'iframe à la taille de l'écran et ignorer les deux. Ne lisez le mode que si votre wrapper affiche parfois l'iframe sur moins que le plein écran : dans ce cas, les messages resize d'une page content sont ce qui vous indique sa hauteur nécessaire.
Ne dimensionnez pas l'iframe d'un jeu d'après ce qu'il rapporte
Une page dimensionnée par le viewport mesure window.innerHeight, qui dans une iframe est la hauteur de cette iframe. Réinjecter cette valeur crée une boucle sans entrée extérieure et l'iframe se fige à sa taille initiale. OmniLab supprime resize en mode viewport pour l'éviter — donc si votre borne attend une hauteur avant d'afficher un jeu, elle attendra indéfiniment. Donnez à l'iframe la taille de l'écran dès le départ.
Placement des drawers
C'est le point qui piège les wrappers de borne écrits à la main. Quand l'expérience ouvre un drawer — panneau de récompense, formulaire, étape de connexion — elle envoie open-modal puis affiche le drawer sans positionnement tant que l'hôte n'a pas répondu.
| Sens | Message | Signification |
|---|---|---|
| D'OmniLab | open-modal | Le premier drawer s'est ouvert. Les drawers imbriqués ne le répètent pas. |
| D'OmniLab | close-modal (avec source) | Le dernier drawer s'est fermé : cela signifie toujours « entièrement fermé ». |
| Vers OmniLab | ready-to-open-modal (avec parentHeight, parentWidth, scrollTop, stickyOffset, position) | Accuse réception de l'ouverture et indique au drawer la portion de l'iframe visible par le visiteur. À envoyer à chaque open-modal. |
| Vers OmniLab | ready-to-close-modal (avec source) | Ferme le drawer au sommet de la pile, quand votre propre interface le referme. |
Sans accusé de réception, les drawers s'ouvrent quand même — ils se replient sur une présentation par défaut en pleine hauteur. Sur une borne en plein écran c'est souvent acceptable, puisque l'iframe est toute la zone visible et qu'aucun header sticky n'est à éviter. Testez sur le panneau réel avant de décider de vous en passer, en particulier en portrait.
<script>
const OMNILAB_ORIGIN = "https://experience.example.com";
const frame = document.getElementById("omnilab-embed");
window.addEventListener("message", (event) => {
if (event.origin !== OMNILAB_ORIGIN) return;
const data = event.data;
if (!data || typeof data !== "object") return;
if (data.type === "open-modal") {
// Indiquez au drawer l'espace dont il dispose : sur une borne en plein
// écran, c'est simplement l'iframe elle-même.
frame.contentWindow.postMessage(
{
type: "ready-to-open-modal",
position: 0,
parentHeight: frame.clientHeight,
parentWidth: frame.clientWidth,
scrollTop: 0,
stickyOffset: 0,
},
OMNILAB_ORIGIN
);
}
});
</script>Vérifiez toujours event.origin. Un wrapper de borne qui réagit à n'importe quel message reçu réagira aux messages de n'importe quelle page chargée dans l'iframe.
Pattern JavaScript player
Lorsqu'une seule page borne doit charger différentes campagnes, utilisez le tag player OmniLab avec un paramètre de requête tel que id.
<div id="omnilab-container"></div>
<script>
(function (b, o, n, u, s) {
var a, t;
a = b.createElement(u);
a.async = 1;
a.src = s;
t = b.getElementsByTagName(u)[0];
t.parentNode.insertBefore(a, t);
o[n] = o[n] || [];
})(document, window, "_om_async", "script", "https://<omnilab-script-host>/omplayer.js");
_om_async.push([
"init",
{
domain: "experience.example.com",
targetId: "omnilab-container",
queryParam: "id",
},
]);
</script>Démarrer une session en tant que membre connu avec fci
fci (foreign contact identifier) porte l'identifiant utilisé par votre propre système client pour un contact — typiquement la valeur encodée dans le code-barres d'une carte de fidélité. Ajoutez-le à l'URL de l'expérience quand la borne scanne une carte :
https://experience.example.com/<campaign-public-link>?fci=<valeur-scannée>&embedded=1Ce qu'OmniLab en fait :
- La présence de
fciimpose une session authentifiée. OmniLab redirige vers le flux d'identité client configuré pour l'organisation au lieu de charger l'expérience anonymement, et conserve la query string d'origine à travers la redirection. - Si l'identifiant correspond à un contact, le visiteur arrive dans l'expérience déjà connecté, et la participation est attribuée à ce contact.
- S'il ne correspond à rien, OmniLab affiche une boîte de dialogue bloquante et le visiteur ne peut pas continuer. Il n'y a pas de repli anonyme.
fci est également lu depuis l'en-tête Referer sur les appels API de l'expérience, il continue donc de s'appliquer aux requêtes émises après le chargement initial de la page.
`fci` nécessite un contexte first-party
fci impose une redirection via votre flux d'identité, et cet aller-retour repose sur des cookies. Il n'aboutit que si OmniLab est chargé comme page de premier niveau, ou depuis un sous-domaine de la page qui l'encadre — voir la note sur le modèle de wrapper ci-dessus. Dans une iframe cross-domain, la redirection revient sans rien à reprendre, et tous les scans échouent de la même façon : on croit alors que les identifiants sont faux plutôt que le conteneur.
`fci` nécessite une connexion d'identité client
Sans intégration d'identité client configurée pour l'organisation, OmniLab n'a rien contre quoi résoudre l'identifiant et chaque scan aboutit à la boîte de dialogue bloquante. Vérifiez que la connexion est active dans l'environnement cible avant de démarrer les tests borne. Voir Comptes clients.
Le titre et le corps de cette boîte de dialogue sont surchargeables via les labels personnalisés de l'expérience (fci.identity.error.title et fci.identity.error.description), ce qui vous permet de remplacer le texte par défaut par un message indiquant au visiteur quoi faire sur la borne.
Pour des sessions borne anonymes, omettez complètement fci et placez un formulaire d'acquisition sur le point de contact. Le visiteur le complète sur le clavier de la borne avant de jouer.
Détecter une session abandonnée avec omnilab:idle
Une borne laissée en cours de jeu bloque le visiteur suivant. L'expérience peut signaler au wrapper que personne n'a touché l'écran, pour qu'il propose une réinitialisation.
Le travail se répartit clairement en deux, et la frontière suit celle de l'iframe :
| Côté | Rôle | Qui le développe |
|---|---|---|
| À l'intérieur de l'iframe | Surveille l'activité et émet omnilab:idle | Un script d'organisation, installé côté OmniLab |
| À l'extérieur de l'iframe | Reçoit le signal, affiche la fenêtre, met fin à la session | L'application de la borne |
Aucune des deux moitiés n'est un comportement natif de la plateforme, et OmniLab ne fournit pas du tout la moitié côté parent. Le code ci-dessous est un document de référence pour celui qui développe l'application de la borne : il vit dans son code, s'exécute dans son wrapper, et c'est à lui de l'implémenter, de le tester et de le maintenir. Considérez-le comme une spécification à transmettre, pas comme quelque chose que vous installez.
La moitié à l'intérieur de l'iframe est un script d'organisation qu'un administrateur ajoute au niveau de l'organisation globale, limité aux campagnes qui tournent sur borne. Voir Écrire des scripts d'organisation pour la façon dont ces scripts sont écrits et installés.
Le script à l'intérieur de l'expérience
<script>
(function () {
// Ne s'exécute que sur les campagnes qui tournent réellement sur borne.
const TRIGGER_STRINGS = ["summer-experience-kiosk", "back-to-school-2026-kiosk"];
const shouldActivate = TRIGGER_STRINGS.some((trigger) =>
window.location.href.includes(trigger)
);
if (!shouldActivate) return;
const IDLE_TIMEOUT = 10000;
let timer;
function sendIdle() {
window.parent.postMessage(
{ type: "omnilab:idle", timestamp: Date.now() },
"*"
);
reset();
}
function reset() {
clearTimeout(timer);
timer = setTimeout(sendIdle, IDLE_TIMEOUT);
}
["click", "mousedown", "mousemove", "keydown", "scroll", "touchstart", "touchmove"].forEach(
(e) => document.addEventListener(e, reset, { passive: true })
);
reset();
})();
</script>À savoir avant de développer le côté parent :
- La liste d'autorisation est une correspondance de sous-chaîne sur l'URL complète. Chaque entrée est comparée avec
includes()àwindow.location.href: un lien public de campagne fonctionne donc comme entrée. Tout ce qui ne correspond pas sort immédiatement et n'installe aucun listener — c'est ce qui garde le script inerte sur vos campagnes hors borne. - Le minuteur redémarre après chaque signal. Une borne abandonnée émet
omnilab:idletoutes les 10 secondes, pas une seule fois. Votre listener doit tolérer les répétitions. - Sept événements d'activité réinitialisent le minuteur —
click,mousedown,mousemove,keydown,scroll,touchstart,touchmove— enregistrés en mode passif surdocument. - Seules les interactions à l'intérieur de l'iframe comptent. Un visiteur qui touche l'interface propre à la borne ne réinitialise pas ce minuteur.
Installez-le comme script iframe uniquement sur le déclencheur de visite, puis republiez chaque campagne concernée. Un script enregistré mais non republié n'atteint pas l'expérience en ligne.
Ce que l'application de la borne doit implémenter
Tout ce qui suit s'exécute dans le wrapper de la borne, pas dans OmniLab. Transmettez-le à votre prestataire borne comme le contrat que son côté doit respecter.
let modalOpen = false;
let countdownInterval = null;
window.addEventListener("message", (event) => {
if (event.data?.type === "omnilab:idle" && !modalOpen) {
showInactivityModal();
}
});
function showInactivityModal() {
modalOpen = true;
let countdown = 10;
updateCountdown(countdown);
countdownInterval = setInterval(() => {
countdown -= 1;
updateCountdown(countdown);
if (countdown <= 0) closeIframe();
}, 1000);
}
function stayInSession() {
clearInterval(countdownInterval);
modalOpen = false;
hideModal();
}
function closeIframe() {
clearInterval(countdownInterval);
modalOpen = false;
document.getElementById("omnilab-embed").src = "";
showWelcomeScreen();
}Testez `event.data.type`, pas `event.data`
Le message est un objet : event.data === "omnilab:idle" ne correspond donc jamais et la fenêtre n'apparaît pas. Lisez event.data?.type. C'est la même enveloppe que les messages resize et de modale ci-dessus : un seul listener peut donc tous les traiter.
Le garde modalOpen n'est pas optionnel. Sans lui, le signal répété relance le compte à rebours toutes les 10 secondes et la fenêtre n'expire jamais.
Prévoyez la durée totale de réinitialisation lors du dimensionnement du parcours : 10 secondes de silence avant le premier signal, plus la durée du compte à rebours de votre fenêtre. Avec un compte à rebours de 10 secondes, une borne abandonnée se libère en une vingtaine de secondes.
Ce que la fenêtre doit proposer
C'est le prestataire borne qui la conçoit et la développe. OmniLab n'a aucun contrôle dessus et ne peut pas la styler.
| Élément | Rôle |
|---|---|
| Message | Indique au visiteur que la session va se terminer |
| Compte à rebours | Minuteur visible pour que la réinitialisation ne soit pas une surprise |
| Rester | Ferme la fenêtre et revient à l'expérience |
| Retour à l'accueil | Ferme l'iframe immédiatement et affiche l'écran d'accueil |
C'est le vidage du src de l'iframe qui met réellement fin à la session — l'expérience OmniLab n'a aucun moyen de se décharger elle-même : si l'application de la borne n'agit pas sur le signal, il ne se passe tout simplement rien.
Responsabilités de l'hôte hors d'OmniLab
Votre wrapper doit prendre en charge :
- les permissions au niveau de l'appareil pour la caméra, le microphone et la géolocalisation
- la fenêtre d'inactivité et la décision de réinitialiser l'iframe — OmniLab peut signaler l'inactivité, mais seul le wrapper peut fermer la session
- le matériel de scan ou les périphériques externes
- le passage de session entre votre wrapper et OmniLab
- la journalisation et les diagnostics de support à distance pour le conteneur lui-même
Checklist de debug
- Confirmez que le wrapper charge la bonne URL de campagne ou de point de contact.
- Vérifiez que l'iframe ou la WebView peut recevoir la caméra et les autres permissions requises.
- Contrôlez que les événements
postMessaged'OmniLab atteignent le wrapper parent, et que votre listener vérifieevent.origin. - Ouvrez un jeu et vérifiez que l'iframe est déjà en plein écran — une page dimensionnée par le viewport n'envoie aucune hauteur, donc un wrapper qui en attend une n'affiche rien.
- Testez les drawers, claviers et flux de modales dans le vrai conteneur borne, pas seulement dans un onglet de navigateur autonome.
- Journalisez les messages OmniLab bruts pendant le staging pour comparer rapidement le comportement attendu et le comportement réel.
Gardez les secrets hors du client borne
Si la borne a aussi besoin d'un accès API server-to-server, effectuez l'authentification et les appels API depuis votre propre backend. N'intégrez pas les identifiants client OmniLab dans la page borne ou le bundle WebView.
Patterns d'URL et paramètres de requête pour borne
Les déploiements borne utilisent généralement l'un de ces patterns d'URL. Incluez toujours embedded=1 lors du chargement dans une iframe ou une WebView.
| Cas d'usage | Pattern d'URL |
|---|---|
| Page d'accueil de campagne | https://experience.example.com/<campaign-public-link>?embedded=1 |
| Point de contact direct | https://experience.example.com/<campaign-public-link>?c=<touchpoint-id>&embedded=1 |
| Espace spécifique | https://experience.example.com/<campaign-public-link>?s=<space-id>&embedded=1 |
| Langue fixe | Ajoutez &l=<language-code> |
| Variante fixe | Ajoutez &v=<variant-id> |
| Membre fidélité connu | Ajoutez &fci=<foreign-contact-identifier> |
Pour les déploiements utilisant le tag JavaScript player, l'URL de la page hôte contrôle quelle campagne se charge :
https://kiosk.example.com/?id=summer-campaignhttps://kiosk.example.com/?id=summer-campaign%3Fc%3D<touchpoint-id>%26embedded%3D1
Encodez toute query string imbriquée avant de publier le lien vers le tag player.
Pour aller plus loin
Gestion de l'inactivité
Réinitialisez automatiquement une borne quand un visiteur s'en va en cours d'expérience, pour que l'écran soit prêt pour la personne suivante.
Checklist d'implémentation borne
Menez un déploiement borne en trois étapes — architecture, tests et lancement — et sachez à qui adresser chaque type de problème.