Extension Chrome qui réclame automatiquement les coffres bonus Twitch pendant que vous regardez · survit au churn du CSS obfusqué de Twitch grâce à une cascade de sélecteurs sémantiques · aucun backend
Architecture
Chrome injecte content.js dans chaque page twitch.tv. Un garde readyState démarre la boucle que l'événement load ait déjà été déclenché ou non, puis interroge toutes les 5 s. À chaque tick, findClaimButton() limite la recherche au résumé des points de communauté et parcourt une cascade de 4 sélecteurs sémantiques ; quand le coffre apparaît il remonte jusqu'au <button> englobant avec closest(), vérifie qu'il n'est pas désactivé, et clique. Sur une réclamation réussie il incrémente claimedBonusChestCount dans chrome.storage.local. Le popup Stats lit ce compteur à l'ouverture et en déduit les points de chaîne gagnés.
Déploiement : Extension → Chrome Web Store · MV3, tout s'exécute côté client
Résultats
- ≤ 5 s · coffre visible → réclamé
- 0 · serveur · toute la logique s'exécute dans l'onglet
- 4 · sélecteurs de repli, sémantiques avant cosmétiques
- 2 · permissions au total : storage + hôte twitch.tv
Compétences démontrées : stratégie de sélecteurs DOM résiliente · extension Chrome MV3 · scripts de contenu compatibles SPA · surface de permissions minimale
Problèmes et solutions
Chaque choix répond à une contrainte : l'extension vit à l'intérieur d'une page qu'elle ne contrôle pas et qui change sans prévenir.
▓ Survivre au churn du CSS de Twitch
Problème : Twitch construit son interface avec styled-components : les noms de classe sont des hachages de compilateur comme ScCoreButton-sc-ocjdkq-0 qui changent à chaque rebuild du frontend. Les premières versions s'accrochaient à ces hachages et cassaient silencieusement, quatre releases distinctes dans l'historique git sont des correctifs de sélecteurs.
J'ai remplacé le sélecteur de hachage par une cascade priorisée de 4 crochets sémantiques : la classe de l'icône du coffre, deux des propres attributs data-test-selector de Twitch, et un data-a-target. Une correspondance peut être une icône interne, donc le code remonte jusqu'à l'élément cliquable avec closest('button').
- Hachages de classe rejetés après des cassures répétées : ce sont des sorties de compilateur, pas une API
- Correspondance sur le texte du bouton rejetée : elle casse dans toute langue autre que l'anglais
- Les crochets de test restent des internes de Twitch, donc 4 replis indépendants plutôt que de faire confiance à un seul
▒ Polling plutôt que MutationObserver
Problème : Le coffre apparaît à des moments imprévisibles, et la détection s'exécute dans un onglet déjà occupé à lire de la vidéo. Un mauvais choix ici coûte du CPU à chaque message de chat.
Un simple setInterval de 5 s exécutant un querySelector limité par tick : environ 180 vérifications no-op peu coûteuses par coffre à la cadence d'environ 15 min de Twitch.
- MutationObserver essayé en premier (l'expérience survit sous forme de content copy.js) : le chat de Twitch mute constamment, donc le callback se déclenchait en rafales
- Jusqu'à 5 s de latence de réclamation acceptées en échange d'une boucle trivialement correcte
- L'intervalle survit aussi à la navigation SPA de Twitch, puisque le document persiste entre les changements de chaîne
▚ Aucun service worker en arrière-plan
Problème : MV3 pousse la logique des extensions vers les service workers : plus de pièces mobiles, plus de bugs de cycle de vie, plus de scrutin lors de la revue du Web Store.
Toute l'extension est un script de contenu et un popup. La seule permission au-delà de l'hôte twitch.tv est storage.
- Worker d'arrière-plan avec l'API alarms rejeté : il n'y a rien à faire quand aucun onglet Twitch n'est ouvert
- Une surface de permissions à 2 entrées signifie une revue de store plus rapide et une invite d'installation à laquelle les utilisateurs peuvent réellement se fier
- Compromis : pas de coordination inter-onglets, chaque onglet interroge indépendamment, acceptable car cliquer un coffre déjà réclamé est un no-op (vérification de désactivation)
▧ Timing d'injection sur une SPA
Problème : Le script de contenu peut s'exécuter avant ou après l'événement load de la page, et Twitch navigue entre les chaînes avec pushState, sans jamais recharger. Une version antérieure ratait chaque coffre après que l'utilisateur changeait de chaîne.
Un garde readyState démarre la boucle immédiatement si load a déjà été déclenché et l'écoute sinon ; l'intervalle survit alors à chaque navigation interne de l'application.
- Réinjection via les événements webNavigation rejetée : une permission supplémentaire pour un problème que l'intervalle persistant résout déjà
- Le garde ferme la course où un script injecté tardivement attend un événement load qui ne vient jamais
- Un seul intervalle couvre chaque chaîne que l'utilisateur consulte dans cet onglet
▤ Des stats sans backend
Problème : Les utilisateurs veulent la preuve que l'extension fonctionne, mais un serveur de télémétrie représente un coût, de la maintenance, et une responsabilité de confidentialité pour un outil gratuit.
Un seul compteur dans chrome.storage.local, incrémenté à chaque réclamation ; le popup le lit et en déduit les points de chaîne gagnés (x50).
- Analytics distants entièrement rejetés : zéro infrastructure, les données ne quittent jamais le navigateur
- Compromis : le compteur est local à l'appareil et ne se synchronise pas entre machines, très bien pour une stat de vanité
- Le read-modify-write via l'API de stockage asynchrone garde le popup et le script de contenu découplés