Matrice complète des combinaisons d'usage
Le SDK tk_ota_push et le fichier shorebird.yaml permettent de combiner librement la politique de déclenchement (auto-update natif, Gatekeeper silencieux, Gatekeeper critique forcé, déclenchement manuel), la politique de redémarrage, le filtrage Wi-Fi, le canal (stable / staging) et le bouclier de sécurité (Certificate Pinning / Anti-Repackaging).
Tableau comparatif des 8 patterns d'intégration
| # | Pattern d'usage | shorebird.yaml (auto_update) | enableGatekeeper | forceUpdate | requireWifi | Canal (track) | Pinning & Shield | Application du patch |
|---|---|---|---|---|---|---|---|---|
| 1 | Silencieux Éco-Data avec Gatekeeper (Standard Production) | false | true | false | false | stable | Actif | Prochain Cold Start |
| 2 | Silencieux Wi-Fi Only & Batterie >= 30 % | false | true | false | true | stable | Actif | Différé (workmanager) puis Cold Start |
| 3 | Mise à jour Critique Forcée (Modale bloquante) | false | true | true | Ignoré | stable | Actif | Téléchargement immédiat + invitation à redémarrer |
| 4 | Auto-Update Natif Rust autonome (Zéro code Dart) | true | (N/A) | (N/A) | Non | stable | Signature RSA (strict) | Prochain Cold Start |
| 5 | Contrôle 100 % Manuel (Bouton "Vérifier les mises à jour") | false | false | (N/A) | Non | stable | Optionnel | À la demande |
| 6 | Bascule dynamique Canal staging (Menu QA interne) | false | true / false | false | false | staging | Optionnel | Immédiat après redémarrage |
| 7 | Haute Sécurité : Certificate Pinning TLS + Anti-Repackaging + Anti-Root | false | true | false / true | false | stable | Strict (HttpOverrides + SHA-256 APK) | Bloqué si appareil ou réseau compromis |
| 8 | Sans Gatekeeper (enableGatekeeper: false) avec politiques SDK | false | false | false / true | true / false | stable | Actif | Interroge toujours le serveur |
Extraits de code pour chaque combinaison
Combinaison 1 : Silencieux Éco-Data avec Gatekeeper (Recommandé en production)
Dans shorebird.yaml : auto_update: false. Le serveur OTA n'est interrogé que si les 3 conditions du RemoteConfigGatekeeper sont réunies. Le patch est téléchargé en tâche de fond et appliqué au prochain lancement de l'application sans interrompre l'utilisateur.
final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
enableGatekeeper: true,
requireWifiForSilentUpdate: false,
minBatteryPercentageForSilentUpdate: 20,
);
final outcome = await ota.syncWithRemoteConfig(
const OtaRemoteConfig(
targetReleaseVersion: '1.0.0+3',
patchAvailable: true,
latestPatchNumber: 2,
forceUpdate: false,
),
);
// outcome.executionMode == OtaUpdateExecutionMode.silentBackgroundUpdate
// outcome.restartRequired == false (appliqué au prochain Cold Start)
Combinaison 2 : Silencieux Wi-Fi uniquement + Planificateur d'arrière-plan
Idéal lorsque vos utilisateurs disposent de forfaits mobiles limités et que vous souhaitez différer tout téléchargement jusqu'à ce que le téléphone soit connecté en Wi-Fi avec au moins 30 % de batterie.
final scheduler = InMemoryOtaBackgroundScheduler(); // Ou votre implémentation workmanager
final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
enableGatekeeper: true,
requireWifiForSilentUpdate: true,
minBatteryPercentageForSilentUpdate: 30,
environmentMonitor: const DefaultOtaEnvironmentMonitor(
defaultConnectivity: OtaConnectivityType.mobile,
defaultBatteryPercentage: 80,
),
backgroundScheduler: scheduler,
);
final outcome = await ota.syncWithRemoteConfig(
const OtaRemoteConfig(
targetReleaseVersion: '1.0.0+3',
patchAvailable: true,
latestPatchNumber: 2,
forceUpdate: false,
),
);
// outcome.executionMode == OtaUpdateExecutionMode.deferredToBackgroundTask
// outcome.networkCallPerformed == false
Combinaison 3 : Mise à jour Critique Forcée avec Modale Bloquante et Redémarrage
Lorsque forceUpdate: true est envoyé dans le Remote Config, le SDK ignore la restriction Wi-Fi, invoque onCriticalUpdateRequired et positionne outcome.restartRequired = true dès que le patch est téléchargé.
final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
enableGatekeeper: true,
requireWifiForSilentUpdate: true, // Automatiquement ignoré quand forceUpdate == true
onCriticalUpdateRequired: (config, triggerImmediateDownload) async {
// Afficher une modale non fermable (barrierDismissible: false)
final success = await triggerImmediateDownload();
if (success) {
// Inviter l'utilisateur à relancer immédiatement l'application
}
},
);
final outcome = await ota.syncWithRemoteConfig(
const OtaRemoteConfig(
targetReleaseVersion: '1.0.0+3',
patchAvailable: true,
latestPatchNumber: 3,
forceUpdate: true,
releaseNotes: 'Correctif critique obligatoire.',
),
);
// outcome.executionMode == OtaUpdateExecutionMode.criticalForcedUpdate
// outcome.restartRequired == true
Combinaison 4 : Auto-Update Natif Rust autonome (auto_update: true, sans Gatekeeper)
Si vous ne disposez pas de système Remote Config et souhaitez que le moteur C++/Rust vérifie automatiquement la présence d'un patch à chaque ouverture de l'application :
# shorebird.yaml
app_id: "com.monapp.mobile"
base_url: "https://ota.monapp.internal"
channel: "stable"
auto_update: true
patch_verification: strict
patch_public_key: MIIBCgKCAQEA...
Dans main.dart, vous pouvez tout de même utiliser TkOtaPush pour la détection de rollback et la lecture du Code Support N3 :
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+1',
onRollbackDetected: (event) async {
// Nettoyage préventif du cache local après un rollback natif
},
);
await ota.verifyStartupAndHandleRollback();
runApp(const MyApp());
}
Combinaison 5 : Contrôle 100 % Manuel sans Remote Config
Pour déclencher la vérification uniquement lorsque l'utilisateur appuie sur un bouton « Vérifier les mises à jour » dans les paramètres de l'application (auto_update: false dans shorebird.yaml) :
Future<void> onManualUpdateButtonPressed(BuildContext context) async {
final ota = TkOtaPush.instance;
if (!ota.isAvailable) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Moteur OTA non disponible en mode debug.')),
);
return;
}
final status = await ota.checkForUpdate();
if (status == UpdateStatus.outdated) {
await ota.update();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Mise à jour téléchargée. Redémarrez l\'application.')),
);
}
} else if (status == UpdateStatus.restartRequired) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Un patch est déjà prêt. Redémarrez l\'application.')),
);
}
}
Combinaison 6 : Bascule Canal staging vs stable (Mode QA)
Permet aux testeurs internes d'installer sur leur téléphone le binaire de production (stable) puis de basculer dynamiquement sur la piste staging pour valider un patch avant sa promotion :
final ota = TkOtaPush.instance;
// Activer la piste staging
ota.enableQaMode();
assert(ota.track == UpdateTrack.staging);
// Télécharger le patch publié avec --channel staging
final downloaded = await ota.checkAndUpdate();
// Revenir sur la piste publique stable
ota.disableQaMode();
assert(ota.track == UpdateTrack.stable);
Combinaison 7 : Avec Certificate Pinning TLS, Anti-Repackaging APK et Anti-Root
Combine le pinning TLS au niveau de la pile HTTP Dart (HttpOverrides.global) et la politique OtaSecurityPolicy dans TkOtaPush.init :
import 'dart:io';
import 'package:tk_ota_push/tk_ota_push.dart';
void initHighSecurityOta() {
const pinnedCertHash = '42d889f8f4b6bf9d3abb767d3b5f215e0009e4d26cb7b958497e8b291e1b96dc';
// 1. Activer le Certificate Pinning TLS sur HttpClient
HttpOverrides.global = OtaPinnedHttpOverrides(
OtaCertificatePinner(
pinnedSha256Fingerprints: {pinnedCertHash},
enforcedHosts: {'ota.saramoney.internal'},
),
);
// 2. Initialiser TkOtaPush avec vérification APK et détection Root/Jailbreak
TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
securityPolicy: const OtaSecurityPolicy(
blockRootedOrJailbrokenDevices: true,
expectedApkSignaturesSha256: {
'6b6929c8ff345cf008496328d7bff081a8ccfb89498bf9916ed668be3c3dedcf',
},
pinnedServerCertSha256Fingerprints: {pinnedCertHash},
),
);
}
Combinaison 8 : Sans Gatekeeper (enableGatekeeper: false) mais avec Bouclier et Planificateur
Si vous souhaitez conserver le Bouclier de Sécurité Mobile, la détection de Rollback et le contrôle Wi-Fi/Batterie, tout en court-circuitant les 3 conditions du RemoteConfigGatekeeper (GatekeeperVerdict.bypassedGatekeeperDisabled) :
final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
enableGatekeeper: false, // Court-circuite les 3 conditions de version/numéro
requireWifiForSilentUpdate: true,
);
final outcome = await ota.syncWithRemoteConfig(
const OtaRemoteConfig(
targetReleaseVersion: '',
patchAvailable: false,
latestPatchNumber: 0,
forceUpdate: false,
),
);
// outcome.gatekeeperEvaluation?.verdict == GatekeeperVerdict.bypassedGatekeeperDisabled