Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c748df7
feat(bindings): Android peer streams keep the stream the lower addres…
mizanxali Oct 7, 2026
34d3d64
feat(bindings): bound Android's inbound peer streams per host and in …
mizanxali Oct 7, 2026
468ad5d
refactor(bindings): Android peer streams know their carrier
mizanxali Oct 7, 2026
2c221f1
feat(bindings): Android peer streams find iPhones and hosts on a shar…
mizanxali Oct 7, 2026
d3fbf8d
docs: Android peer streams on a shared Wi-Fi network
mizanxali Oct 7, 2026
7689f6d
fix(bindings): Android LAN peers that restart on a new port are diale…
mizanxali Oct 7, 2026
31a3065
fix(bindings): tune Android peer-stream keepalive only where fromSock…
mizanxali Oct 7, 2026
8f4f5c7
fix(bindings): a peer-stream proof that carried nothing no longer res…
mizanxali Oct 7, 2026
dad9ba2
fix(bindings): iOS starts its peer-stream redial ladder over only on …
mizanxali Oct 7, 2026
9b7f418
fix(bindings): Android's LAN carrier holds a multicast lock where mDN…
mizanxali Oct 7, 2026
b6e5242
fix(bindings): a Wi-Fi Direct client publishes its held owner before …
mizanxali Oct 7, 2026
2f4a940
fix(bindings): an idle peer stream that held its address resets the r…
mizanxali Oct 7, 2026
5be293f
fix(bindings): Android's LAN carrier stays off without ACCESS_LOCAL_N…
mizanxali Oct 7, 2026
657c345
fix(bindings): Android's LAN carrier retries a failed advert or browse
mizanxali Oct 7, 2026
8c73bb5
fix(bindings): Android's LAN resolve queue cannot be held by one resolve
mizanxali Oct 7, 2026
c921e6a
fix(bindings): Android's LAN carrier advertises and browses on the Wi…
mizanxali Oct 7, 2026
a1a120e
fix(bindings): Android's multicast lock is held while the LAN advert …
mizanxali Oct 7, 2026
adb0f82
fix(bindings): Android's LAN carrier keeps its network and falls back…
mizanxali Oct 7, 2026
0625359
fix(bindings): Android's LAN carrier resolves a peer again after a fa…
mizanxali Oct 7, 2026
b9d295f
fix(bindings): Android's LAN permission gate keys off the app's targe…
mizanxali Oct 7, 2026
9836638
docs: the Android multicast lock covers the LAN advert as well as the…
mizanxali Oct 7, 2026
e3eda05
fix(bindings): Android's LAN carrier applies a resolve only while the…
mizanxali Oct 7, 2026
43ec4fd
refactor(bindings): the Android LAN carrier's resolve queue and netwo…
mizanxali Oct 7, 2026
6becb97
Merge remote-tracking branch 'origin/main' into pr534-merge
bahdotsh Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,39 @@ archived by series under [docs/changelog/](docs/changelog/); see the

## [Unreleased]

> **Android and iOS meet on a shared Wi-Fi network.** The Android
> peer-stream slot now finds iPhones and Python hosts on the Wi-Fi network
> it is on, and they find it, with no Wi-Fi Direct group.
> [§27](docs/UPGRADING.md#27-behaviour-that-changes-without-a-compile-error-unreleased)
> lists what changes without a compile error.

### Added

- **Android peer streams on the Wi-Fi network.** With
`wifiDirect: { enabled: true }`, the Android manager advertises and browses
DNS-SD `_offlineprotocol._tcp` through `NsdManager` on the Wi-Fi network it
is on, with the record iOS and Python publish (`txtvers=1`, `addr`, the same
instance name), and dials what it finds on iOS's policy: the lower address
at once, the higher after five seconds, a redial ladder up to a minute, and
a record whose dial is answered by another address left out. Dials are bound
to the Wi-Fi network (a socket left to the default network goes out over
cellular on a Wi-Fi with no internet) and carry the record's `addr` as the
address the preamble must prove. A Wi-Fi network with no internet counts.
The slot stays up while either the group or the network is, and Wi-Fi P2P
going off, or leaving the group, ends only the group's streams. It needs
`ACCESS_NETWORK_STATE` and `CHANGE_WIFI_MULTICAST_STATE` (a multicast lock
while advertising or browsing on Android 12 and lower, where mDNS needs one), which the
module declares, and no runtime grant until the app targets Android 17
(API 37), so it also runs on a phone that has not granted
`NEARBY_WIFI_DEVICES`. From API 37 the app must declare
`ACCESS_LOCAL_NETWORK` and request it before `start()`, or the LAN carrier
stays off with a warning diagnostic; the SDK does not declare it, because a
declaration revokes the grant apps targeting 36 and lower hold by default. A network
that blocks multicast or isolates clients (many guest and office networks)
finds nothing, and Android to iOS with no shared network still goes over
Bluetooth LE or the relays
([ADR 0028](docs/adr/0028-android-peer-streams-join-the-lan.md)).

- **The service starts every transport its configuration enables.**
`offline-protocol-service` started only the peer stream and the gateway
client, so a configuration with `ble_enabled` built both Bluetooth LE
Expand Down Expand Up @@ -135,6 +166,31 @@ archived by series under [docs/changelog/](docs/changelog/); see the

### Changed

- **Android keeps the stream the lower address opened.** Of two streams for
one address, Android kept the newer; iOS and Python keep the one the lower
address opened, and the newer of two such. On a shared network both ends
dial, and two different rules keep opposite streams and reconnect forever.
Android now computes the same rule, comparing addresses by their UTF-8
bytes, and `every_peer_stream_manager_keeps_the_same_stream` (renamed from
`ios_and_python_peer_streams_keep_the_same_stream`) pins all three copies.
Inside a Wi-Fi Direct group a client that is the higher address can no
longer supersede its own half-open stream: its reconnect waits for keepalive,
about thirty seconds on Android 10 and later.
- **Android peer streams use the iOS and Python keepalive** on Android 10 and
later: 15 seconds idle, 5 between probes, 3 probes, and 30 seconds for
unacknowledged data, where the OS defaults were two hours and about fifteen
minutes.
- **A peer-stream redial ladder starts over only after a stream that carried
its peer**, on iOS and Android: a body after the preamble, or the address
held for thirty seconds, not the proof alone. A stream the peer refused for its own stale one proved the address
too, so the dialer was announced and lost every second until that stream
died, and a Wi-Fi Direct client whose owner was held by a LAN stream
redialed it every second. Such a client now waits for that stream to end.
- **Android's peer-stream listener bounds inbound streams.** It binds every
interface, so any device on a shared network could take all sixteen slots.
At most twelve are inbound now, and four from one remote address, iOS's
bounds.

- **A peer-stream or relay flag the configuration cannot honour is refused.**
`--listen` and `--peer` without `wifi_direct_enabled` were ignored, which
left a service that reached nobody and said nothing; they, `--lan`, and
Expand Down
9 changes: 6 additions & 3 deletions bindings/react-native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,11 @@ Add to `AndroidManifest.xml`:
```

Wi-Fi Direct and the internet need nothing in your manifest: the SDK's own
manifest declares `ACCESS_WIFI_STATE`, `CHANGE_WIFI_STATE`, `INTERNET` and
`NEARBY_WIFI_DEVICES`, and the build merges them into your app. It declares
manifest declares `ACCESS_WIFI_STATE`, `CHANGE_WIFI_STATE`, `INTERNET`,
`ACCESS_NETWORK_STATE`, `CHANGE_WIFI_MULTICAST_STATE` and `NEARBY_WIFI_DEVICES`, and the build merges them
into your app. The peer-stream slot also finds iPhones and hosts on the Wi-Fi
network the phone is on, which needs no runtime grant unless your app
targets Android 17 (API 37): then an app targeting Android 17 (API 37) must declare `ACCESS_LOCAL_NETWORK` and request it before `start()`, or the LAN carrier stays off with a warning diagnostic. The SDK does not declare it, because a declaration revokes the grant apps targeting 36 and lower hold by default. The SDK's manifest declares
`NEARBY_WIFI_DEVICES` with `android:usesPermissionFlags="neverForLocation"`,
and that flag reaches your merged manifest too: the transport derives no
location from Wi-Fi, so on Android 13+ it needs `NEARBY_WIFI_DEVICES` alone.
Expand Down Expand Up @@ -519,7 +522,7 @@ interface TransportsConfig {
reconnectDelay?: number; // ms
};
wifiDirect?: {
enabled: boolean; // default: false (Android only)
enabled: boolean; // default: false. Android: Wi-Fi Direct and the Wi-Fi network; iOS: the LAN and AWDL
deviceName?: string;
autoAccept?: boolean; // Android 10+: form the group without the system settings
groupOwnerIntent?: number; // deprecated, not used
Expand Down
6 changes: 6 additions & 0 deletions bindings/react-native/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- The same slot's LAN carrier (LanPeerDiscovery): it watches for a
Wi-Fi network and binds its dials to it, and before T extensions 7
(Android 12 and lower) holds a multicast lock while advertising or browsing, which
mDNS needs there. Both install-time, no prompt. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />

<!-- BLE feature requirement (not required to allow installation on devices without BLE) -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
Expand Down
Loading
Loading