Skip to content

Commit

Permalink
feat: ipv6 support
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Sep 15, 2022
1 parent 5dbf933 commit eb9f5ef
Showing 1 changed file with 35 additions and 19 deletions.
54 changes: 35 additions & 19 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import https from 'https'
import { promisify } from 'util'
import { promises as fs } from 'fs'
import { networkInterfaces } from 'os'
import { joinURL } from 'ufo'
import type { AddressInfo } from 'net'
import { cyan, gray, underline, bold } from 'colorette'
import { getPort, GetPortInput } from 'get-port-please'
import addShutdown from 'http-shutdown'
Expand Down Expand Up @@ -44,6 +44,7 @@ export interface ShowURLOptions {

export interface Listener {
url: string,
address: { },
server: http.Server | https.Server,
close: () => Promise<void>,
open: () => Promise<void>,
Expand All @@ -53,7 +54,7 @@ export interface Listener {
export async function listen (handle: http.RequestListener, opts: Partial<ListenOptions> = {}): Promise<Listener> {
opts = defu(opts, {
port: process.env.PORT || 3000,
hostname: process.env.HOST || '0.0.0.0',
hostname: process.env.HOST || '',
showURL: true,
baseURL: '/',
open: false,
Expand All @@ -79,24 +80,25 @@ export async function listen (handle: http.RequestListener, opts: Partial<Listen
})

let server: http.Server | https.Server
let url: string

const isExternal = opts.hostname === '0.0.0.0'
const displayHost = isExternal ? 'localhost' : opts.hostname
let addr: { proto: 'http' | 'https', addr: string, port: number } | null
const getURL = (host?: string, baseURL?: string) => `${addr!.proto}://${host || addr!.addr}:${addr!.port}${baseURL || opts.baseURL}`

if (opts.https) {
const { key, cert } = await resolveCert({ ...opts.https as any })
server = https.createServer({ key, cert }, handle)
addShutdown(server)
// @ts-ignore
await promisify(server.listen.bind(server))(port, opts.hostname)
url = `https://${displayHost}:${port}${opts.baseURL}`
const _addr = server.address() as AddressInfo
addr = { proto: 'https', addr: formatAddress(_addr), port: _addr.port }
} else {
server = http.createServer(handle)
addShutdown(server)
// @ts-ignore
await promisify(server.listen.bind(server))(port, opts.hostname)
url = `http://${displayHost}:${port}${opts.baseURL}`
const _addr = server.address() as AddressInfo
addr = { proto: 'http', addr: formatAddress(_addr), port: _addr.port }
}

let _closed = false
Expand All @@ -110,19 +112,24 @@ export async function listen (handle: http.RequestListener, opts: Partial<Listen

if (opts.clipboard) {
const clipboardy = await import('clipboardy').then(r => r.default || r)
await clipboardy.write(url).catch(() => { opts.clipboard = false })
await clipboardy.write(getURL('localhost')).catch(() => { opts.clipboard = false })
}

const showURL = (options?: ShowURLOptions) => {
const add = opts.clipboard ? gray('(copied to clipboard)') : ''
const lines = []
const baseURL = options?.baseURL || opts.baseURL || ''
const name = options?.name ? ` (${options.name})` : ''
lines.push(` > Local${name}: ${formatURL(joinURL(url, baseURL))} ${add}`)
if (isExternal) {
for (const ip of getExternalIps()) {
lines.push(` > Network${name}: ${formatURL(joinURL(url.replace('localhost', ip), baseURL))}`)

const anyV4 = addr?.addr === '0.0.0.0'
const anyV6 = addr?.addr === '[::]'
if (anyV4 || anyV6) {
lines.push(` > Local${name}: ${formatURL(getURL('localhost', baseURL))} ${add}`)
for (const addr of getNetworkInterfaces(anyV4)) {
lines.push(` > Network${name}: ${formatURL(getURL(addr, baseURL))}`)
}
} else {
lines.push(` > Listening${name}: ${formatURL(getURL(undefined, baseURL))} ${add}`)
}
// eslint-disable-next-line no-console
console.log('\n' + lines.join('\n') + '\n')
Expand All @@ -133,7 +140,7 @@ export async function listen (handle: http.RequestListener, opts: Partial<Listen
}

const _open = async () => {
await open(url).catch(() => { })
await open(getURL('localhost')).catch(() => { })
}
if (opts.open) {
await _open()
Expand All @@ -144,7 +151,7 @@ export async function listen (handle: http.RequestListener, opts: Partial<Listen
}

return <Listener>{
url,
url: getURL(),
server,
open: _open,
showURL,
Expand Down Expand Up @@ -175,18 +182,27 @@ async function resolveCert (opts: HTTPSOptions): Promise<Certificate> {
return cert
}

function getExternalIps (): string[] {
const ips = new Set<string>()
function getNetworkInterfaces (v4Only: boolean = true): string[] {
const addrs = new Set<string>()
for (const details of Object.values(networkInterfaces())) {
if (details) {
for (const d of details) {
if ((d.family === 'IPv4' || +d.family === 4) && !d.internal) {
ips.add(d.address)
if (
!d.internal &&
!(d.mac === '00:00:00:00:00:00') &&
!(d.address.startsWith('fe80::')) &&
!(v4Only && (d.family === 'IPv6' || +d.family === 6))
) {
addrs.add(formatAddress(d))
}
}
}
}
return Array.from(ips)
return Array.from(addrs).sort()
}

function formatAddress (addr: { family: string | number, address: string }) {
return ((addr.family === 'IPv6' || addr.family === 6) ? `[${addr.address}]` : addr.address)
}

function formatURL (url: string) {
Expand Down

0 comments on commit eb9f5ef

Please sign in to comment.