refactor: unify gateway discovery on bridge

This commit is contained in:
Peter Steinberger
2025-12-19 23:12:52 +01:00
parent bcced90f11
commit bc2a66da32
13 changed files with 489 additions and 286 deletions

View File

@@ -99,37 +99,32 @@ describe("gateway bonjour advertiser", () => {
tailnetDns: "host.tailnet.ts.net",
});
expect(createService).toHaveBeenCalledTimes(2);
const [masterCall, bridgeCall] = createService.mock.calls as Array<
expect(createService).toHaveBeenCalledTimes(1);
const [bridgeCall] = createService.mock.calls as Array<
[Record<string, unknown>]
>;
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",
);
expect((masterCall?.[0]?.txt as Record<string, string>)?.sshPort).toBe(
"2222",
);
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>)?.lanHost).toBe(
"test-host.local",
);
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.bridgePort).toBe(
"18790",
);
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.sshPort).toBe(
"2222",
);
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.transport).toBe(
"bridge",
);
// We don't await `advertise()`, but it should still be called for each service.
expect(advertise).toHaveBeenCalledTimes(2);
expect(advertise).toHaveBeenCalledTimes(1);
await started.stop();
expect(destroy).toHaveBeenCalledTimes(2);
expect(destroy).toHaveBeenCalledTimes(1);
expect(shutdown).toHaveBeenCalledTimes(1);
});
@@ -166,12 +161,10 @@ describe("gateway bonjour advertiser", () => {
bridgePort: 18790,
});
// 2 services × 2 listeners each
// 1 service × 2 listeners
expect(onCalls.map((c) => c.event)).toEqual([
"name-change",
"hostname-change",
"name-change",
"hostname-change",
]);
await started.stop();
@@ -207,7 +200,7 @@ describe("gateway bonjour advertiser", () => {
const started = await startGatewayBonjourAdvertiser({
gatewayPort: 18789,
sshPort: 2222,
bridgePort: 0,
bridgePort: 18790,
});
// initial advertise attempt happens immediately
@@ -257,7 +250,7 @@ describe("gateway bonjour advertiser", () => {
const started = await startGatewayBonjourAdvertiser({
gatewayPort: 18789,
sshPort: 2222,
bridgePort: 0,
bridgePort: 18790,
});
expect(advertise).toHaveBeenCalledTimes(1);
@@ -296,11 +289,11 @@ describe("gateway bonjour advertiser", () => {
bridgePort: 18790,
});
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(
const [bridgeCall] = createService.mock.calls as Array<[ServiceCall]>;
expect(bridgeCall?.[0]?.name).toBe("Mac (Clawdis)");
expect(bridgeCall?.[0]?.domain).toBe("local");
expect(bridgeCall?.[0]?.hostname).toBe("Mac");
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.lanHost).toBe(
"Mac.local",
);

View File

@@ -101,7 +101,7 @@ export async function startGatewayBonjourAdvertiser(
const displayName = prettifyInstanceName(instanceName);
const txtBase: Record<string, string> = {
role: "master",
role: "gateway",
gatewayPort: String(opts.gatewayPort),
lanHost: `${hostname}.local`,
displayName,
@@ -118,26 +118,7 @@ export async function startGatewayBonjourAdvertiser(
const services: Array<{ label: string; svc: BonjourService }> = [];
// Master beacon: used for discovery (auto-fill SSH/direct targets).
// We advertise a TCP service so clients can resolve the host; the port itself is informational.
const master = responder.createService({
name: safeServiceName(instanceName),
type: "clawdis-master",
protocol: Protocol.TCP,
port: opts.sshPort ?? 22,
domain: "local",
hostname,
txt: {
...txtBase,
sshPort: String(opts.sshPort ?? 22),
},
});
services.push({
label: "master",
svc: master as unknown as BonjourService,
});
// Optional bridge beacon (same type used by iOS/Android nodes today).
// Bridge beacon (used by macOS/iOS/Android nodes and the mac app onboarding flow).
if (typeof opts.bridgePort === "number" && opts.bridgePort > 0) {
const bridge = responder.createService({
name: safeServiceName(instanceName),
@@ -148,6 +129,7 @@ export async function startGatewayBonjourAdvertiser(
hostname,
txt: {
...txtBase,
sshPort: String(opts.sshPort ?? 22),
transport: "bridge",
},
});