Electron Development: Get the current client IP
1. Background and needs
1. Project background
The client will start a service automatically, and the Web/backend service will request it through IP + port to operate the client interface.
2. Initial Plans and Issues
2.1. Initial plan: Get the native IP through code
/**
* Obtain LAN IP
* @returns {string} LAN IP
*/
export function getLocalIP(): string {
const interfaces = ()
for (const name of (interfaces)) {
for (const ife of interfaces[name] || []) {
if ( === 'IPv4' && !) {
('Get LAN IP:', )
Return
}
}
}
('Cannot get LAN IP, use default IP: 127.0.0.1')
return '127.0.0.1'
}
2.2. Problems encountered
If the device has enabled the proxy, it may obtain the proxy IP, causing the backend request to fail.
2. Solution Design
1. Overall idea
- Get all IPs on this machine
- Traversing IP + port request client service interface
- The target IP is the response successfully
- Caches valid IP to avoid frequent requests
2. Obtain all possible IPs
Use () to get all available IPs
private getAllPossibleIPs(): string[] {
const interfaces = ()
const result: string[] = []
for (const name of (interfaces)) {
const lowerName = ()
if (('vmware')
|| ('virtual')
|| ('vpn')
|| ('docker')
|| ('vethernet')) {
continue
}
for (const iface of interfaces[name] || []) {
if ( === 'IPv4' && !) {
()
}
}
}
return result
}
3. Traversing IP request verification
Poll all IPs, try to access the client service, verify that it is available
private async testIPsParallel(ips: string[]): Promise<string | null> {
if ( === 0)
return null
return new Promise((resolve) => {
const globalTimeout = setTimeout(() => {
resolve(null)
}, * 1.5)
const controllers = (() => new AbortController())
let hasResolved = false
let completedCount = 0
const testIP = (ip: string, index: number) => {
const controller = controllers[index]
(`http://${ip}:${PORT}/api/task-server/ip`, {
timeout: ,
signal: ,
})
.then(() => {
if (!hasResolved) {
hasResolved = true
clearTimeout(globalTimeout)
((c, i) => {
if (i !== index)
()
})
resolve(ip)
}
})
.catch(() => {
if (!hasResolved) {
completedCount++
if (completedCount >= ) {
clearTimeout(globalTimeout)
resolve(null)
}
}
})
}
(testIP)
})
}
4. Add a cache policy
Cache successful IPs, set the cache valid time, and avoid repeated requests
private cachedValidIP: string | null = null
private lastValidationTime = 0
private readonly CACHE_VALID_DURATION = 24 * 60 * 60 * 1000
3. Complete code
import os from 'node:os'
import axios from 'axios'
import { PORT } from '../../enum/env'
/**
* IP Manager Singleton Class
* Used to obtain and cache local valid IP addresses
*/
export class IPManager {
private static instance: IPManager
private cachedValidIP: string | null = null
private lastValidationTime = 0
private readonly CACHE_VALID_DURATION = 24 * 60 * 60 * 1000
private readonly TIMEOUT = 200
private isTestingIPs = false
private constructor() {}
static getInstance(): IPManager {
if (!) {
= new IPManager()
}
Return
}
async getLocalIP(): Promise<string> {
const now = ()
if ( && now - < this.CACHE_VALID_DURATION) {
('Get IP from cache', )
Return
}
if () {
const allIPs = ()
return > 0 ? allIPs[0] : '127.0.0.1'
}
= true
try {
const allIPs = ()
if ( === 0) {
return '127.0.0.1'
}
const validIP = await (allIPs)
if (validIP) {
= validIP
= now
return validIP
}
return allIPs[0]
}
catch (error) {
const allIPs = ()
return > 0 ? allIPs[0] : '127.0.0.1'
}
Finally {
= false
}
}
private getAllPossibleIPs(): string[] {
const interfaces = ()
const result: string[] = []
for (const name of (interfaces)) {
const lowerName = ()
if (('vmware')
|| ('virtual')
|| ('vpn')
|| ('docker')
|| ('vethernet')) {
Continue continue
}
for (const ife of interfaces[name] || []) {
if ( === 'IPv4' && !) {
()
}
}
}
return result
}
private async testIPsParallel(ips: string[]): Promise<string | null> {
if ( === 0)
return null
return new Promise((resolve) => {
const globalTimeout = setTimeout(() => {
resolve(null)
}, * 1.5)
const controllers = (() => new AbortController())
let hasResolved = false
let completedCount = 0
const testIP = (ip: string, index: number) => {
const controller = controllers[index]
(`http://${ip}:${PORT}/api/task-server/ip`, {
timeout: ,
signal: ,
// validateStatus: status => status === 200,
})
.then(() => {
if (!hasResolved) {
hasResolved = true
clearTimeout(globalTimeout)
((c, i) => {
if (i !== index)
()
})
resolve(ip)
}
})
.catch(() => {
if (!hasResolved) {
completedCount++
if (completedCount >= ) {
clearTimeout(globalTimeout)
resolve(null)
}
}
})
}
(testIP)
})
}
}
/**
* Get a local valid IP address
*/
export async function getLocalIP(): Promise<string> {
return ().getLocalIP()
}