discoverFirebaseEmulatorHost static method

Future<String?> discoverFirebaseEmulatorHost({
  1. int port = 5001,
})

Discovers the host machine's IP address for Firebase emulator

Implementation

static Future<String?> discoverFirebaseEmulatorHost({int port = 5001}) async {
  if (kIsWeb) {
    return '127.0.0.1'; // Web always uses localhost
  }

  logd('Starting Firebase emulator host discovery on port $port...');

  // Try cached address first (if available)
  final cachedAddress = await _getCachedEmulatorAddress();
  if (cachedAddress != null) {
    logd('Testing cached emulator address: $cachedAddress');
    if (await _testConnection(cachedAddress, port)) {
      logd('Cached Firebase emulator address still valid: $cachedAddress');
      return cachedAddress;
    } else {
      logd('Cached address no longer valid, clearing cache');
      await _clearCachedEmulatorAddress();
    }
  }

  // Test localhost first (might work for some setups)
  if (await _testConnection('127.0.0.1', port)) {
    logd('Firebase emulator found at localhost');
    await _saveCachedEmulatorAddress('127.0.0.1');
    return '127.0.0.1';
  }

  // Get device IP addresses to determine which subnets to scan
  final deviceIps = await getDeviceIpAddresses();
  final subnetsToScan = <String>{};

  // Extract subnets from device IPs
  for (final deviceIp in deviceIps) {
    final subnet = _getSubnetFromIp(deviceIp);
    if (subnet.isNotEmpty) {
      subnetsToScan.add(subnet);
      logd('Will scan subnet $subnet (from device IP: $deviceIp)');
    }
  }

  // If no device IPs found, fall back to common ranges
  if (subnetsToScan.isEmpty) {
    logd('No device IPs found, using common network ranges');
    subnetsToScan.addAll([
      '192.168.1.', // Common home networks
      '172.20.10.', // iOS hotspot range
      '192.168.0.', // Common router default
      '10.0.0.', // Some corporate networks
      '10.0.1.', // Alternative corporate setup
      '172.16.0.', // Docker/VPN networks
      '192.168.43.', // Android hotspot range
    ]);
  }

  // Test each subnet with priority IPs first
  for (final range in subnetsToScan) {
    // Test common host IPs first (router gateway, common static IPs)
    final priorityIPs = [1, 100, 101, 102, 2, 10, 20, 50];

    // Test priority IPs in parallel
    final priorityFutures = priorityIPs.map((ip) async {
      final address = '$range$ip';
      if (await _testConnection(address, port)) {
        return address;
      }
      return null;
    }).toList();

    final priorityResults = await Future.wait(priorityFutures);
    for (final result in priorityResults) {
      if (result != null) {
        logd('Firebase emulator found at: $result');
        await _saveCachedEmulatorAddress(result);
        return result;
      }
    }

    // If priority IPs don't work, scan the full range in parallel batches
    const batchSize = 20; // Test 20 IPs at a time
    for (int batchStart = 2; batchStart <= 254; batchStart += batchSize) {
      final batchEnd = (batchStart + batchSize - 1).clamp(2, 254);

      final batchFutures = <Future<String?>>[];
      for (int i = batchStart; i <= batchEnd; i++) {
        if (priorityIPs.contains(i)) continue; // Skip already tested IPs

        final address = '$range$i';
        batchFutures.add(_testConnection(address, port).then((success) {
          return success ? address : null;
        }));
      }

      final batchResults = await Future.wait(batchFutures);
      for (final result in batchResults) {
        if (result != null) {
          logd('Firebase emulator found at: $result');
          await _saveCachedEmulatorAddress(result);
          return result;
        }
      }
    }
  }

  logw('Firebase emulator host not found');
  return null;
}