Principe et contraintes

Le besoin initial était d’utiliser un QR code imprimé sur une carte de visite pour proposer le téléchargement d’une application mobile. Un QR code ne peut encoder qu’une seule URL, alors que le téléchargement doit pointer vers trois destinations : l’App Store pour iOS, le Play Store pour Android et le site web pour tout autre dispositif. De plus, l’URL imprimée doit rester fonctionnelle même si les destinations évoluent, ce qui impose l’utilisation d’un lien intermédiaire contrôlé par le propriétaire.

Génération du QR code et optimisation

Le QR code est créé dans le navigateur à l’aide d’un générateur JavaScript qui exporte du SVG ou du PNG, formats requis par les imprimeurs. Le lien court https://go.pokernexus.com/app a été choisi pour limiter la version du QR (densité moindre) et ainsi obtenir des modules plus grands, plus fiables à l’échelle d’une carte de visite. Lorsqu’un logo est ajouté au centre, le générateur augmente le niveau de correction d’erreur à H, capable de récupérer environ 30 % des modules endommagés, garantissant la lecture malgré l’obstruction du logo.

Service de redirection Hono

Le serveur de redirection est une petite application Hono écrite en TypeScript. Chaque destination est décrite par une interface Destination contenant l’URL cible et un indicateur de gestion de la chaîne de requête (forward ou drop). Le tableau de routage ne comporte que deux entrées : la racine / renvoie toujours le site web, et /app invoque la fonction storeFor qui sélectionne la destination en fonction du User-Agent.

interface Destination { readonly url: string; readonly query: "forward" | "drop"; }
const WEBSITE: Destination = { url: "https://pokernexus.com", query: "forward" };
const APP_STORE: Destination = { url: "https://apps.apple.com/app/id6800184564", query: "drop" };
const PLAY_STORE: Destination = { url: "https://play.google.com/store/apps/details?id=com.pokernexus.app", query: "drop" };

La logique de sélection repose sur trois expressions régulières : BOT détecte les crawlers, IOS les appareils iOS et ANDROID les appareils Android. L’ordre de ces tests est crucial : les bots sont évalués en premier afin d’éviter le « cloaking », c’est‑à‑dire d’envoyer un robot d’indexation vers un store d’applications, ce qui serait considéré comme une manipulation de SEO.

export const storeFor = (userAgent: string | null | undefined): Destination => {
  if (!userAgent) return WEBSITE;
  if (BOT.test(userAgent)) return WEBSITE;
  if (IOS.test(userAgent)) return APP_STORE;
  if (ANDROID.test(userAgent)) return PLAY_STORE;
  return WEBSITE;
};

Détection d’appareil et limites

Le système s’appuie uniquement sur le User-Agent, ce qui pose des problèmes avec les navigateurs modernes. Safari sur iPad, depuis iPadOS 13, envoie un User-Agent identique à celui d’un Mac, rendant impossible la distinction sans client hints. Les Sec-CH-UA-* offrent une alternative structurée, mais Safari ne les supporte pas, limitant la précision de la détection. Une première implémentation utilisait une page intermédiaire JavaScript qui testait navigator.maxTouchPoints pour différencier iPad et Mac, mais elle a été abandonnée à cause du coût supplémentaire d’un round‑trip HTTP et d’une réponse 200 non cacheable. Ainsi, la solution actuelle privilégie la robustesse : en cas de doute, elle renvoie vers le site web, qui reste fonctionnel sur tous les appareils, évitant ainsi d’afficher un store incompatible.