Location>code7788 >text

Electron Development: Get the current client IP

Popularity:506 ℃/2025-04-11 11:35:06

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()
 }