diff --git a/src/index.ts b/src/index.ts index 6e9d6c3..9d9cd8e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,20 +46,35 @@ const ASSUME_HTTP_CODES = [ interface Interpreter { (value: string, ma: StringTuple[]): string } -function extractSNI (ma: StringTuple[]): string | null { - let sniProtoCode: number +function extractSNI (ma: StringTuple[]): string | undefined { + return extractTuple('sni', ma)?.[1] +} + +function extractPort (ma: StringTuple[]): string { + const port = extractTuple('tcp', ma)?.[1] + + if (port == null) { + return '' + } + + return `:${port}` +} + +function extractTuple (name: string, ma: StringTuple[]): StringTuple | undefined { + let code: number + try { - sniProtoCode = protocols('sni').code + code = protocols(name).code } catch (e) { - // No SNI protocol in multiaddr - return null + // No support for protocol in multiaddr + return } + for (const [proto, value] of ma) { - if (proto === sniProtoCode && value !== undefined) { - return value + if (proto === code && value != null) { + return [proto, value] } } - return null } function hasTLS (ma: StringTuple[]): boolean { @@ -68,7 +83,7 @@ function hasTLS (ma: StringTuple[]): boolean { function interpretNext (headProtoCode: number, headProtoVal: string, restMa: StringTuple[]): string { const interpreter = interpreters[protocols(headProtoCode).name] - if (interpreter === undefined) { + if (interpreter == null) { throw new Error(`Can't interpret protocol ${protocols(headProtoCode).name}`) } const restVal = interpreter(headProtoVal, restMa) @@ -88,14 +103,14 @@ const interpreters: Record = { }, tcp: (value: string, restMa: StringTuple[]) => { const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } return `tcp://${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}:${value}` }, udp: (value: string, restMa: StringTuple[]) => { const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } return `udp://${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}:${value}` @@ -106,14 +121,14 @@ const interpreters: Record = { dns: (value: string, restMa: StringTuple[]) => value, ipfs: (value: string, restMa: StringTuple[]) => { const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } return `${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}/ipfs/${value}` }, p2p: (value: string, restMa: StringTuple[]) => { const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } return `${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}/p2p/${value}` @@ -121,12 +136,13 @@ const interpreters: Record = { http: (value: string, restMa: StringTuple[]) => { const maHasTLS = hasTLS(restMa) const sni = extractSNI(restMa) - if (maHasTLS && sni !== null) { - return `https://${sni}` + const port = extractPort(restMa) + if (maHasTLS && sni != null) { + return `https://${sni}${port}` } const protocol = maHasTLS ? 'https://' : 'http://' const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } let baseVal = interpretNext(tailProto[0], tailProto[1] ?? '', restMa) @@ -136,7 +152,7 @@ const interpreters: Record = { }, 'http-path': (value: string, restMa: StringTuple[]) => { const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } const baseVal = interpretNext(tailProto[0], tailProto[1] ?? '', restMa) @@ -147,7 +163,7 @@ const interpreters: Record = { // Noop, the parent context knows that it's tls. We don't need to do // anything here const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } return interpretNext(tailProto[0], tailProto[1] ?? '', restMa) @@ -156,14 +172,14 @@ const interpreters: Record = { // Noop, the parent context uses the sni information, we don't need to do // anything here const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } return interpretNext(tailProto[0], tailProto[1] ?? '', restMa) }, https: (value: string, restMa: StringTuple[]) => { const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } let baseVal = interpretNext(tailProto[0], tailProto[1] ?? '', restMa) @@ -174,12 +190,13 @@ const interpreters: Record = { ws: (value: string, restMa: StringTuple[]) => { const maHasTLS = hasTLS(restMa) const sni = extractSNI(restMa) - if (maHasTLS && sni !== null) { - return `wss://${sni}` + const port = extractPort(restMa) + if (maHasTLS && sni != null) { + return `wss://${sni}${port}` } const protocol = maHasTLS ? 'wss://' : 'ws://' const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } let baseVal = interpretNext(tailProto[0], tailProto[1] ?? '', restMa) @@ -189,7 +206,7 @@ const interpreters: Record = { }, wss: (value: string, restMa: StringTuple[]) => { const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } let baseVal = interpretNext(tailProto[0], tailProto[1] ?? '', restMa) @@ -199,21 +216,21 @@ const interpreters: Record = { }, 'p2p-websocket-star': (value: string, restMa: StringTuple[]) => { const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } return `${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}/p2p-websocket-star` }, 'p2p-webrtc-star': (value: string, restMa: StringTuple[]) => { const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } return `${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}/p2p-webrtc-star` }, 'p2p-webrtc-direct': (value: string, restMa: StringTuple[]) => { const tailProto = restMa.pop() - if (tailProto === undefined) { + if (tailProto == null) { throw new Error('Unexpected end of multiaddr') } return `${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}/p2p-webrtc-direct` @@ -224,7 +241,7 @@ export function multiaddrToUri (input: Multiaddr | string | Uint8Array, opts?: M const ma = multiaddr(input) const parts = ma.stringTuples() const head = parts.pop() - if (head === undefined) { + if (head == null) { throw new Error('Unexpected end of multiaddr') } @@ -248,7 +265,7 @@ export function multiaddrToUri (input: Multiaddr | string | Uint8Array, opts?: M } } - if (uri.startsWith('http://') || uri.startsWith('https://')) { + if (uri.startsWith('http://') || uri.startsWith('https://') || uri.startsWith('ws://') || uri.startsWith('wss://')) { // this will strip default ports while keeping paths intact uri = new URL(uri).toString() diff --git a/test/test.spec.ts b/test/test.spec.ts index 00dd96f..d791ab1 100644 --- a/test/test.spec.ts +++ b/test/test.spec.ts @@ -12,8 +12,10 @@ describe('multiaddr-to-uri', () => { ['/ip4/0.0.7.6/tcp/1234/http', 'http://0.0.7.6:1234'], ['/ip4/0.0.7.6/tcp/1234/https', 'https://0.0.7.6:1234'], ['/ip4/0.0.7.6/tcp/1234/tls/http', 'https://0.0.7.6:1234'], - ['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/http', 'https://ipfs.io'], - ['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/http/http-path/foo%2fbar', 'https://ipfs.io/foo/bar'], + ['/ip4/1.2.3.4/tcp/443/tls/sni/ipfs.io/http', 'https://ipfs.io'], + ['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/http', 'https://ipfs.io:1234'], + ['/ip4/1.2.3.4/tcp/443/tls/sni/ipfs.io/http/http-path/foo%2fbar', 'https://ipfs.io/foo/bar'], + ['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/http/http-path/foo%2fbar', 'https://ipfs.io:1234/foo/bar'], ['/ip4/0.0.7.6/udp/1234', 'udp://0.0.7.6:1234'], ['/ip6/::/udp/0', 'udp://[::]:0'], ['/dns/a.com/tcp/1234', 'http://a.com:1234'], @@ -35,7 +37,8 @@ describe('multiaddr-to-uri', () => { ['/ip6/::/tcp/0/ws', 'ws://[::]:0'], ['/dnsaddr/ipfs.io/wss', 'wss://ipfs.io'], ['/dnsaddr/ipfs.io/tls/ws', 'wss://ipfs.io'], - ['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/ws', 'wss://ipfs.io'], + ['/ip4/1.2.3.4/tcp/443/tls/sni/ipfs.io/ws', 'wss://ipfs.io'], + ['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/ws', 'wss://ipfs.io:1234'], ['/ip4/1.2.3.4/tcp/3456/wss', 'wss://1.2.3.4:3456'], ['/ip6/::/tcp/0/wss', 'wss://[::]:0'], [ @@ -56,7 +59,7 @@ describe('multiaddr-to-uri', () => { ], [ '/dns4/wrtc-star.discovery.libp2p.io/tcp/443/wss/p2p-webrtc-star/ipfs/QmTysQQiTGMdfRsDQp516oZ9bR3FiSCDnicUnqny2q1d79', - 'wss://wrtc-star.discovery.libp2p.io:443/p2p-webrtc-star/p2p/QmTysQQiTGMdfRsDQp516oZ9bR3FiSCDnicUnqny2q1d79' + 'wss://wrtc-star.discovery.libp2p.io/p2p-webrtc-star/p2p/QmTysQQiTGMdfRsDQp516oZ9bR3FiSCDnicUnqny2q1d79' ], ['/ip4/1.2.3.4/tcp/3456/http/p2p-webrtc-direct', 'http://1.2.3.4:3456/p2p-webrtc-direct'], ['/ip6/::/tcp/0/http/p2p-webrtc-direct', 'http://[::]:0/p2p-webrtc-direct'], @@ -72,7 +75,7 @@ describe('multiaddr-to-uri', () => { ], [ '/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star/ipfs/QmP3vadpN9dqZ7j6KtmwP5Y4prg7XqdS7ixgZMWtXxBAbp', - 'wss://ws-star.discovery.libp2p.io:443/p2p-websocket-star/p2p/QmP3vadpN9dqZ7j6KtmwP5Y4prg7XqdS7ixgZMWtXxBAbp' + 'wss://ws-star.discovery.libp2p.io/p2p-websocket-star/p2p/QmP3vadpN9dqZ7j6KtmwP5Y4prg7XqdS7ixgZMWtXxBAbp' ], [ '/ip4/127.0.0.1/tcp/20008/ws/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj',