Skip to content

Commit

Permalink
fix: use Multiaddr as class name (#352)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
achingbrain committed Nov 6, 2023
1 parent 21938b5 commit 35348cd
Show file tree
Hide file tree
Showing 2 changed files with 290 additions and 273 deletions.
277 changes: 4 additions & 273 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,8 @@
* ```
*/

import { CodeError } from '@libp2p/interface/errors'
import { base58btc } from 'multiformats/bases/base58'
import { CID } from 'multiformats/cid'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { bytesToMultiaddrParts, stringToMultiaddrParts, type MultiaddrParts, tuplesToBytes } from './codec.js'
import { getProtocol, names } from './protocols-table.js'

const inspect = Symbol.for('nodejs.util.inspect.custom')

const DNS_CODES = [
getProtocol('dns').code,
getProtocol('dns4').code,
getProtocol('dns6').code,
getProtocol('dnsaddr').code
]
import { Multiaddr as MultiaddrClass, symbol } from './multiaddr.js'
import { getProtocol } from './protocols-table.js'

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

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

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

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

/**
* Creates a {@link Multiaddr} from a {@link MultiaddrInput}
*/
class DefaultMultiaddr implements Multiaddr {
public bytes: Uint8Array
#string: string
#tuples: Tuple[]
#stringTuples: StringTuple[]
#path: string | null

[symbol]: boolean = true

constructor (addr?: MultiaddrInput) {
// default
if (addr == null) {
addr = ''
}

let parts: MultiaddrParts
if (addr instanceof Uint8Array) {
parts = bytesToMultiaddrParts(addr)
} else if (typeof addr === 'string') {
if (addr.length > 0 && addr.charAt(0) !== '/') {
throw new Error(`multiaddr "${addr}" must start with a "/"`)
}
parts = stringToMultiaddrParts(addr)
} else if (isMultiaddr(addr)) { // Multiaddr
parts = bytesToMultiaddrParts(addr.bytes)
} else {
throw new Error('addr must be a string, Buffer, or another Multiaddr')
}

this.bytes = parts.bytes
this.#string = parts.string
this.#tuples = parts.tuples
this.#stringTuples = parts.stringTuples
this.#path = parts.path
}

toString (): string {
return this.#string
}

toJSON (): string {
return this.toString()
}

toOptions (): MultiaddrObject {
let family: 4 | 6 | undefined
let transport: string | undefined
let host: string | undefined
let port: number | undefined
let zone = ''

const tcp = getProtocol('tcp')
const udp = getProtocol('udp')
const ip4 = getProtocol('ip4')
const ip6 = getProtocol('ip6')
const dns6 = getProtocol('dns6')
const ip6zone = getProtocol('ip6zone')

for (const [code, value] of this.stringTuples()) {
if (code === ip6zone.code) {
zone = `%${value ?? ''}`
}

// default to https when protocol & port are omitted from DNS addrs
if (DNS_CODES.includes(code)) {
transport = tcp.name
port = 443
host = `${value ?? ''}${zone}`
family = code === dns6.code ? 6 : 4
}

if (code === tcp.code || code === udp.code) {
transport = getProtocol(code).name
port = parseInt(value ?? '')
}

if (code === ip4.code || code === ip6.code) {
transport = getProtocol(code).name
host = `${value ?? ''}${zone}`
family = code === ip6.code ? 6 : 4
}
}

if (family == null || transport == null || host == null || port == null) {
throw new Error('multiaddr must have a valid format: "/{ip4, ip6, dns4, dns6, dnsaddr}/{address}/{tcp, udp}/{port}".')
}

const opts: MultiaddrObject = {
family,
host,
transport,
port
}

return opts
}

protos (): Protocol[] {
return this.#tuples.map(([code]) => Object.assign({}, getProtocol(code)))
}

protoCodes (): number[] {
return this.#tuples.map(([code]) => code)
}

protoNames (): string[] {
return this.#tuples.map(([code]) => getProtocol(code).name)
}

tuples (): Array<[number, Uint8Array?]> {
return this.#tuples
}

stringTuples (): Array<[number, string?]> {
return this.#stringTuples
}

encapsulate (addr: MultiaddrInput): Multiaddr {
addr = new DefaultMultiaddr(addr)
return new DefaultMultiaddr(this.toString() + addr.toString())
}

decapsulate (addr: Multiaddr | string): Multiaddr {
const addrString = addr.toString()
const s = this.toString()
const i = s.lastIndexOf(addrString)
if (i < 0) {
throw new Error(`Address ${this.toString()} does not contain subaddress: ${addr.toString()}`)
}
return new DefaultMultiaddr(s.slice(0, i))
}

decapsulateCode (code: number): Multiaddr {
const tuples = this.tuples()
for (let i = tuples.length - 1; i >= 0; i--) {
if (tuples[i][0] === code) {
return new DefaultMultiaddr(tuplesToBytes(tuples.slice(0, i)))
}
}
return this
}

getPeerId (): string | null {
try {
let tuples: Array<[number, string | undefined]> = []

this.stringTuples().forEach(([code, name]) => {
if (code === names.p2p.code) {
tuples.push([code, name])
}

// if this is a p2p-circuit address, return the target peer id if present
// not the peer id of the relay
if (code === names['p2p-circuit'].code) {
tuples = []
}
})

// Get the last ipfs tuple ['p2p', 'peerid string']
const tuple = tuples.pop()
if (tuple?.[1] != null) {
const peerIdStr = tuple[1]

// peer id is base58btc encoded string but not multibase encoded so add the `z`
// prefix so we can validate that it is correctly encoded
if (peerIdStr[0] === 'Q' || peerIdStr[0] === '1') {
return uint8ArrayToString(base58btc.decode(`z${peerIdStr}`), 'base58btc')
}

// try to parse peer id as CID
return uint8ArrayToString(CID.parse(peerIdStr).multihash.bytes, 'base58btc')
}

return null
} catch (e) {
return null
}
}

getPath (): string | null {
return this.#path
}

equals (addr: { bytes: Uint8Array }): boolean {
return uint8ArrayEquals(this.bytes, addr.bytes)
}

async resolve (options?: AbortOptions): Promise<Multiaddr[]> {
const resolvableProto = this.protos().find((p) => p.resolvable)

// Multiaddr is not resolvable?
if (resolvableProto == null) {
return [this]
}

const resolver = resolvers.get(resolvableProto.name)
if (resolver == null) {
throw new CodeError(`no available resolver for ${resolvableProto.name}`, 'ERR_NO_AVAILABLE_RESOLVER')
}

const addresses = await resolver(this, options)
return addresses.map((a) => new DefaultMultiaddr(a))
}

nodeAddress (): NodeAddress {
const options = this.toOptions()

if (options.transport !== 'tcp' && options.transport !== 'udp') {
throw new Error(`multiaddr must have a valid format - no protocol with name: "${options.transport}". Must have a valid transport protocol: "{tcp, udp}"`)
}

return {
family: options.family,
address: options.host,
port: options.port
}
}

isThinWaistAddress (addr?: Multiaddr): boolean {
const protos = (addr ?? this).protos()

if (protos.length !== 2) {
return false
}

if (protos[0].code !== 4 && protos[0].code !== 41) {
return false
}
if (protos[1].code !== 6 && protos[1].code !== 273) {
return false
}
return true
}

/**
* Returns Multiaddr as a human-readable string
* https://nodejs.org/api/util.html#utilinspectcustom
*
* @example
* ```js
* import { multiaddr } from '@multiformats/multiaddr'
*
* console.info(multiaddr('/ip4/127.0.0.1/tcp/4001'))
* // 'Multiaddr(/ip4/127.0.0.1/tcp/4001)'
* ```
*/
[inspect] (): string {
return `Multiaddr(${this.#string})`
}
}

/**
* A function that takes a {@link MultiaddrInput} and returns a {@link Multiaddr}
*
Expand All @@ -756,7 +487,7 @@ class DefaultMultiaddr implements Multiaddr {
* @param {MultiaddrInput} [addr] - If String or Uint8Array, needs to adhere to the address format of a [multiaddr](https://github.com/multiformats/multiaddr#string-format)
*/
export function multiaddr (addr?: MultiaddrInput): Multiaddr {
return new DefaultMultiaddr(addr)
return new MultiaddrClass(addr)
}

export { getProtocol as protocols }

0 comments on commit 35348cd

Please sign in to comment.