Air Link Discovery

Zero-config LAN device discovery using UDP broadcast.

// Advertise this device
await DiscoveryService.advertise(name: 'My Phone', port: 8080);

// Discover other devices
DiscoveryService.discover().listen((peer) {
  print('Found: ${peer.name} @ ${peer.ip}:${peer.port}');
});

// Cleanup
await DiscoveryService.stopAll();

No configuration, no IP scanning, no manual setup. Devices on the same network find each other automatically.


Quick Start

1. Add the dependency

dependencies:
  air_link_discovery: ^0.2.0

2. Advertise your device

import 'package:air_link_discovery/air_link_discovery.dart';

await DiscoveryService.advertise(port: 8080, name: 'My Device');

The device broadcasts its name and port every 2 seconds over UDP. Other devices running discover() will find it.

3. Discover peers

DiscoveryService.discover().listen((peer) {
  print('${peer.name} at ${peer.ip}:${peer.port}');

  // Connect using Air Link Core
  final session = await Lan.connect(peer.ip, port: peer.port);
});

4. Stop when done

await DiscoveryService.stopAll();

Instance API

For running multiple discovery services independently (e.g., different apps on the same device):

final svc = DiscoveryService();

// Advertise
await svc.startAdvertise(name: 'Node A', port: 8081);

// Discover
svc.startDiscovery().listen((peer) {
  print('Found via instance: ${peer.name}');
});

// Cleanup
await svc.shutdown();

Self-filtering

Each DiscoveryService instance generates a unique ID. By default, discovery() filters out advertisements from the same instance (filterSelf: true), so you never discover yourself.


API

Static methods (backward compatible)

Method Description
advertise({name, port, metadata, identityFingerprint, discoveryPort, interval}) Start advertising
discover({discoveryPort, filterSelf}) Start discovery, returns Stream<DiscoveredPeer>
stopAll() Stop all discovery activity
stopAdvertising() Stop advertising only
stopDiscovering() Stop discovering only

Instance methods

Method Description
startAdvertise(...) Same as static advertise()
startDiscovery(...) Same as static discover()
stopAdvertise() Stop advertising
stopDiscovery() Stop discovering
shutdown() Stop all activity

Configuration

Parameter Default Description
name Platform.localHostname Human-readable device name
port (required) The TCP port your server runs on
metadata {} Additional key-value pairs to advertise
identityFingerprint defaultFingerprint SHA-256 fingerprint of ECDSA identity key
discoveryPort 41234 UDP port for broadcast
interval 2 seconds How often to broadcast
filterSelf true Whether to filter own advertisements

Global defaults

// Set once at app startup
DiscoveryService.defaultFingerprint = identity.fingerprint;

// All future adverts include it automatically
await DiscoveryService.advertise(port: 8080);

DiscoveredPeer

Property Description
String name Device name
String ip IP address
int port TCP port
Map<String, String> metadata Advertised metadata
String? identityFingerprint ECDSA identity fingerprint (if provided)

Security

Discovery sends plaintext UDP broadcasts. It is designed for discovery only — not for transmitting sensitive data.

  • Use identityFingerprint to verify the discovered peer matches the expected identity key when connecting via air_link_secure
  • The broadcast includes an instance ID for self-filtering, but the ID is not authenticated — any device on the network can send advertisements

Platform Support

Platform Status
Android ✅ (requires INTERNET permission)
iOS ✅ (requires NSLocalNetworkUsageDescription)
macOS ✅
Windows ✅
Linux ✅

Resource Limits

  • The dedup set (prevents duplicate peer emissions) is capped at 1000 entries
  • Old entries are automatically evicted on overflow

Module Integration

// Discovery + Secure: find peers with verified identity
final fp = identity.fingerprint;
await DiscoveryService.advertise(port: 8080, identityFingerprint: fp);

DiscoveryService.discover().listen((peer) {
  if (peer.identityFingerprint != null) {
    // Connect securely — the fingerprint matches the expected key
    connectSecure(peer.ip, identity, port: peer.port);
  }
});
// Discovery + Core: simple connect
DiscoveryService.discover().listen((peer) async {
  final session = await Lan.connect(peer.ip, port: peer.port);
  await session.peer.sendText('Hi! Found you via discovery.');
});

Requirements

  • Dart SDK >=3.0.0 <4.0.0
  • No external dependencies

License

MIT

Libraries