diff --git a/docs/bonjour.md b/docs/bonjour.md index 7e76fd4ec..4d8e5baa2 100644 --- a/docs/bonjour.md +++ b/docs/bonjour.md @@ -49,6 +49,15 @@ If browsing shows instances but resolving fails, you’re usually hitting a LAN - **Bonjour doesn’t cross networks**: London/Vienna style setups require Tailnet (MagicDNS/IP) or SSH. - **Multicast blocked**: some Wi‑Fi networks (enterprise/hotels) disable mDNS; expect “no results”. - **Sleep / interface churn**: macOS may temporarily drop mDNS results when switching networks; retry. +- **Browse works but resolve fails (iOS “NoSuchRecord”)**: make sure the advertiser publishes a valid SRV target hostname. + - Implementation detail: `@homebridge/ciao` defaults `hostname` to the *service instance name* when `hostname` is omitted. If your instance name contains spaces/parentheses, some resolvers can fail to resolve the implied A/AAAA record. + - Fix: set an explicit DNS-safe `hostname` (single label; no `.local`) in `src/infra/bonjour.ts`. + +## Escaped instance names (`\\032`) +Bonjour/DNS-SD often escapes bytes in service instance names as decimal `\\DDD` sequences (e.g. spaces become `\\032`). + +- This is normal at the protocol level. +- UIs should decode for display (iOS uses `BonjourEscapes.decode` in `apps/shared/ClawdisKit`). ## Disabling / configuration diff --git a/src/infra/bonjour.test.ts b/src/infra/bonjour.test.ts index 178fda57a..b1f9ae038 100644 --- a/src/infra/bonjour.test.ts +++ b/src/infra/bonjour.test.ts @@ -20,6 +20,8 @@ const { startGatewayBonjourAdvertiser } = await import("./bonjour.js"); describe("gateway bonjour advertiser", () => { type ServiceCall = { name?: unknown; + hostname?: unknown; + domain?: unknown; txt?: unknown; }; @@ -66,6 +68,8 @@ describe("gateway bonjour advertiser", () => { >; expect(masterCall?.[0]?.type).toBe("clawdis-master"); expect(masterCall?.[0]?.port).toBe(2222); + expect(masterCall?.[0]?.domain).toBe("local"); + expect(masterCall?.[0]?.hostname).toBe("test-host"); expect((masterCall?.[0]?.txt as Record)?.lanHost).toBe( "test-host.local", ); @@ -75,6 +79,8 @@ describe("gateway bonjour advertiser", () => { expect(bridgeCall?.[0]?.type).toBe("clawdis-bridge"); expect(bridgeCall?.[0]?.port).toBe(18790); + expect(bridgeCall?.[0]?.domain).toBe("local"); + expect(bridgeCall?.[0]?.hostname).toBe("test-host"); expect((bridgeCall?.[0]?.txt as Record)?.bridgePort).toBe( "18790", ); @@ -109,6 +115,8 @@ describe("gateway bonjour advertiser", () => { const [masterCall] = createService.mock.calls as Array<[ServiceCall]>; expect(masterCall?.[0]?.name).toBe("Mac (Clawdis)"); + expect(masterCall?.[0]?.domain).toBe("local"); + expect(masterCall?.[0]?.hostname).toBe("Mac"); expect((masterCall?.[0]?.txt as Record)?.lanHost).toBe( "Mac.local", ); diff --git a/src/infra/bonjour.ts b/src/infra/bonjour.ts index f8141ec62..a35be1e92 100644 --- a/src/infra/bonjour.ts +++ b/src/infra/bonjour.ts @@ -74,6 +74,8 @@ export async function startGatewayBonjourAdvertiser( type: "clawdis-master", protocol: Protocol.TCP, port: opts.sshPort ?? 22, + domain: "local", + hostname, txt: { ...txtBase, sshPort: String(opts.sshPort ?? 22), @@ -88,6 +90,8 @@ export async function startGatewayBonjourAdvertiser( type: "clawdis-bridge", protocol: Protocol.TCP, port: opts.bridgePort, + domain: "local", + hostname, txt: { ...txtBase, transport: "bridge",