From dcf33df1a60d615e6c896e2952a49c6cf1c25a4f Mon Sep 17 00:00:00 2001 From: Richie Bendall Date: Sat, 21 Nov 2020 04:44:47 +1300 Subject: [PATCH] Add browser support (#36) Co-authored-by: Sindre Sorhus --- browser.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 14 ++++++++--- readme.md | 2 +- 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 browser.js diff --git a/browser.js b/browser.js new file mode 100644 index 0000000..0befb48 --- /dev/null +++ b/browser.js @@ -0,0 +1,68 @@ +/* eslint-env browser */ +'use strict'; +import pEvent from 'p-event'; +import isIp from 'is-ip'; + +const getIp = async ({isSecondTry = false} = {}) => { + try { + const peerConnection = new RTCPeerConnection({iceServers: []}); + + peerConnection.createDataChannel(''); + peerConnection.createOffer(peerConnection.setLocalDescription.bind(peerConnection), () => {}); + + const {candidate} = await pEvent(peerConnection, 'icecandidate', { + timeout: 10000 + }); + + peerConnection.close(); + + if (candidate && candidate.candidate) { + const result = candidate.candidate.split(' ')[4]; + if (result.endsWith('.local')) { + if (isSecondTry) { + return; + } + + const inputDevices = await navigator.mediaDevices.enumerateDevices(); + const inputDeviceTypes = new Set(inputDevices.map(({kind}) => kind)); + + const constraints = {}; + + if (inputDeviceTypes.has('audioinput')) { + constraints.audio = true; + } else if (inputDeviceTypes.has('videoinput')) { + constraints.video = true; + } else { + return; + } + + const mediaStream = await navigator.mediaDevices.getUserMedia(constraints); + for (const track of mediaStream.getTracks()) { + track.stop(); + } + + return await getIp({isSecondTry: true}); + } + + return result; + } + } catch {} +}; + +export const v4 = async () => { + const result = await getIp(); + if (isIp.v4(result)) { + return result; + } +}; + +v4.sync = () => undefined; + +export const v6 = async () => { + const result = await getIp(); + if (isIp.v6(result)) { + return result; + } +}; + +v6.sync = () => undefined; diff --git a/package.json b/package.json index bff021a..0c1aadb 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,13 @@ }, "files": [ "index.js", - "index.d.ts" + "index.d.ts", + "browser.js" ], + "exports": { + "browser": "./browser.js", + "default": "./index.js" + }, "keywords": [ "ip", "ipv6", @@ -34,11 +39,14 @@ ], "dependencies": { "default-gateway": "^6.0.0", - "ipaddr.js": "^1.9.1" + "ipaddr.js": "^1.9.1", + "is-ip": "^3.1.0", + "p-event": "^4.2.0" }, "devDependencies": { "ava": "^2.4.0", "tsd": "^0.13.1", "xo": "^0.32.1" - } + }, + "browser": "browser.js" } diff --git a/readme.md b/readme.md index 6214456..930fe32 100644 --- a/readme.md +++ b/readme.md @@ -30,7 +30,7 @@ console.log(internalIp.v4.sync()) The module returns the address of the internet-facing interface, as determined from the default gateway. When the address cannot be determined for any reason, `undefined` will be returned. -The module relies on operating systems tools. On Linux and Android, the `ip` command must be available, which depending on distribution might not be installed by default. It is usually provided by the `iproute2` package. +The module relies on operating systems tools. On Linux and Android, the `ip` command must be available, which depending on distribution might not be installed by default. It is usually provided by the `iproute2` package. `.v4.sync()` and `.v6.sync()` are not supported in browsers and just return `undefined`. ## Related