WebRTC fetchUserIpAddress本地正常生产环境失效问题排查与解决
问题原因分析
- ICE服务器配置缺失:本地环境下WebRTC无需STUN服务器就能获取内网IP,但生产环境中,没有STUN服务器的话,浏览器无法生成包含公网IP的ICE候选,甚至可能不触发有效的候选事件。
- ICE候选事件处理逻辑错误:当前代码在
event.candidate为null时直接reject,但null候选是所有候选收集完成的标志,此时可能已经有有效IP候选被忽略;同时只处理第一个候选,若第一个候选不包含可匹配的IP就直接失败。 - 正则表达式覆盖不全:现有正则只能匹配完整的IPv4和标准IPv6,无法处理压缩形式的IPv6(如
2001:db8::1)或候选字符串中其他位置的IP。 - 非安全上下文限制:如果生产环境使用HTTP而非HTTPS,现代浏览器会限制WebRTC API的使用,导致无法生成ICE候选。
修改后的代码
export const fetchUserIpAddress = (): Promise<string> => { return new Promise<string>((resolve, reject) => { // 设置超时,防止无限等待 const timeout = setTimeout(() => { reject("获取IP超时"); rtc.close(); }, 5000); const rtc = new RTCPeerConnection({ iceServers: [ { urls: "stun:stun.l.google.com:19302" } // 添加公共STUN服务器 ] }); let foundIp: string | null = null; rtc.createDataChannel(""); rtc.createOffer() .then((offer) => rtc.setLocalDescription(offer)) .catch((error) => { clearTimeout(timeout); reject(error); rtc.close(); }); rtc.onicecandidate = (event) => { if (event.candidate) { const candidate = event.candidate.candidate; // 覆盖IPv4、压缩/完整IPv6的正则 const ipRegex = /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b|(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:(?:(:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])/; const ipMatch = candidate.match(ipRegex); if (ipMatch && !foundIp) { foundIp = ipMatch[0]; // 优先返回公网IP,过滤常见内网段 if (!foundIp.startsWith("192.168.") && !foundIp.startsWith("10.") && !foundIp.startsWith("172.") && !foundIp.startsWith("fe80:")) { clearTimeout(timeout); resolve(foundIp); rtc.close(); } } } else { // 所有候选收集完成后统一处理 clearTimeout(timeout); foundIp ? resolve(foundIp) : reject("未找到可用IP地址"); rtc.close(); } }; }); };
修改说明
- 添加STUN服务器:借助公共STUN服务器获取公网IP,解决生产环境无法生成有效候选的问题。
- 优化候选处理逻辑:收集所有ICE候选直到事件结束,优先筛选公网IP,避免遗漏有效地址。
- 增强正则表达式:支持压缩格式IPv6等更多IP场景,提升匹配成功率。
- 添加超时机制:避免因网络问题导致Promise长期处于pending状态。
- 安全上下文要求:确保生产环境使用HTTPS,现代浏览器仅允许在安全上下文下调用WebRTC API。
内容的提问来源于stack exchange,提问作者BugHunter
相关产品推荐
相关产品推荐

