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) :
| Plateforme | Chemins 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é / Test | Tout 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
-duclient_idet 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_idet son code court#8CAR - Sa
release_version, saplatform(android/ios) et sonarch(aarch64/arm) - Son pays résolu (
X-Country-Code,CF-IPCountry,X-Geo-Countryou paramètrecountry)
Dans l'écran Device Fleet & Support (/support) de la Console Web, vous pouvez rechercher directement #6959347F pour :
- Assigner un label lisible au téléphone (ex:
"iPhone 12 Pro — Équipe QA"). - 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. - 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.