checkForUpdate static method

Future<UpdateCheckResult> checkForUpdate({
  1. String? channel,
  2. String? branch,
})

Checks for compatible OTA updates on the active channel and branch.

Implementation

static Future<UpdateCheckResult> checkForUpdate({
  String? channel,
  String? branch,
}) async {
  _isChecking.value = true;
  _error.value = null;

  final targetChannel = channel ?? _activeChannel;
  final targetBranch = branch ?? _activeBranch;

  try {
    final manifest = await _adapter.checkServerForUpdate(
      channel: targetChannel,
      branch: targetBranch,
      runtimeFingerprint: _localRuntimeFingerprint,
      deviceId: _deviceId,
    );

    if (manifest == null) {
      _isAvailable.value = false;
      _isChecking.value = false;
      return const UpdateCheckResult.upToDate();
    }

    // 1. Verify Cryptographic Runtime Fingerprint Compatibility
    if (manifest.runtimeFingerprint.isNotEmpty &&
        manifest.runtimeFingerprint.toLowerCase() != _localRuntimeFingerprint.toLowerCase()) {
      final remoteShort = manifest.runtimeFingerprint.length >= 8 ? manifest.runtimeFingerprint.substring(0, 8) : manifest.runtimeFingerprint;
      final localShort = _localRuntimeFingerprint.length >= 8 ? _localRuntimeFingerprint.substring(0, 8) : _localRuntimeFingerprint;
      final reason = 'Incompatible native runtime fingerprint: remote requires "$remoteShort...", local binary is "$localShort..."';
      logger.warn('BloomUpdates: OTA update "${manifest.id}" rejected! $reason');
      _isAvailable.value = false;
      _isChecking.value = false;
      return UpdateCheckResult.rejected(reason: reason, manifest: manifest);
    }

    // 2. Evaluate Staged Percentage Rollout
    if (manifest.rolloutPercentage < 100) {
      final eligible = StagedRolloutEvaluator.isEligible(
        deviceId: _deviceId,
        updateId: manifest.id,
        rolloutPercentage: manifest.rolloutPercentage,
      );

      if (!eligible) {
        final bucket = StagedRolloutEvaluator.getDeviceBucket(_deviceId, manifest.id);
        final reason = 'Device bucket ($bucket) excluded by staged rollout window (${manifest.rolloutPercentage}%)';
        logger.debug('BloomUpdates: Device not in rollout partition: $reason');
        _isAvailable.value = false;
        _isChecking.value = false;
        return UpdateCheckResult.rejected(reason: reason, manifest: manifest);
      }
    }

    _pendingStagedManifest = manifest;
    _isAvailable.value = true;
    _isChecking.value = false;
    return UpdateCheckResult.available(manifest);
  } catch (e, st) {
    logger.error('BloomUpdates: Check for update failed: $e', e, st);
    _error.value = e;
    _isAvailable.value = false;
    _isChecking.value = false;
    return UpdateCheckResult.rejected(reason: e.toString());
  }
}