Follow-up from the dual-interface refactor (ea2e57e on feat/enable-tunnel-flip-flag).
Current behaviour
AppServices.registerTunnelInterface() picks the host/port to mirror via:
guard let endpoint = tcpEndpoints.values.first else {
DiagLog.log("[TUNNEL] registerTunnelInterface — no foreground TCP endpoint to mirror, skipping")
return
}
If the user has multiple enabled tcpClient InterfaceEntity rows (Settings → Interface Management → multiple TCP relays), only the first (per Dictionary.values.first — unspecified iteration order) ends up with a tunnel-path mirror. The others stay foreground-only and lose their rnsd path the moment the host app suspends. Phase B notifications would only fire for messages routed through the one mirrored relay.
What it should do
For each enabled foreground TCP interface, register a paired TunnelTCPInterface with a unique id (e.g., "tcp-tunnel-\(foregroundEntityId)"), publish each endpoint into tunnelTCPEndpointsKey, and have ExtensionFrameReader's routing dispatch by id to the right tunnel interface.
PacketTunnelProvider.loadInterfaceConfigs already iterates tunnelTCPEndpointsKey and opens a connection per entry, so the extension side scales naturally; the app side just needs to mirror per-entity instead of picking the first.
Suggested implementation sketch
registerTunnelInterface() loops over tcpEndpoints, creating one TunnelTCPInterface per foreground entity.
- Hold them in a
[String: TunnelTCPInterface] keyed by foreground entity id (or by tunnel entity id).
deregisterTunnelInterface() tears all of them down.
connectTCPInterface / stopTCPInterface invalidate / refresh the matching tunnel mirror.
- The
entityId used by TunnelManager.sendFrame ↔ ExtensionFrameReader routing needs to be derived from the per-pair id, not the hard-coded TUNNEL_TCP_INTERFACE_ID constant.
Why deferred
For Phase B's MVP, a single tunnel mirror was enough to validate the architecture end-to-end (suspended_notification scenario went green with one relay). Tyler ships with one TCP relay in his test deployment, so this never fires in dev. Worth fixing before users with multi-relay setups exercise Background Transport.
Follow-up from the dual-interface refactor (
ea2e57eonfeat/enable-tunnel-flip-flag).Current behaviour
AppServices.registerTunnelInterface()picks the host/port to mirror via:If the user has multiple enabled
tcpClientInterfaceEntityrows (Settings → Interface Management → multiple TCP relays), only the first (perDictionary.values.first— unspecified iteration order) ends up with a tunnel-path mirror. The others stay foreground-only and lose their rnsd path the moment the host app suspends. Phase B notifications would only fire for messages routed through the one mirrored relay.What it should do
For each enabled foreground TCP interface, register a paired
TunnelTCPInterfacewith a unique id (e.g.,"tcp-tunnel-\(foregroundEntityId)"), publish each endpoint intotunnelTCPEndpointsKey, and haveExtensionFrameReader's routing dispatch by id to the right tunnel interface.PacketTunnelProvider.loadInterfaceConfigsalready iteratestunnelTCPEndpointsKeyand opens a connection per entry, so the extension side scales naturally; the app side just needs to mirror per-entity instead of picking the first.Suggested implementation sketch
registerTunnelInterface()loops overtcpEndpoints, creating oneTunnelTCPInterfaceper foreground entity.[String: TunnelTCPInterface]keyed by foreground entity id (or by tunnel entity id).deregisterTunnelInterface()tears all of them down.connectTCPInterface/stopTCPInterfaceinvalidate / refresh the matching tunnel mirror.entityIdused byTunnelManager.sendFrame↔ExtensionFrameReaderrouting needs to be derived from the per-pair id, not the hard-codedTUNNEL_TCP_INTERFACE_IDconstant.Why deferred
For Phase B's MVP, a single tunnel mirror was enough to validate the architecture end-to-end (
suspended_notificationscenario went green with one relay). Tyler ships with one TCP relay in his test deployment, so this never fires in dev. Worth fixing before users with multi-relay setups exercise Background Transport.