fix(gateway): advertise bonjour hostname

This commit is contained in:
Peter Steinberger
2025-12-13 12:28:16 +00:00
parent 36f21c5a4f
commit 2b71ea21ad
3 changed files with 21 additions and 0 deletions

View File

@@ -49,6 +49,15 @@ If browsing shows instances but resolving fails, youre usually hitting a LAN
- **Bonjour doesnt cross networks**: London/Vienna style setups require Tailnet (MagicDNS/IP) or SSH.
- **Multicast blocked**: some WiFi 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

View File

@@ -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<string, string>)?.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<string, string>)?.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<string, string>)?.lanHost).toBe(
"Mac.local",
);

View File

@@ -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",