All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 41s
iCloud Private Relay hides iPhones' real public IP, breaking IP-based LAN grouping. Devices now detect their local IP (192.168.x.x) via WebRTC ICE candidates and send it to the server. The server groups devices by local subnet as a fallback when public IPs differ. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
53 lines
1.3 KiB
TypeScript
53 lines
1.3 KiB
TypeScript
/**
|
|
* Detect the local LAN IP (192.168.x.x, 10.x.x.x, 172.16-31.x.x)
|
|
* using WebRTC ICE candidate gathering.
|
|
*
|
|
* Returns the first private IPv4 found, or undefined if none detected
|
|
* (e.g. browser blocks mDNS candidates).
|
|
*/
|
|
export async function detectLocalIP(timeoutMs = 3000): Promise<string | undefined> {
|
|
return new Promise((resolve) => {
|
|
let resolved = false;
|
|
const done = (ip?: string) => {
|
|
if (resolved) return;
|
|
resolved = true;
|
|
pc.close();
|
|
clearTimeout(timer);
|
|
resolve(ip);
|
|
};
|
|
|
|
const timer = setTimeout(() => done(undefined), timeoutMs);
|
|
|
|
const pc = new RTCPeerConnection({
|
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
|
});
|
|
|
|
pc.createDataChannel("");
|
|
|
|
pc.onicecandidate = (event) => {
|
|
if (!event.candidate?.candidate) return;
|
|
const match = event.candidate.candidate.match(
|
|
/(?:srflx|host)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s/,
|
|
);
|
|
if (match) {
|
|
const ip = match[1];
|
|
if (isPrivateIPv4(ip)) {
|
|
done(ip);
|
|
}
|
|
}
|
|
};
|
|
|
|
pc.createOffer()
|
|
.then((offer) => pc.setLocalDescription(offer))
|
|
.catch(() => done(undefined));
|
|
});
|
|
}
|
|
|
|
function isPrivateIPv4(ip: string): boolean {
|
|
return (
|
|
ip.startsWith("192.168.") ||
|
|
ip.startsWith("10.") ||
|
|
/^172\.(1[6-9]|2\d|3[01])\./.test(ip)
|
|
);
|
|
}
|