Tous les articles

Tous les articles

Une carte de visite à scanner et à enregistrer

Un QR code imprimé qui ouvre une carte et enregistre le contact, et ce qu'un Pixel m'a appris sur les photos vCard et Chrome sur Android.

Donner mes coordonnées en personne voulait dire les dicter. Le site public a désormais une carte : un QR code imprimé ouvre une page avec mon nom, mon rôle et les moyens de me joindre, et un bouton qui enregistre le contact dans le téléphone. Une page non listée affiche le code en grand, pour tendre un téléphone à quelqu'un, ou seul sur la page, pour l'imprimer.

Le code contient une redirection#

Une URL imprimée ne peut plus jamais changer, alors le code ne contient pas l'adresse de la carte elle-même. Il contient un chemin court à la racine du site, auquel le serveur web répond par une redirection vers la carte. La page derrière peut ainsi déménager, et une charge plus courte donne aussi un symbole plus petit, aux modules plus grands. Avec une correction d'erreur de niveau M, qui résiste à une impression abîmée ou en partie masquée, l'URL tient encore dans un code de version 2.

C'est à la redirection que la langue se choisit. Partout ailleurs sur le site, la langue est dans le chemin et n'est jamais devinée à partir du navigateur, mais un code ne porte qu'une URL : la première langue préférée du téléphone est le seul signal disponible. Le français reçoit la carte française, tout le reste la carte anglaise. La redirection est une 302 et pas une 301, parce que les téléphones gardent une redirection permanente pour de bon et que la carte derrière peut déménager. La réponse n'est pas mise en cache et varie selon l'en-tête de langue, pour qu'aucun cache en chemin ne serve la carte anglaise à un téléphone francophone.

Chaque build encode sa propre origine : un build de staging imprime un code de staging. Le bandeau qui signale un build autre que le site en production reste sur la feuille imprimable du code, même sur papier, pour qu'un code de staging ne passe pas pour le vrai. La carte elle-même abandonne le bandeau, puisque la personne qui vient de la scanner n'a rien à faire d'un avis de déploiement.

Le code est dessiné une seule fois, au build. Un petit plugin de build encode la charge, trace les suites de modules sombres de chaque rangée comme des rectangles dans un seul chemin SVG, et écrit le code en SVG et en PNG. Aucune bibliothèque QR n'arrive chez le visiteur, et la page affiche le SVG comme une image, ce qui garde son chemin hors d'un budget JavaScript qui n'avait plus de place.

Une vCard écrite à la main, avec soin#

Le fichier de contact est une vCard 3.0 et pas 4.0 : la 3.0 est la version qu'iOS et Android importent tous deux sans rien demander, et la 4.0 est plus récente sans être mieux prise en charge. Le format est strict sur trois points qu'une chaîne écrite à la main rate, et chacun donne un contact importé avec un champ en moins, ou pas importé du tout. Les lignes se terminent par CRLF. Les virgules et les points-virgules dans une valeur sont échappés, puisqu'ils séparent les parties d'un champ structuré. Aucune ligne ne dépasse 75 octets, et le repli compte des octets UTF-8 et coupe entre deux caractères, parce qu'un repli au milieu d'un caractère multi-octets le corrompt.

Chaque langue a son propre fichier, parce que l'intitulé du poste est traduit. Le portrait est intégré en JPEG : les applications de contacts ignorent une photo donnée par URL, et aucune ne lit le WebP. Le champ du site web pointe vers le site de production dans chaque build, parce qu'un contact enregistré survit au déploiement d'où il vient.

Ce qu'a dit un Pixel#

Tous les tests unitaires passaient, et sur un Pixel la carte échouait à la seule chose pour laquelle elle existe. Il y avait deux problèmes distincts.

Chrome sur Android télécharge tout fichier .vcf, quels que soient son type de contenu et sa disposition. Le bouton d'enregistrement rangeait un fichier dans les téléchargements au lieu de proposer le contact. Sur Android, le bouton ouvre désormais l'écran de nouveau contact du téléphone par un intent d'insertion, déjà rempli. Un intent ne porte que des chaînes simples, donc ni photo ni champ de site web ; le site et le profil LinkedIn vont dans les notes, et le fichier devient un lien sous le bouton pour qui veut aussi la photo. Le filtre de liens du design system transforme à juste titre une URL intent: en # : le bouton garde donc le fichier comme adresse et lance l'intent depuis son gestionnaire de clic.

Le second problème était pire. Ouvrir ce fichier téléchargé mettait le base64 de la photo dans l'adresse professionnelle, alors que le fichier était valide. Mes lectures m'avaient mené à une théorie sur une ligne vide après la photo, avec une confiance moyenne, et elle était fausse. C'est l'appareil qui a tranché : quatre variantes du fichier, chacune avec un prénom différent pour que l'application de contacts ne les fusionne pas, importées en une seule série. Une photo repliée juste après l'adresse atterrit dans l'adresse, avec ou sans ligne vide. La même photo non repliée, ou repliée et remontée après le nom, s'importe comme avatar. Le fichier porte désormais la photo juste après le nom, suivie d'une ligne vide, soit la forme qu'écrit l'exportateur d'Android lui-même et, octet pour octet, la variante qui a fonctionné.

Tester l'intent de bout en bout avait sa propre bizarrerie. Dans Chromium, une navigation intent: laisse la page où elle est, et la requête en échec ne rapporte que intent:, sans le fragment qui contient les valeurs. Le test lit plutôt l'URL complète dans l'événement de navigation du protocole DevTools.

Les corrections plus petites#

La phrase d'introduction de la page du code est limitée à la largeur du code. La version française se recomposait sinon à l'arrivée de la police de la marque, et un texte centré qui se recompose bouge de côté autant que vers le bas, ce qui triplait le décalage de mise en page de la page.

Le lendemain, la carte est passée à ma propre adresse e-mail plutôt qu'à la boîte partagée de l'activité, parce que la carte présente une personne et que le contact qu'on en tire doit joindre cette personne. Le reste du site garde l'adresse de l'activité.