Skip to content

Commit 35348cd

Browse files
authoredNov 6, 2023
fix: use Multiaddr as class name (#352)
Cosmetic change ahead. When logging multaddrs in browsers `DefaultMultiaddr` appears in the console. It's called this to prevent a collision with the `Multiaddr` TypeScript interface name. The interfaces are removed at compile time so here we import the interface with a different name, freeing up the `Multiaddr` symbol to be used as the class name.
1 parent 21938b5 commit 35348cd

File tree

2 files changed

+290
-273
lines changed

2 files changed

+290
-273
lines changed
 

‎src/index.ts

+4-273
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,8 @@
1212
* ```
1313
*/
1414

15-
import { CodeError } from '@libp2p/interface/errors'
16-
import { base58btc } from 'multiformats/bases/base58'
17-
import { CID } from 'multiformats/cid'
18-
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
19-
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
20-
import { bytesToMultiaddrParts, stringToMultiaddrParts, type MultiaddrParts, tuplesToBytes } from './codec.js'
21-
import { getProtocol, names } from './protocols-table.js'
22-
23-
const inspect = Symbol.for('nodejs.util.inspect.custom')
24-
25-
const DNS_CODES = [
26-
getProtocol('dns').code,
27-
getProtocol('dns4').code,
28-
getProtocol('dns6').code,
29-
getProtocol('dnsaddr').code
30-
]
15+
import { Multiaddr as MultiaddrClass, symbol } from './multiaddr.js'
16+
import { getProtocol } from './protocols-table.js'
3117

3218
/**
3319
* Protocols are present in the protocol table
@@ -91,7 +77,6 @@ export interface AbortOptions {
9177
* All configured {@link Resolver}s
9278
*/
9379
export const resolvers = new Map<string, Resolver>()
94-
const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr')
9580

9681
export { MultiaddrFilter } from './filter/multiaddr-filter.js'
9782

@@ -444,7 +429,7 @@ export function fromNodeAddress (addr: NodeAddress, transport: string): Multiadd
444429
default:
445430
throw Error('Invalid addr family, should be 4 or 6.')
446431
}
447-
return new DefaultMultiaddr('/' + [ip, host, transport, addr.port].join('/'))
432+
return new MultiaddrClass('/' + [ip, host, transport, addr.port].join('/'))
448433
}
449434

450435
/**
@@ -488,260 +473,6 @@ export function isMultiaddr (value: any): value is Multiaddr {
488473
return Boolean(value?.[symbol])
489474
}
490475

491-
/**
492-
* Creates a {@link Multiaddr} from a {@link MultiaddrInput}
493-
*/
494-
class DefaultMultiaddr implements Multiaddr {
495-
public bytes: Uint8Array
496-
#string: string
497-
#tuples: Tuple[]
498-
#stringTuples: StringTuple[]
499-
#path: string | null
500-
501-
[symbol]: boolean = true
502-
503-
constructor (addr?: MultiaddrInput) {
504-
// default
505-
if (addr == null) {
506-
addr = ''
507-
}
508-
509-
let parts: MultiaddrParts
510-
if (addr instanceof Uint8Array) {
511-
parts = bytesToMultiaddrParts(addr)
512-
} else if (typeof addr === 'string') {
513-
if (addr.length > 0 && addr.charAt(0) !== '/') {
514-
throw new Error(`multiaddr "${addr}" must start with a "/"`)
515-
}
516-
parts = stringToMultiaddrParts(addr)
517-
} else if (isMultiaddr(addr)) { // Multiaddr
518-
parts = bytesToMultiaddrParts(addr.bytes)
519-
} else {
520-
throw new Error('addr must be a string, Buffer, or another Multiaddr')
521-
}
522-
523-
this.bytes = parts.bytes
524-
this.#string = parts.string
525-
this.#tuples = parts.tuples
526-
this.#stringTuples = parts.stringTuples
527-
this.#path = parts.path
528-
}
529-
530-
toString (): string {
531-
return this.#string
532-
}
533-
534-
toJSON (): string {
535-
return this.toString()
536-
}
537-
538-
toOptions (): MultiaddrObject {
539-
let family: 4 | 6 | undefined
540-
let transport: string | undefined
541-
let host: string | undefined
542-
let port: number | undefined
543-
let zone = ''
544-
545-
const tcp = getProtocol('tcp')
546-
const udp = getProtocol('udp')
547-
const ip4 = getProtocol('ip4')
548-
const ip6 = getProtocol('ip6')
549-
const dns6 = getProtocol('dns6')
550-
const ip6zone = getProtocol('ip6zone')
551-
552-
for (const [code, value] of this.stringTuples()) {
553-
if (code === ip6zone.code) {
554-
zone = `%${value ?? ''}`
555-
}
556-
557-
// default to https when protocol & port are omitted from DNS addrs
558-
if (DNS_CODES.includes(code)) {
559-
transport = tcp.name
560-
port = 443
561-
host = `${value ?? ''}${zone}`
562-
family = code === dns6.code ? 6 : 4
563-
}
564-
565-
if (code === tcp.code || code === udp.code) {
566-
transport = getProtocol(code).name
567-
port = parseInt(value ?? '')
568-
}
569-
570-
if (code === ip4.code || code === ip6.code) {
571-
transport = getProtocol(code).name
572-
host = `${value ?? ''}${zone}`
573-
family = code === ip6.code ? 6 : 4
574-
}
575-
}
576-
577-
if (family == null || transport == null || host == null || port == null) {
578-
throw new Error('multiaddr must have a valid format: "/{ip4, ip6, dns4, dns6, dnsaddr}/{address}/{tcp, udp}/{port}".')
579-
}
580-
581-
const opts: MultiaddrObject = {
582-
family,
583-
host,
584-
transport,
585-
port
586-
}
587-
588-
return opts
589-
}
590-
591-
protos (): Protocol[] {
592-
return this.#tuples.map(([code]) => Object.assign({}, getProtocol(code)))
593-
}
594-
595-
protoCodes (): number[] {
596-
return this.#tuples.map(([code]) => code)
597-
}
598-
599-
protoNames (): string[] {
600-
return this.#tuples.map(([code]) => getProtocol(code).name)
601-
}
602-
603-
tuples (): Array<[number, Uint8Array?]> {
604-
return this.#tuples
605-
}
606-
607-
stringTuples (): Array<[number, string?]> {
608-
return this.#stringTuples
609-
}
610-
611-
encapsulate (addr: MultiaddrInput): Multiaddr {
612-
addr = new DefaultMultiaddr(addr)
613-
return new DefaultMultiaddr(this.toString() + addr.toString())
614-
}
615-
616-
decapsulate (addr: Multiaddr | string): Multiaddr {
617-
const addrString = addr.toString()
618-
const s = this.toString()
619-
const i = s.lastIndexOf(addrString)
620-
if (i < 0) {
621-
throw new Error(`Address ${this.toString()} does not contain subaddress: ${addr.toString()}`)
622-
}
623-
return new DefaultMultiaddr(s.slice(0, i))
624-
}
625-
626-
decapsulateCode (code: number): Multiaddr {
627-
const tuples = this.tuples()
628-
for (let i = tuples.length - 1; i >= 0; i--) {
629-
if (tuples[i][0] === code) {
630-
return new DefaultMultiaddr(tuplesToBytes(tuples.slice(0, i)))
631-
}
632-
}
633-
return this
634-
}
635-
636-
getPeerId (): string | null {
637-
try {
638-
let tuples: Array<[number, string | undefined]> = []
639-
640-
this.stringTuples().forEach(([code, name]) => {
641-
if (code === names.p2p.code) {
642-
tuples.push([code, name])
643-
}
644-
645-
// if this is a p2p-circuit address, return the target peer id if present
646-
// not the peer id of the relay
647-
if (code === names['p2p-circuit'].code) {
648-
tuples = []
649-
}
650-
})
651-
652-
// Get the last ipfs tuple ['p2p', 'peerid string']
653-
const tuple = tuples.pop()
654-
if (tuple?.[1] != null) {
655-
const peerIdStr = tuple[1]
656-
657-
// peer id is base58btc encoded string but not multibase encoded so add the `z`
658-
// prefix so we can validate that it is correctly encoded
659-
if (peerIdStr[0] === 'Q' || peerIdStr[0] === '1') {
660-
return uint8ArrayToString(base58btc.decode(`z${peerIdStr}`), 'base58btc')
661-
}
662-
663-
// try to parse peer id as CID
664-
return uint8ArrayToString(CID.parse(peerIdStr).multihash.bytes, 'base58btc')
665-
}
666-
667-
return null
668-
} catch (e) {
669-
return null
670-
}
671-
}
672-
673-
getPath (): string | null {
674-
return this.#path
675-
}
676-
677-
equals (addr: { bytes: Uint8Array }): boolean {
678-
return uint8ArrayEquals(this.bytes, addr.bytes)
679-
}
680-
681-
async resolve (options?: AbortOptions): Promise<Multiaddr[]> {
682-
const resolvableProto = this.protos().find((p) => p.resolvable)
683-
684-
// Multiaddr is not resolvable?
685-
if (resolvableProto == null) {
686-
return [this]
687-
}
688-
689-
const resolver = resolvers.get(resolvableProto.name)
690-
if (resolver == null) {
691-
throw new CodeError(`no available resolver for ${resolvableProto.name}`, 'ERR_NO_AVAILABLE_RESOLVER')
692-
}
693-
694-
const addresses = await resolver(this, options)
695-
return addresses.map((a) => new DefaultMultiaddr(a))
696-
}
697-
698-
nodeAddress (): NodeAddress {
699-
const options = this.toOptions()
700-
701-
if (options.transport !== 'tcp' && options.transport !== 'udp') {
702-
throw new Error(`multiaddr must have a valid format - no protocol with name: "${options.transport}". Must have a valid transport protocol: "{tcp, udp}"`)
703-
}
704-
705-
return {
706-
family: options.family,
707-
address: options.host,
708-
port: options.port
709-
}
710-
}
711-
712-
isThinWaistAddress (addr?: Multiaddr): boolean {
713-
const protos = (addr ?? this).protos()
714-
715-
if (protos.length !== 2) {
716-
return false
717-
}
718-
719-
if (protos[0].code !== 4 && protos[0].code !== 41) {
720-
return false
721-
}
722-
if (protos[1].code !== 6 && protos[1].code !== 273) {
723-
return false
724-
}
725-
return true
726-
}
727-
728-
/**
729-
* Returns Multiaddr as a human-readable string
730-
* https://nodejs.org/api/util.html#utilinspectcustom
731-
*
732-
* @example
733-
* ```js
734-
* import { multiaddr } from '@multiformats/multiaddr'
735-
*
736-
* console.info(multiaddr('/ip4/127.0.0.1/tcp/4001'))
737-
* // 'Multiaddr(/ip4/127.0.0.1/tcp/4001)'
738-
* ```
739-
*/
740-
[inspect] (): string {
741-
return `Multiaddr(${this.#string})`
742-
}
743-
}
744-
745476
/**
746477
* A function that takes a {@link MultiaddrInput} and returns a {@link Multiaddr}
747478
*
@@ -756,7 +487,7 @@ class DefaultMultiaddr implements Multiaddr {
756487
* @param {MultiaddrInput} [addr] - If String or Uint8Array, needs to adhere to the address format of a [multiaddr](https://github.com/multiformats/multiaddr#string-format)
757488
*/
758489
export function multiaddr (addr?: MultiaddrInput): Multiaddr {
759-
return new DefaultMultiaddr(addr)
490+
return new MultiaddrClass(addr)
760491
}
761492

762493
export { getProtocol as protocols }

‎src/multiaddr.ts

+286
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
/**
2+
* @packageDocumentation
3+
*
4+
* An implementation of a Multiaddr in JavaScript
5+
*
6+
* @example
7+
*
8+
* ```js
9+
* import { multiaddr } from '@multiformats/multiaddr'
10+
*
11+
* const ma = multiaddr('/ip4/127.0.0.1/tcp/1234')
12+
* ```
13+
*/
14+
15+
import { CodeError } from '@libp2p/interface/errors'
16+
import { base58btc } from 'multiformats/bases/base58'
17+
import { CID } from 'multiformats/cid'
18+
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
19+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
20+
import { bytesToMultiaddrParts, stringToMultiaddrParts, type MultiaddrParts, tuplesToBytes } from './codec.js'
21+
import { getProtocol, names } from './protocols-table.js'
22+
import { isMultiaddr, type AbortOptions, type MultiaddrInput, type Multiaddr as MultiaddrInterface, type MultiaddrObject, type Protocol, type StringTuple, type Tuple, resolvers, type NodeAddress } from './index.js'
23+
24+
const inspect = Symbol.for('nodejs.util.inspect.custom')
25+
export const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr')
26+
27+
const DNS_CODES = [
28+
getProtocol('dns').code,
29+
getProtocol('dns4').code,
30+
getProtocol('dns6').code,
31+
getProtocol('dnsaddr').code
32+
]
33+
34+
/**
35+
* Creates a {@link Multiaddr} from a {@link MultiaddrInput}
36+
*/
37+
export class Multiaddr implements MultiaddrInterface {
38+
public bytes: Uint8Array
39+
#string: string
40+
#tuples: Tuple[]
41+
#stringTuples: StringTuple[]
42+
#path: string | null
43+
44+
[symbol]: boolean = true
45+
46+
constructor (addr?: MultiaddrInput) {
47+
// default
48+
if (addr == null) {
49+
addr = ''
50+
}
51+
52+
let parts: MultiaddrParts
53+
if (addr instanceof Uint8Array) {
54+
parts = bytesToMultiaddrParts(addr)
55+
} else if (typeof addr === 'string') {
56+
if (addr.length > 0 && addr.charAt(0) !== '/') {
57+
throw new Error(`multiaddr "${addr}" must start with a "/"`)
58+
}
59+
parts = stringToMultiaddrParts(addr)
60+
} else if (isMultiaddr(addr)) { // Multiaddr
61+
parts = bytesToMultiaddrParts(addr.bytes)
62+
} else {
63+
throw new Error('addr must be a string, Buffer, or another Multiaddr')
64+
}
65+
66+
this.bytes = parts.bytes
67+
this.#string = parts.string
68+
this.#tuples = parts.tuples
69+
this.#stringTuples = parts.stringTuples
70+
this.#path = parts.path
71+
}
72+
73+
toString (): string {
74+
return this.#string
75+
}
76+
77+
toJSON (): string {
78+
return this.toString()
79+
}
80+
81+
toOptions (): MultiaddrObject {
82+
let family: 4 | 6 | undefined
83+
let transport: string | undefined
84+
let host: string | undefined
85+
let port: number | undefined
86+
let zone = ''
87+
88+
const tcp = getProtocol('tcp')
89+
const udp = getProtocol('udp')
90+
const ip4 = getProtocol('ip4')
91+
const ip6 = getProtocol('ip6')
92+
const dns6 = getProtocol('dns6')
93+
const ip6zone = getProtocol('ip6zone')
94+
95+
for (const [code, value] of this.stringTuples()) {
96+
if (code === ip6zone.code) {
97+
zone = `%${value ?? ''}`
98+
}
99+
100+
// default to https when protocol & port are omitted from DNS addrs
101+
if (DNS_CODES.includes(code)) {
102+
transport = tcp.name
103+
port = 443
104+
host = `${value ?? ''}${zone}`
105+
family = code === dns6.code ? 6 : 4
106+
}
107+
108+
if (code === tcp.code || code === udp.code) {
109+
transport = getProtocol(code).name
110+
port = parseInt(value ?? '')
111+
}
112+
113+
if (code === ip4.code || code === ip6.code) {
114+
transport = getProtocol(code).name
115+
host = `${value ?? ''}${zone}`
116+
family = code === ip6.code ? 6 : 4
117+
}
118+
}
119+
120+
if (family == null || transport == null || host == null || port == null) {
121+
throw new Error('multiaddr must have a valid format: "/{ip4, ip6, dns4, dns6, dnsaddr}/{address}/{tcp, udp}/{port}".')
122+
}
123+
124+
const opts: MultiaddrObject = {
125+
family,
126+
host,
127+
transport,
128+
port
129+
}
130+
131+
return opts
132+
}
133+
134+
protos (): Protocol[] {
135+
return this.#tuples.map(([code]) => Object.assign({}, getProtocol(code)))
136+
}
137+
138+
protoCodes (): number[] {
139+
return this.#tuples.map(([code]) => code)
140+
}
141+
142+
protoNames (): string[] {
143+
return this.#tuples.map(([code]) => getProtocol(code).name)
144+
}
145+
146+
tuples (): Array<[number, Uint8Array?]> {
147+
return this.#tuples
148+
}
149+
150+
stringTuples (): Array<[number, string?]> {
151+
return this.#stringTuples
152+
}
153+
154+
encapsulate (addr: MultiaddrInput): Multiaddr {
155+
addr = new Multiaddr(addr)
156+
return new Multiaddr(this.toString() + addr.toString())
157+
}
158+
159+
decapsulate (addr: Multiaddr | string): Multiaddr {
160+
const addrString = addr.toString()
161+
const s = this.toString()
162+
const i = s.lastIndexOf(addrString)
163+
if (i < 0) {
164+
throw new Error(`Address ${this.toString()} does not contain subaddress: ${addr.toString()}`)
165+
}
166+
return new Multiaddr(s.slice(0, i))
167+
}
168+
169+
decapsulateCode (code: number): Multiaddr {
170+
const tuples = this.tuples()
171+
for (let i = tuples.length - 1; i >= 0; i--) {
172+
if (tuples[i][0] === code) {
173+
return new Multiaddr(tuplesToBytes(tuples.slice(0, i)))
174+
}
175+
}
176+
return this
177+
}
178+
179+
getPeerId (): string | null {
180+
try {
181+
let tuples: Array<[number, string | undefined]> = []
182+
183+
this.stringTuples().forEach(([code, name]) => {
184+
if (code === names.p2p.code) {
185+
tuples.push([code, name])
186+
}
187+
188+
// if this is a p2p-circuit address, return the target peer id if present
189+
// not the peer id of the relay
190+
if (code === names['p2p-circuit'].code) {
191+
tuples = []
192+
}
193+
})
194+
195+
// Get the last ipfs tuple ['p2p', 'peerid string']
196+
const tuple = tuples.pop()
197+
if (tuple?.[1] != null) {
198+
const peerIdStr = tuple[1]
199+
200+
// peer id is base58btc encoded string but not multibase encoded so add the `z`
201+
// prefix so we can validate that it is correctly encoded
202+
if (peerIdStr[0] === 'Q' || peerIdStr[0] === '1') {
203+
return uint8ArrayToString(base58btc.decode(`z${peerIdStr}`), 'base58btc')
204+
}
205+
206+
// try to parse peer id as CID
207+
return uint8ArrayToString(CID.parse(peerIdStr).multihash.bytes, 'base58btc')
208+
}
209+
210+
return null
211+
} catch (e) {
212+
return null
213+
}
214+
}
215+
216+
getPath (): string | null {
217+
return this.#path
218+
}
219+
220+
equals (addr: { bytes: Uint8Array }): boolean {
221+
return uint8ArrayEquals(this.bytes, addr.bytes)
222+
}
223+
224+
async resolve (options?: AbortOptions): Promise<Multiaddr[]> {
225+
const resolvableProto = this.protos().find((p) => p.resolvable)
226+
227+
// Multiaddr is not resolvable?
228+
if (resolvableProto == null) {
229+
return [this]
230+
}
231+
232+
const resolver = resolvers.get(resolvableProto.name)
233+
if (resolver == null) {
234+
throw new CodeError(`no available resolver for ${resolvableProto.name}`, 'ERR_NO_AVAILABLE_RESOLVER')
235+
}
236+
237+
const addresses = await resolver(this, options)
238+
return addresses.map((a) => new Multiaddr(a))
239+
}
240+
241+
nodeAddress (): NodeAddress {
242+
const options = this.toOptions()
243+
244+
if (options.transport !== 'tcp' && options.transport !== 'udp') {
245+
throw new Error(`multiaddr must have a valid format - no protocol with name: "${options.transport}". Must have a valid transport protocol: "{tcp, udp}"`)
246+
}
247+
248+
return {
249+
family: options.family,
250+
address: options.host,
251+
port: options.port
252+
}
253+
}
254+
255+
isThinWaistAddress (addr?: Multiaddr): boolean {
256+
const protos = (addr ?? this).protos()
257+
258+
if (protos.length !== 2) {
259+
return false
260+
}
261+
262+
if (protos[0].code !== 4 && protos[0].code !== 41) {
263+
return false
264+
}
265+
if (protos[1].code !== 6 && protos[1].code !== 273) {
266+
return false
267+
}
268+
return true
269+
}
270+
271+
/**
272+
* Returns Multiaddr as a human-readable string
273+
* https://nodejs.org/api/util.html#utilinspectcustom
274+
*
275+
* @example
276+
* ```js
277+
* import { multiaddr } from '@multiformats/multiaddr'
278+
*
279+
* console.info(multiaddr('/ip4/127.0.0.1/tcp/4001'))
280+
* // 'Multiaddr(/ip4/127.0.0.1/tcp/4001)'
281+
* ```
282+
*/
283+
[inspect] (): string {
284+
return `Multiaddr(${this.#string})`
285+
}
286+
}

0 commit comments

Comments
 (0)
Please sign in to comment.