Aller au contenu principal

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'usageshorebird.yaml (auto_update)enableGatekeeperforceUpdaterequireWifiCanal (track)Pinning & ShieldApplication du patch
1Silencieux Éco-Data avec Gatekeeper (Standard Production)falsetruefalsefalsestableActifProchain Cold Start
2Silencieux Wi-Fi Only & Batterie >= 30 %falsetruefalsetruestableActifDifféré (workmanager) puis Cold Start
3Mise à jour Critique Forcée (Modale bloquante)falsetruetrueIgnoréstableActifTéléchargement immédiat + invitation à redémarrer
4Auto-Update Natif Rust autonome (Zéro code Dart)true(N/A)(N/A)NonstableSignature RSA (strict)Prochain Cold Start
5Contrôle 100 % Manuel (Bouton "Vérifier les mises à jour")falsefalse(N/A)NonstableOptionnelÀ la demande
6Bascule dynamique Canal staging (Menu QA interne)falsetrue / falsefalsefalsestagingOptionnelImmédiat après redémarrage
7Haute Sécurité : Certificate Pinning TLS + Anti-Repackaging + Anti-Rootfalsetruefalse / truefalsestableStrict (HttpOverrides + SHA-256 APK)Bloqué si appareil ou réseau compromis
8Sans Gatekeeper (enableGatekeeper: false) avec politiques SDKfalsefalsefalse / truetrue / falsestableActifInterroge 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