diff --git a/src/infra/bonjour.test.ts b/src/infra/bonjour.test.ts index 8ae6ae0d9..178fda57a 100644 --- a/src/infra/bonjour.test.ts +++ b/src/infra/bonjour.test.ts @@ -18,6 +18,11 @@ vi.mock("@homebridge/ciao", () => { const { startGatewayBonjourAdvertiser } = await import("./bonjour.js"); describe("gateway bonjour advertiser", () => { + type ServiceCall = { + name?: unknown; + txt?: unknown; + }; + const prevEnv = { ...process.env }; afterEach(() => { @@ -84,4 +89,30 @@ describe("gateway bonjour advertiser", () => { expect(destroy).toHaveBeenCalledTimes(2); expect(shutdown).toHaveBeenCalledTimes(1); }); + + it("normalizes hostnames with domains for service names", async () => { + // Allow advertiser to run in unit tests. + delete process.env.VITEST; + process.env.NODE_ENV = "development"; + + vi.spyOn(os, "hostname").mockReturnValue("Mac.localdomain"); + + const destroy = vi.fn().mockResolvedValue(undefined); + const advertise = vi.fn().mockResolvedValue(undefined); + createService.mockReturnValue({ advertise, destroy }); + + const started = await startGatewayBonjourAdvertiser({ + gatewayPort: 18789, + sshPort: 2222, + bridgePort: 18790, + }); + + const [masterCall] = createService.mock.calls as Array<[ServiceCall]>; + expect(masterCall?.[0]?.name).toBe("Mac (Clawdis)"); + expect((masterCall?.[0]?.txt as Record)?.lanHost).toBe( + "Mac.local", + ); + + await started.stop(); + }); }); diff --git a/src/infra/bonjour.ts b/src/infra/bonjour.ts index 332aabea0..f8141ec62 100644 --- a/src/infra/bonjour.ts +++ b/src/infra/bonjour.ts @@ -39,7 +39,15 @@ export async function startGatewayBonjourAdvertiser( const { getResponder, Protocol } = await import("@homebridge/ciao"); const responder = getResponder(); - const hostname = os.hostname().replace(/\.local$/i, ""); + // mDNS service instance names are single DNS labels; dots in hostnames (like + // `Mac.localdomain`) can confuse some resolvers/browsers and break discovery. + // Keep only the first label and normalize away a trailing `.local`. + const hostname = + os + .hostname() + .replace(/\.local$/i, "") + .split(".")[0] + .trim() || "clawdis"; const instanceName = typeof opts.instanceName === "string" && opts.instanceName.trim() ? opts.instanceName.trim()