Aller au contenu principal

Télémétrie, Enregistrement d'Appareils, Code Court Support N3 et Enrôlement Canary

Chaque installation d'une application Flutter intégrant le moteur OTA génère automatiquement, dès son premier lancement, un identifiant anonyme unique UUID v4 (client_id). Ce mécanisme permet l'auto-découverte des appareils dans le Back-Office, le diagnostic par le Support Technique N3 via un code court à 8 caractères et l'enrôlement Canary QA.

1. Génération et persistance du client_id (state.json)​

Au premier démarrage natif, le moteur Rust (UpdaterState) génère un UUID v4 aléatoire (ex: "6959347f-ec8d-4b50-8562-a9562443ac3b") et le persiste dans un fichier JSON local (state.json ou patches_state.json) :

PlateformeChemins inspectés par TkOtaPush.readClientId()
Android/data/user/0/<package>/files/shorebird_updater/state.json (détecté via Directory.systemTemp.parent + /files)
iOS<Sandbox>/Library/Application Support/shorebird_updater/state.json et repli sur <Sandbox>/Documents/shorebird_updater/state.json
Personnalisé / TestTout chemin passé via le paramètre optionnel appSupportDirectoryPath

Exemple de structure de shorebird_updater/state.json :

{
"client_id": "6959347f-ec8d-4b50-8562-a9562443ac3b",
"release_version": "1.0.0+13",
"queued_events": []
}

2. Lecture de l'identité de l'appareil et du Code Court Support N3​

Plutôt que de demander à un utilisateur au téléphone de dicter les 36 caractères d'un UUID v4, TkOtaPush calcule automatiquement le Code Court Support N3 à 8 caractères :

  • Supprime les tirets - du client_id et passe la chaîne en majuscules.
  • Extrait les 8 premiers caractères hexadécimaux et les préfixe par # (ex: "6959347f-ec8d-4b50-8562-a9562443ac3b" devient "#6959347F").

Utilisation des 3 méthodes d'identification​

final ota = TkOtaPush.instance;

// 1. Lire uniquement l'UUID v4 complet
final String? clientId = await ota.readClientId();
// -> "6959347f-ec8d-4b50-8562-a9562443ac3b"

// 2. Lire uniquement le Code Court Support N3
final String? shortCode = await ota.readShortSupportCode();
// -> "#6959347F"

// 3. Lire l'objet complet OtaDeviceIdentity
final OtaDeviceIdentity? identity = await ota.readDeviceIdentity();
if (identity != null) {
print(identity.toJson());
// {
// "client_id": "6959347f-ec8d-4b50-8562-a9562443ac3b",
// "short_support_code": "#6959347F",
// "release_version": "1.0.0+13",
// "track": "stable",
// "current_patch_number": 2
// }
}

Widget prêt à l'emploi pour votre écran « Paramètres / Support »​

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tk_ota_push/tk_ota_push.dart';

class OtaSupportDiagnosticTile extends StatelessWidget {
const OtaSupportDiagnosticTile({super.key});


Widget build(BuildContext context) {
return FutureBuilder<OtaDeviceIdentity?>(
future: TkOtaPush.instance.readDeviceIdentity(),
builder: (context, snapshot) {
final identity = snapshot.data;
final shortCode = identity?.shortSupportCode ?? 'Non initialisé';
final fullUuid = identity?.clientId ?? 'En attente du moteur OTA';
final patchLabel = identity?.currentPatchNumber != null
? 'Patch #${identity!.currentPatchNumber}'
: 'Binaire Store original';

return Card(
margin: const EdgeInsets.symmetric(vertical: 8),
child: ListTile(
leading: const Icon(Icons.phonelink_setup, color: Colors.teal),
title: Text(
'Code Support : $shortCode • $patchLabel',
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
'UUID : $fullUuid',
style: const TextStyle(fontFamily: 'monospace', fontSize: 11),
),
trailing: IconButton(
icon: const Icon(Icons.copy, size: 18),
tooltip: 'Copier l\'UUID complet',
onPressed: identity == null
? null
: () {
Clipboard.setData(ClipboardData(text: identity.clientId));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Code ${identity.shortSupportCode} (${identity.clientId}) copié !',
),
),
);
},
),
),
);
},
);
}
}

3. Enregistrement automatique d'appareil et Enrôlement Canary QA​

Dès qu'un appareil effectue son premier appel vers POST /api/v1/patches/check ou POST /api/v1/patches/events, le serveur enregistre automatiquement le terminal dans la flotte d'appareils avec :

  • Son client_id et son code court #8CAR
  • Sa release_version, sa platform (android / ios) et son arch (aarch64 / arm)
  • Son pays résolu (X-Country-Code, CF-IPCountry, X-Geo-Country ou paramètre country)

Dans l'écran Device Fleet & Support (/support) de la Console Web, vous pouvez rechercher directement #6959347F pour :

  1. Assigner un label lisible au téléphone (ex: "iPhone 12 Pro — Équipe QA").
  2. Enrôler l'appareil en Whitelist Canary QA (allowed_client_ids_csv) : le téléphone recevra les patchs restreints ou en cours de test avant l'ouverture au grand public.
  3. Acquitter une alerte de Rollback (rollback_acknowledged_at).

4. Payload et cycle de vie des événements télémétriques (PatchEvent)​

Lorsqu'un événement survient, le moteur Rust l'enregistre d'abord dans queued_events au sein de state.json (garantissant qu'aucun événement n'est perdu si l'appareil est hors-ligne au moment du démarrage), puis l'envoie via POST /api/v1/patches/events.

Structure JSON envoyée à POST /api/v1/patches/events​

{
"event": {
"event_id": "c4f1d820-9a1b-4e3c-8c7a-123456789abc",
"app_id": "com.saramoney.retail",
"arch": "aarch64",
"client_id": "6959347f-ec8d-4b50-8562-a9562443ac3b",
"type": "__patch_install__",
"patch_number": 2,
"platform": "android",
"release_version": "1.0.0+13",
"timestamp": 1731150000,
"failure_category": null,
"message": null
}
}

Dédoublonnage par event_id​

Chaque événement génère un event_id (UUID v4) unique lors de sa création (PatchEvent::new dans updater/library/src/events.rs). Si la réponse HTTP 201 Created du serveur est perdue sur un réseau mobile instable et que le téléphone renvoie l'événement, le serveur dédoublonne automatiquement l'entrée grâce à event_id.