Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions apps/host/src/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,34 @@ function listed(seen: Set<string>): Listing[] {
);
}

function listings(client: Client): Listing[] {
if (client.peer) return local();
const reachable = [...local(), ...peers.listings()];
function listings(client: Client, own = local()): Listing[] {
if (client.peer) return own;
const reachable = [...own, ...peers.listings()];
return [...reachable, ...listed(new Set(reachable.map((value) => value.id)))];
}

const sockets = new Set<ServerWebSocket<Client>>();

/**
* This host's channels changed. Peers receive only that, so their own broadcasts never echo back:
* two hosts relaying each other's frames would loop forever, starving both event loops.
*/
function broadcast() {
const own = local();
const frame = JSON.stringify({ channels: own } satisfies HostFrame);
for (const socket of sockets) if (socket.data.peer) socket.send(frame);
ownersChanged(own);
}

/** Peer and directory listings reach only the owner's app; peers never see them. */
function ownersChanged(own?: Listing[]) {
let frame: string | undefined;
for (const socket of sockets) {
socket.send(JSON.stringify({ channels: listings(socket.data) } satisfies HostFrame));
if (socket.data.peer) continue;
frame ??= JSON.stringify(
{ channels: listings(socket.data, own || local()) } satisfies HostFrame,
);
socket.send(frame);
}
peopleChanged();
}
Expand Down Expand Up @@ -652,15 +669,15 @@ export async function serve(port: number): Promise<never> {
const publish = directory.watch(
() => ({ name, login: catalog.user, github: github.login(peopleChanged), ...address }),
local,
broadcast,
() => ownersChanged(),
);
log("info", "host.tailnet", machine ? { name, address: machine.address } : { tailscale: false });
cleanup.push(publish.stop);
if (machine) {
servers.push(team(machine, { port, secure: false }));
console.log(`Sharing with the tailnet on ${machine.address}:${port} as ${machine.login}`);
console.log(`Your devices can open http://${machine.dns}:${port}`);
cleanup.push(peers.watch(broadcast));
cleanup.push(peers.watch(() => ownersChanged()));
const tls = await web.tls(machine);
if (tls && !closing) {
const listener = { port: config.webPort ?? port + 1000, secure: true };
Expand Down