diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..b94c65e7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,67 @@ +name: ci +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: yarn lint + - run: yarn build + - uses: gozala/typescript-error-reporter-action@v1.0.4 + - run: yarn aegir dep-check -- -i aegir + - uses: ipfs/aegir/actions/bundle-size@master + name: size + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + test-node: + needs: check + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + node: [12, 14] + fail-fast: true + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + - run: yarn + - run: npx nyc --reporter=lcov npm run test:node -- --bail + - uses: codecov/codecov-action@v1 + test-chrome: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: yarn aegir test -t browser -t webworker + test-firefox: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: yarn aegir test -t browser -t webworker -- --browsers FirefoxHeadless + test-electron-main: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: npx xvfb-maybe yarn aegir test -t electron-main --bail + test-electron-renderer: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: npx xvfb-maybe yarn aegir test -t electron-renderer --bail \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 81758d88..00000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -language: node_js -cache: npm -stages: - - check - - test - - cov - -node_js: - - 'lts/*' - - 'stable' - -os: - - linux - - osx - -script: npx nyc -s npm run test:node -- --bail -after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov - -jobs: - include: - - os: windows - cache: false - - - stage: check - script: - - npx aegir dep-check - - npm run lint - - npm run test:types - - - stage: test - name: chrome - addons: - chrome: stable - script: npx aegir test -t browser -t webworker - - - stage: test - name: firefox - addons: - firefox: latest - script: npx aegir test -t browser -t webworker -- --browsers FirefoxHeadless - -notifications: - email: false - diff --git a/README.md b/README.md index cf037f0e..56b4f177 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,31 @@ -js-multiaddr +js-multiaddr ============ -[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) -[![](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)](https://github.com/multiformats/multiformats) -[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipfs) -[![Dependency Status](https://david-dm.org/multiformats/js-multiaddr.svg?style=flat-square)](https://david-dm.org/multiformats/js-multiaddr) -[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) -[![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) -[![](https://img.shields.io/travis/multiformats/js-multiaddr.svg?style=flat-square)](https://travis-ci.com/multiformats/js-multiaddr) +[![pl](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) +[![project](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)](https://github.com/multiformats/multiformats) +[![irc](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipfs) [![codecov](https://img.shields.io/codecov/c/github/multiformats/js-multiaddr.svg?style=flat-square)](https://codecov.io/gh/multiformats/js-multiaddr) +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/multiformats/js-multiaddr/ci?label=ci&style=flat-square)](https://github.com/multiformats/js-multiaddr/actions?query=branch%3Amaster+workflow%3Aci+) > JavaScript implementation of [multiaddr](https://github.com/multiformats/multiaddr). -## Lead Maintainer +## Lead Maintainer [Jacob Heun](https://github.com/jacobheun) -## Table of Contents - -- [js-multiaddr](#js-multiaddr) - - [Lead Maintainer](#lead-maintainer) - - [Table of Contents](#table-of-contents) - - [Background](#background) - - [What is multiaddr?](#what-is-multiaddr) - - [Install](#install) - - [Setup](#setup) - - [Node.js](#nodejs) - - [Browser: Browserify, Webpack, other bundlers](#browser-browserify-webpack-other-bundlers) - - [Browser: ` - - -``` diff --git a/package.json b/package.json index 637dae53..a58273e8 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,21 @@ "description": "multiaddr implementation (binary + string representation of network addresses)", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", - "types": "src/index.d.ts", + "types": "dist/src/index.d.ts", + "typesVersions": { + "*": { + "src/*": [ + "dist/src/*", + "dist/src/*/index" + ] + } + }, "scripts": { "lint": "aegir lint", - "test": "npm run test:node && npm run test:browser", - "test:node": "aegir test --ts -t node", + "test": "aegir test", + "test:node": "aegir test -t node", "test:browser": "aegir test -t browser", - "test:types": "npx tsc", + "test:types": "aegir ts -p check", "build": "aegir build", "release": "aegir release", "release-minor": "aegir release --type minor", @@ -42,16 +50,14 @@ "is-ip": "^3.1.0", "multibase": "^3.0.0", "uint8arrays": "^1.1.0", - "varint": "^5.0.0" + "varint": "^6.0.0" }, "devDependencies": { - "@types/chai": "^4.2.8", - "@types/dirty-chai": "^2.0.2", - "@types/mocha": "^8.0.1", - "@types/node": "^14.0.11", - "aegir": "^29.0.1", - "sinon": "^9.2.0", - "typescript": "^3.9.5" + "aegir": "^29.1.0", + "sinon": "^9.2.0" + }, + "eslintConfig": { + "extends": "ipfs" }, "contributors": [ "David Dias ", diff --git a/src/codec.js b/src/codec.js index 09bbfa0c..75a245d0 100644 --- a/src/codec.js +++ b/src/codec.js @@ -100,11 +100,18 @@ function stringTuplesToTuples (tuples) { }) } -// [[int code, Uint8Array]... ] -> [[str name, str addr]... ] +/** + * Convert tuples to string tuples + * + * [[int code, Uint8Array]... ] -> [[int code, str addr]... ] + * + * @param {Array<[number, Uint8Array?]>} tuples + * @returns {Array<[number, string?]>} + */ function tuplesToStringTuples (tuples) { return tuples.map(tup => { const proto = protoFromTuple(tup) - if (tup.length > 1) { + if (tup.length > 1 && tup[1]) { return [proto.code, convert.toString(proto.code, tup[1])] } return [proto.code] @@ -137,7 +144,14 @@ function sizeForAddr (p, addr) { } // Uint8Array -> [[int code, Uint8Array ]... ] + +/** + * + * @param {Uint8Array} buf + * @returns {Array<[number, Uint8Array?]>} + */ function bytesToTuples (buf) { + /** @type {Array<[number, Uint8Array?]>} */ const tuples = [] let i = 0 while (i < buf.length) { diff --git a/src/convert.js b/src/convert.js index 7258b50c..7af26a6f 100644 --- a/src/convert.js +++ b/src/convert.js @@ -20,9 +20,16 @@ function Convert (proto, a) { } } -Convert.toString = function convertToString (proto, buf) { - proto = protocols(proto) - switch (proto.code) { +/** + * Convert [code,Uint8Array] to string + * + * @param {number|string} proto + * @param {Uint8Array} buf + * @returns {string} + */ +Convert.toString = function (proto, buf) { + const protocol = protocols(proto) + switch (protocol.code) { case 4: // ipv4 case 41: // ipv6 return bytes2ip(buf) @@ -31,7 +38,7 @@ Convert.toString = function convertToString (proto, buf) { case 273: // udp case 33: // dccp case 132: // sctp - return bytes2port(buf) + return bytes2port(buf).toString() case 53: // dns case 54: // dns4 @@ -137,6 +144,12 @@ function mh2bytes (hash) { return uint8ArrayConcat([size, mh], size.length + mh.length) } +/** + * Converts bytes to bas58btc string + * + * @param {Uint8Array} buf + * @returns {string} bas58btc string + */ function bytes2mh (buf) { const size = varint.decode(buf) const address = buf.slice(varint.decode.bytes) diff --git a/src/index.d.ts b/src/index.d.ts deleted file mode 100644 index 0fca50e3..00000000 --- a/src/index.d.ts +++ /dev/null @@ -1,204 +0,0 @@ -declare type NetOptions = { - family: "ipv4" | "ipv6"; - host: string; - transport: "tcp" | "udp"; - port: number; -}; - -declare type Protocol = { - code: number; - size: number; - name: string; - resolvable: boolean | undefined; - path: boolean | undefined; -}; - -declare interface Protocols { - table: { - [index: number]: Protocol; - }; - names: { - [index: string]: Protocol; - }; - codes: { - [index: number]: Protocol; - }; - object( - code: number, - size: number, - name: string, - resolvable?: any, - path?: any - ): Protocol; -} - -declare type NodeAddress = { - family: "IPv4" | "IPv6"; - address: string; - port: string; -}; - -declare type MultiaddrInput = string | Uint8Array | Multiaddr | null; - -declare class Multiaddr { - /** - * Creates a [multiaddr](https://github.com/multiformats/multiaddr) from - * a Uint8Array, String or another Multiaddr instance - * public key. - * @param addr - If String or Uint8Array, needs to adhere - * to the address format of a [multiaddr](https://github.com/multiformats/multiaddr#string-format) - */ - constructor(addr?: MultiaddrInput); - - bytes: Uint8Array; - - /** - * Returns Multiaddr as a String - */ - toString(): string; - - /** - * Returns Multiaddr as a JSON encoded object - */ - toJSON(): string; - - /** - * Returns Multiaddr as a convinient options object to be used with net.createConnection - */ - toOptions(): NetOptions; - - /** - * Returns Multiaddr as a human-readable string - */ - inspect(): string; - - /** - * Returns the protocols the Multiaddr is defined with, as an array of objects, in - * left-to-right order. Each object contains the protocol code, protocol name, - * and the size of its address space in bits. - * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv) - */ - protos(): Protocol[]; - - /** - * Returns the codes of the protocols in left-to-right order. - * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv) - */ - protoCodes(): number[]; - - /** - * Returns the names of the protocols in left-to-right order. - * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv) - */ - protoNames(): string[]; - - /** - * Returns a tuple of parts - */ - tuples(): [number, Uint8Array][]; - - /** - * Returns a tuple of string/number parts - */ - stringTuples(): [number, string | number][]; - - /** - * Encapsulates a Multiaddr in another Multiaddr - */ - encapsulate(addr: MultiaddrInput): Multiaddr; - - /** - * Decapsulates a Multiaddr from another Multiaddr - */ - decapsulate(addr: MultiaddrInput): Multiaddr; - - /** - * A more reliable version of `decapsulate` if you are targeting a - * specific code, such as 421 (the `p2p` protocol code). The last index of the code - * will be removed from the `Multiaddr`, and a new instance will be returned. - * If the code is not present, the original `Multiaddr` is returned. - */ - decapsulateCode(code: number): Multiaddr; - - /** - * Extract the peerId if the multiaddr contains one - */ - getPeerId(): string; - - /** - * Extract the path if the multiaddr contains one - */ - getPath(): string | null; - - /** - * Checks if two Multiaddrs are the same - */ - equals(addr: Multiaddr): boolean; - - /** - * Gets a Multiaddrs node-friendly address object. Note that protocol information - * is left out: in Node (and most network systems) the protocol is unknowable - * given only the address. - * - * Has to be a ThinWaist Address, otherwise throws error - */ - nodeAddress(): NodeAddress; - - /** - * Returns if a Multiaddr is a Thin Waist address or not. - * - * Thin Waist is if a Multiaddr adheres to the standard combination of: - * - * `{IPv4, IPv6}/{TCP, UDP}` - */ - isThinWaistAddress(addr?: Multiaddr): boolean; - - /** - * Resolve multiaddr if containing resolvable hostname. - */ - resolve(): Promise> -} - -declare namespace Multiaddr { - const resolvers: Map < string, (addr: Multiaddr) => Promise < Array < string >>> - - /** - * Creates a Multiaddr from a node-friendly address object - */ - function fromNodeAddress(addr: NodeAddress, transport: string): Multiaddr; - - /** - * Object containing table, names and codes of all supported protocols. - * To get the protocol values from a Multiaddr, you can use - * [`.protos()`](#multiaddrprotos), - * [`.protoCodes()`](#multiaddrprotocodes) or - * [`.protoNames()`](#multiaddrprotonames) - */ - const protocols: Protocols; - - /** - * Returns if something is a Multiaddr - */ - function isMultiaddr(addr: unknown): addr is Multiaddr; - - /** - * Returns if something is a Multiaddr that is a name - */ - function isName(addr: Multiaddr): boolean; - - /** - * Returns an array of multiaddrs, by resolving the multiaddr that is a name - */ - function resolve(addr: Multiaddr): Promise; -} - -/** - * Creates a [multiaddr](https://github.com/multiformats/multiaddr) from - * a Uint8Array, String or another Multiaddr instance - * public key. - * @param addr - If String or Uint8Array, needs to adhere - * to the address format of a [multiaddr](https://github.com/multiformats/multiaddr#string-format) - */ -declare function Multiaddr(input?: MultiaddrInput): Multiaddr; - -export = Multiaddr; diff --git a/src/index.js b/src/index.js index fc464bd6..1450b411 100644 --- a/src/index.js +++ b/src/index.js @@ -4,556 +4,582 @@ const codec = require('./codec') const protocols = require('./protocols-table') const varint = require('varint') const CID = require('cids') -const withIs = require('class-is') const errCode = require('err-code') -const inspect = Symbol.for('nodejs.util.inspect.custom') const uint8ArrayToString = require('uint8arrays/to-string') const uint8ArrayEquals = require('uint8arrays/equals') const resolvers = new Map() +const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr') + +/** + * @typedef {import('./types').Protocol} Protocol + */ /** * Creates a [multiaddr](https://github.com/multiformats/multiaddr) from * a Uint8Array, String or another Multiaddr instance * public key. - * - * @class Multiaddr - * @param {(string | Uint8Array | Multiaddr)} addr - If String or Uint8Array, needs to adhere - * to the address format of a [multiaddr](https://github.com/multiformats/multiaddr#string-format) - * @example - * Multiaddr('/ip4/127.0.0.1/tcp/4001') - * // */ -const Multiaddr = withIs.proto(function (addr) { - if (!(this instanceof Multiaddr)) { - return new Multiaddr(addr) - } +class Multiaddr { + /** + * @param {string | Uint8Array | Multiaddr | null} [addr] - If String or Uint8Array, needs to adhere + * to the address format of a [multiaddr](https://github.com/multiformats/multiaddr#string-format) + * @example + * ```js + * new Multiaddr('/ip4/127.0.0.1/tcp/4001') + * // + * ``` + */ + constructor (addr) { + Object.defineProperty(this, symbol, { value: true }) - // default - if (addr == null) { - addr = '' - } + // default + if (addr == null) { + addr = '' + } - if (addr instanceof Uint8Array) { - /** - * @type {Uint8Array} - The raw bytes representing this multiaddress - */ - this.bytes = codec.fromBytes(addr) - } else if (typeof addr === 'string' || addr instanceof String) { - if (addr.length > 0 && addr.charAt(0) !== '/') { - throw new Error(`multiaddr "${addr}" must start with a "/"`) + if (addr instanceof Uint8Array) { + /** + * @type {Uint8Array} - The raw bytes representing this multiaddress + */ + this.bytes = codec.fromBytes(addr) + } else if (typeof addr === 'string' || addr instanceof String) { + if (addr.length > 0 && addr.charAt(0) !== '/') { + throw new Error(`multiaddr "${addr}" must start with a "/"`) + } + this.bytes = codec.fromString(addr) + } else if (addr.bytes && addr.protos && addr.protoCodes) { // Multiaddr + this.bytes = codec.fromBytes(addr.bytes) // validate + copy buffer + } else { + throw new Error('addr must be a string, Buffer, or another Multiaddr') } - this.bytes = codec.fromString(addr) - } else if (addr.bytes && addr.protos && addr.protoCodes) { // Multiaddr - this.bytes = codec.fromBytes(addr.bytes) // validate + copy buffer - } else { - throw new Error('addr must be a string, Buffer, or another Multiaddr') } -}, { className: 'Multiaddr', symbolName: '@multiformats/js-multiaddr/multiaddr' }) -/** - * Returns Multiaddr as a String - * - * @returns {string} - * @example - * Multiaddr('/ip4/127.0.0.1/tcp/4001').toString() - * // '/ip4/127.0.0.1/tcp/4001' - */ -Multiaddr.prototype.toString = function toString () { - return codec.bytesToString(this.bytes) -} - -/** - * Returns Multiaddr as a JSON encoded object - * - * @returns {string} - * @example - * JSON.stringify(Multiaddr('/ip4/127.0.0.1/tcp/4001')) - * // '/ip4/127.0.0.1/tcp/4001' - */ -Multiaddr.prototype.toJSON = Multiaddr.prototype.toString - -/** - * Returns Multiaddr as a convinient options object to be used with net.createConnection - * - * @returns {{family: string, host: string, transport: string, port: number}} - * @example - * Multiaddr('/ip4/127.0.0.1/tcp/4001').toOptions() - * // { family: 'ipv4', host: '127.0.0.1', transport: 'tcp', port: 4001 } - */ -Multiaddr.prototype.toOptions = function toOptions () { - const opts = {} - const parsed = this.toString().split('/') - opts.family = parsed[1] === 'ip4' ? 'ipv4' : 'ipv6' - opts.host = parsed[2] - opts.transport = parsed[3] - opts.port = parseInt(parsed[4]) - return opts -} + /** + * Returns Multiaddr as a String + * + * @returns {string} + * @example + * ```js + * new Multiaddr('/ip4/127.0.0.1/tcp/4001').toString() + * // '/ip4/127.0.0.1/tcp/4001' + * ``` + */ + toString () { + return codec.bytesToString(this.bytes) + } -/** - * Returns Multiaddr as a human-readable string. - * For post Node.js v10.0.0. - * https://nodejs.org/api/deprecations.html#deprecations_dep0079_custom_inspection_function_on_objects_via_inspect - * - * @returns {string} - * @example - * console.log(Multiaddr('/ip4/127.0.0.1/tcp/4001')) - * // '' - */ -Multiaddr.prototype[inspect] = function inspectCustom () { - return '' -} + /** + * Returns Multiaddr as a JSON encoded object + * + * @returns {string} + * @example + * ```js + * JSON.stringify(new Multiaddr('/ip4/127.0.0.1/tcp/4001')) + * // '/ip4/127.0.0.1/tcp/4001' + * ``` + */ + toJSON () { + return this.toString() + } -/** - * Returns Multiaddr as a human-readable string. - * Fallback for pre Node.js v10.0.0. - * https://nodejs.org/api/deprecations.html#deprecations_dep0079_custom_inspection_function_on_objects_via_inspect - * - * @returns {string} - * @example - * Multiaddr('/ip4/127.0.0.1/tcp/4001').inspect() - * // '' - */ -Multiaddr.prototype.inspect = function inspect () { - return '' -} + /** + * Returns Multiaddr as a convinient options object to be used with net.createConnection + * + * @returns {{family: string, host: string, transport: string, port: number}} + * @example + * ```js + * new Multiaddr('/ip4/127.0.0.1/tcp/4001').toOptions() + * // { family: 'ipv4', host: '127.0.0.1', transport: 'tcp', port: 4001 } + * ``` + */ + toOptions () { + const opts = {} + const parsed = this.toString().split('/') + opts.family = parsed[1] === 'ip4' ? 'ipv4' : 'ipv6' + opts.host = parsed[2] + opts.transport = parsed[3] + opts.port = parseInt(parsed[4]) + return opts + } -/** - * @typedef {object} protocol - * @property {number} code - * @property {number} size - * @property {string} name - * @property {boolean} [resolvable] - * @property {boolean} [path] - */ + /** + * Returns Multiaddr as a human-readable string. + * Fallback for pre Node.js v10.0.0. + * https://nodejs.org/api/deprecations.html#deprecations_dep0079_custom_inspection_function_on_objects_via_inspect + * + * @returns {string} + * @example + * ```js + * new Multiaddr('/ip4/127.0.0.1/tcp/4001').inspect() + * // '' + * ``` + */ + [Symbol.for('nodejs.util.inspect.custom')] () { + return '' + } -/** - * Returns the protocols the Multiaddr is defined with, as an array of objects, in - * left-to-right order. Each object contains the protocol code, protocol name, - * and the size of its address space in bits. - * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv) - * - * @returns {protocol[]} protocols - All the protocols the address is composed of - * @example - * Multiaddr('/ip4/127.0.0.1/tcp/4001').protos() - * // [ { code: 4, size: 32, name: 'ip4' }, - * // { code: 6, size: 16, name: 'tcp' } ] - */ -Multiaddr.prototype.protos = function protos () { - return this.protoCodes().map(code => Object.assign({}, protocols(code))) -} + inspect () { + return '' + } -/** - * Returns the codes of the protocols in left-to-right order. - * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv) - * - * @returns {Array} protocol codes - * @example - * Multiaddr('/ip4/127.0.0.1/tcp/4001').protoCodes() - * // [ 4, 6 ] - */ -Multiaddr.prototype.protoCodes = function protoCodes () { - const codes = [] - const buf = this.bytes - let i = 0 - while (i < buf.length) { - const code = varint.decode(buf, i) - const n = varint.decode.bytes - - const p = protocols(code) - const size = codec.sizeForAddr(p, buf.slice(i + n)) - - i += (size + n) - codes.push(code) + /** + * Returns the protocols the Multiaddr is defined with, as an array of objects, in + * left-to-right order. Each object contains the protocol code, protocol name, + * and the size of its address space in bits. + * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv) + * + * @returns {Protocol[]} protocols - All the protocols the address is composed of + * @example + * ```js + * new Multiaddr('/ip4/127.0.0.1/tcp/4001').protos() + * // [ { code: 4, size: 32, name: 'ip4' }, + * // { code: 6, size: 16, name: 'tcp' } ] + * ``` + */ + protos () { + return this.protoCodes().map(code => Object.assign({}, protocols(code))) } - return codes -} + /** + * Returns the codes of the protocols in left-to-right order. + * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv) + * + * @returns {Array} protocol codes + * @example + * ```js + * new Multiaddr('/ip4/127.0.0.1/tcp/4001').protoCodes() + * // [ 4, 6 ] + * ``` + */ + protoCodes () { + const codes = [] + const buf = this.bytes + let i = 0 + while (i < buf.length) { + const code = varint.decode(buf, i) + const n = varint.decode.bytes + + const p = protocols(code) + const size = codec.sizeForAddr(p, buf.slice(i + n)) + + i += (size + n) + codes.push(code) + } -/** - * Returns the names of the protocols in left-to-right order. - * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv) - * - * @returns {Array.} protocol names - * @example - * Multiaddr('/ip4/127.0.0.1/tcp/4001').protoNames() - * // [ 'ip4', 'tcp' ] - */ -Multiaddr.prototype.protoNames = function protoNames () { - return this.protos().map(proto => proto.name) -} + return codes + } -/** - * Returns a tuple of parts - * - * @returns {[number, Uint8Array][]} tuples - * @example - * Multiaddr("/ip4/127.0.0.1/tcp/4001").tuples() - * // [ [ 4, ], [ 6, ] ] - */ -Multiaddr.prototype.tuples = function tuples () { - return codec.bytesToTuples(this.bytes) -} + /** + * Returns the names of the protocols in left-to-right order. + * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv) + * + * @returns {Array.} protocol names + * @example + * ```js + * new Multiaddr('/ip4/127.0.0.1/tcp/4001').protoNames() + * // [ 'ip4', 'tcp' ] + * ``` + */ + protoNames () { + return this.protos().map(proto => proto.name) + } -/** - * Returns a tuple of string/number parts - * - tuples[][0] = code of protocol - * - tuples[][1] = contents of address - * - * @returns {[number, string|number][]} tuples - * @example - * Multiaddr("/ip4/127.0.0.1/tcp/4001").stringTuples() - * // [ [ 4, '127.0.0.1' ], [ 6, 4001 ] ] - */ -Multiaddr.prototype.stringTuples = function stringTuples () { - const t = codec.bytesToTuples(this.bytes) - return codec.tuplesToStringTuples(t) -} + /** + * Returns a tuple of parts + * + * @returns {[number, (Uint8Array | undefined)?][]} tuples + * @example + * ```js + * new Multiaddr("/ip4/127.0.0.1/tcp/4001").tuples() + * // [ [ 4, ], [ 6, ] ] + * ``` + */ + tuples () { + return codec.bytesToTuples(this.bytes) + } -/** - * Encapsulates a Multiaddr in another Multiaddr - * - * @param {Multiaddr} addr - Multiaddr to add into this Multiaddr - * @returns {Multiaddr} - * @example - * const mh1 = Multiaddr('/ip4/8.8.8.8/tcp/1080') - * // - * - * const mh2 = Multiaddr('/ip4/127.0.0.1/tcp/4001') - * // - * - * const mh3 = mh1.encapsulate(mh2) - * // - * - * mh3.toString() - * // '/ip4/8.8.8.8/tcp/1080/ip4/127.0.0.1/tcp/4001' - */ -Multiaddr.prototype.encapsulate = function encapsulate (addr) { - addr = Multiaddr(addr) - return Multiaddr(this.toString() + addr.toString()) -} + /** + * Returns a tuple of string/number parts + * - tuples[][0] = code of protocol + * - tuples[][1] = contents of address + * + * @returns {[number, string?][]} tuples + * @example + * ```js + * new Multiaddr("/ip4/127.0.0.1/tcp/4001").stringTuples() + * // [ [ 4, '127.0.0.1' ], [ 6, '4001' ] ] + * ``` + */ + stringTuples () { + const t = codec.bytesToTuples(this.bytes) + return codec.tuplesToStringTuples(t) + } -/** - * Decapsulates a Multiaddr from another Multiaddr - * - * @param {Multiaddr} addr - Multiaddr to remove from this Multiaddr - * @returns {Multiaddr} - * @example - * const mh1 = Multiaddr('/ip4/8.8.8.8/tcp/1080') - * // - * - * const mh2 = Multiaddr('/ip4/127.0.0.1/tcp/4001') - * // - * - * const mh3 = mh1.encapsulate(mh2) - * // - * - * mh3.decapsulate(mh2).toString() - * // '/ip4/8.8.8.8/tcp/1080' - */ -Multiaddr.prototype.decapsulate = function decapsulate (addr) { - addr = addr.toString() - const s = this.toString() - const i = s.lastIndexOf(addr) - if (i < 0) { - throw new Error('Address ' + this + ' does not contain subaddress: ' + addr) + /** + * Encapsulates a Multiaddr in another Multiaddr + * + * @param {Multiaddr} addr - Multiaddr to add into this Multiaddr + * @returns {Multiaddr} + * @example + * ```js + * const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080') + * // + * + * const mh2 = new Multiaddr('/ip4/127.0.0.1/tcp/4001') + * // + * + * const mh3 = mh1.encapsulate(mh2) + * // + * + * mh3.toString() + * // '/ip4/8.8.8.8/tcp/1080/ip4/127.0.0.1/tcp/4001' + * ``` + */ + encapsulate (addr) { + addr = new Multiaddr(addr) + return new Multiaddr(this.toString() + addr.toString()) } - return Multiaddr(s.slice(0, i)) -} -/** - * A more reliable version of `decapsulate` if you are targeting a - * specific code, such as 421 (the `p2p` protocol code). The last index of the code - * will be removed from the `Multiaddr`, and a new instance will be returned. - * If the code is not present, the original `Multiaddr` is returned. - * - * @param {number} code - The code of the protocol to decapsulate from this Multiaddr - * @returns {Multiaddr} - * @example - * const addr = Multiaddr('/ip4/0.0.0.0/tcp/8080/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC') - * // - * - * addr.decapsulateCode(421).toString() - * // '/ip4/0.0.0.0/tcp/8080' - * - * Multiaddr('/ip4/127.0.0.1/tcp/8080').decapsulateCode(421).toString() - * // '/ip4/127.0.0.1/tcp/8080' - */ -Multiaddr.prototype.decapsulateCode = function decapsulateCode (code) { - const tuples = this.tuples() - for (let i = tuples.length - 1; i >= 0; i--) { - if (tuples[i][0] === code) { - return Multiaddr(codec.tuplesToBytes(tuples.slice(0, i))) + /** + * Decapsulates a Multiaddr from another Multiaddr + * + * @param {Multiaddr} addr - Multiaddr to remove from this Multiaddr + * @returns {Multiaddr} + * @example + * ```js + * const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080') + * // + * + * const mh2 = new Multiaddr('/ip4/127.0.0.1/tcp/4001') + * // + * + * const mh3 = mh1.encapsulate(mh2) + * // + * + * mh3.decapsulate(mh2).toString() + * // '/ip4/8.8.8.8/tcp/1080' + * ``` + */ + decapsulate (addr) { + const addrString = addr.toString() + const s = this.toString() + const i = s.lastIndexOf(addrString) + if (i < 0) { + throw new Error('Address ' + this + ' does not contain subaddress: ' + addrString) } + return new Multiaddr(s.slice(0, i)) } - return this -} -/** - * Extract the peerId if the multiaddr contains one - * - * @returns {string | null} peerId - The id of the peer or null if invalid or missing from the ma - * @example - * const mh1 = Multiaddr('/ip4/8.8.8.8/tcp/1080/ipfs/QmValidBase58string') - * // - * - * // should return QmValidBase58string or null if the id is missing or invalid - * const peerId = mh1.getPeerId() - */ -Multiaddr.prototype.getPeerId = function getPeerId () { - let b58str = null - try { - const tuples = this.stringTuples().filter((tuple) => { - if (tuple[0] === protocols.names.ipfs.code) { - return true + /** + * A more reliable version of `decapsulate` if you are targeting a + * specific code, such as 421 (the `p2p` protocol code). The last index of the code + * will be removed from the `Multiaddr`, and a new instance will be returned. + * If the code is not present, the original `Multiaddr` is returned. + * + * @param {number} code - The code of the protocol to decapsulate from this Multiaddr + * @returns {Multiaddr} + * @example + * ```js + * const addr = new Multiaddr('/ip4/0.0.0.0/tcp/8080/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC') + * // + * + * addr.decapsulateCode(421).toString() + * // '/ip4/0.0.0.0/tcp/8080' + * + * new Multiaddr('/ip4/127.0.0.1/tcp/8080').decapsulateCode(421).toString() + * // '/ip4/127.0.0.1/tcp/8080' + * ``` + */ + decapsulateCode (code) { + const tuples = this.tuples() + for (let i = tuples.length - 1; i >= 0; i--) { + if (tuples[i][0] === code) { + return new Multiaddr(codec.tuplesToBytes(tuples.slice(0, i))) } - }) - - // Get the last id - b58str = tuples.pop()[1] - // Get multihash, unwrap from CID if needed - b58str = uint8ArrayToString(new CID(b58str).multihash, 'base58btc') - } catch (e) { - b58str = null + } + return this } - return b58str -} - -/** - * Extract the path if the multiaddr contains one - * - * @returns {string | null} path - The path of the multiaddr, or null if no path protocol is present - * @example - * const mh1 = Multiaddr('/ip4/8.8.8.8/tcp/1080/unix/tmp/p2p.sock') - * // - * - * // should return utf8 string or null if the id is missing or invalid - * const path = mh1.getPath() - */ -Multiaddr.prototype.getPath = function getPath () { - let path = null - try { - path = this.stringTuples().filter((tuple) => { - const proto = protocols(tuple[0]) - if (proto.path) { - return true + /** + * Extract the peerId if the multiaddr contains one + * + * @returns {string | null} peerId - The id of the peer or null if invalid or missing from the ma + * @example + * ```js + * const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080/ipfs/QmValidBase58string') + * // + * + * // should return QmValidBase58string or null if the id is missing or invalid + * const peerId = mh1.getPeerId() + * ``` + */ + getPeerId () { + try { + const tuples = this.stringTuples().filter((tuple) => { + if (tuple[0] === protocols.names.ipfs.code) { + return true + } + }) + + // Get the last ipfs tuple ['ipfs', 'peerid string'] + const tuple = tuples.pop() + if (tuple && tuple[1]) { + // Get multihash, unwrap from CID if needed + return uint8ArrayToString(new CID(tuple[1]).multihash, 'base58btc') + } else { + return null } - })[0][1] - } catch (e) { - path = null + } catch (e) { + return null + } } - return path -} - -/** - * Checks if two Multiaddrs are the same - * - * @param {Multiaddr} addr - * @returns {Bool} - * @example - * const mh1 = Multiaddr('/ip4/8.8.8.8/tcp/1080') - * // - * - * const mh2 = Multiaddr('/ip4/127.0.0.1/tcp/4001') - * // - * - * mh1.equals(mh1) - * // true - * - * mh1.equals(mh2) - * // false - */ -Multiaddr.prototype.equals = function equals (addr) { - return uint8ArrayEquals(this.bytes, addr.bytes) -} - -/** - * Resolve multiaddr if containing resolvable hostname. - * - * @returns {Promise>} - * @example - * Multiaddr.resolvers.set('dnsaddr', resolverFunction) - * const mh1 = Multiaddr('/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb') - * const resolvedMultiaddrs = await mh1.resolve() - * // [ - * // , - * // , - * // - * // ] - */ -Multiaddr.prototype.resolve = async function resolve () { - const resolvableProto = this.protos().find((p) => p.resolvable) + /** + * Extract the path if the multiaddr contains one + * + * @returns {string | null} path - The path of the multiaddr, or null if no path protocol is present + * @example + * ```js + * const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080/unix/tmp/p2p.sock') + * // + * + * // should return utf8 string or null if the id is missing or invalid + * const path = mh1.getPath() + * ``` + */ + getPath () { + let path = null + try { + path = this.stringTuples().filter((tuple) => { + const proto = protocols(tuple[0]) + if (proto.path) { + return true + } + })[0][1] + + if (!path) { + path = null + } + } catch (e) { + path = null + } - // Multiaddr is not resolvable? - if (!resolvableProto) { - return [this] + return path } - const resolver = resolvers.get(resolvableProto.name) - if (!resolver) { - throw errCode(new Error(`no available resolver for ${resolvableProto.name}`), 'ERR_NO_AVAILABLE_RESOLVER') + /** + * Checks if two Multiaddrs are the same + * + * @param {Multiaddr} addr + * @returns {boolean} + * @example + * ```js + * const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080') + * // + * + * const mh2 = new Multiaddr('/ip4/127.0.0.1/tcp/4001') + * // + * + * mh1.equals(mh1) + * // true + * + * mh1.equals(mh2) + * // false + * ``` + */ + equals (addr) { + return uint8ArrayEquals(this.bytes, addr.bytes) } - const addresses = await resolver(this) - return addresses.map(a => Multiaddr(a)) -} + /** + * Resolve multiaddr if containing resolvable hostname. + * + * @returns {Promise>} + * @example + * ```js + * Multiaddr.resolvers.set('dnsaddr', resolverFunction) + * const mh1 = new Multiaddr('/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb') + * const resolvedMultiaddrs = await mh1.resolve() + * // [ + * // , + * // , + * // + * // ] + * ``` + */ + async resolve () { + const resolvableProto = this.protos().find((p) => p.resolvable) -/** - * Gets a Multiaddrs node-friendly address object. Note that protocol information - * is left out: in Node (and most network systems) the protocol is unknowable - * given only the address. - * - * Has to be a ThinWaist Address, otherwise throws error - * - * @returns {{family: string, address: string, port: number}} - * @throws {Error} Throws error if Multiaddr is not a Thin Waist address - * @example - * Multiaddr('/ip4/127.0.0.1/tcp/4001').nodeAddress() - * // {family: 'IPv4', address: '127.0.0.1', port: '4001'} - */ -Multiaddr.prototype.nodeAddress = function nodeAddress () { - const codes = this.protoCodes() - const names = this.protoNames() - const parts = this.toString().split('/').slice(1) - - if (parts.length < 4) { - throw new Error('multiaddr must have a valid format: "/{ip4, ip6, dns4, dns6}/{address}/{tcp, udp}/{port}".') - } else if (codes[0] !== 4 && codes[0] !== 41 && codes[0] !== 54 && codes[0] !== 55) { - throw new Error(`no protocol with name: "'${names[0]}'". Must have a valid family name: "{ip4, ip6, dns4, dns6}".`) - } else if (parts[2] !== 'tcp' && parts[2] !== 'udp') { - throw new Error(`no protocol with name: "'${names[1]}'". Must have a valid transport protocol: "{tcp, udp}".`) - } + // Multiaddr is not resolvable? + if (!resolvableProto) { + return [this] + } - return { - family: (codes[0] === 41 || codes[0] === 55) ? 6 : 4, - address: parts[1], // ip addr - port: parseInt(parts[3]) // tcp or udp port - } -} + const resolver = resolvers.get(resolvableProto.name) + if (!resolver) { + throw errCode(new Error(`no available resolver for ${resolvableProto.name}`), 'ERR_NO_AVAILABLE_RESOLVER') + } -/** - * Creates a Multiaddr from a node-friendly address object - * - * @param {{family: string, address: string, port: number}} addr - * @param {string} transport - * @returns {Multiaddr} multiaddr - * @throws {Error} Throws error if addr is not truthy - * @throws {Error} Throws error if transport is not truthy - * @example - * Multiaddr.fromNodeAddress({address: '127.0.0.1', port: '4001'}, 'tcp') - * // - */ -Multiaddr.fromNodeAddress = function fromNodeAddress (addr, transport) { - if (!addr) throw new Error('requires node address object') - if (!transport) throw new Error('requires transport protocol') - let ip - switch (addr.family) { - case 'IPv4': - ip = 'ip4' - break - case 'IPv6': - ip = 'ip6' - break - default: - throw Error(`Invalid addr family. Got '${addr.family}' instead of 'IPv4' or 'IPv6'`) + const addresses = await resolver(this) + return addresses.map(a => new Multiaddr(a)) } - return Multiaddr('/' + [ip, addr.address, transport, addr.port].join('/')) -} -// TODO find a better example, not sure about it's good enough -/** - * Returns if a Multiaddr is a Thin Waist address or not. - * - * Thin Waist is if a Multiaddr adheres to the standard combination of: - * - * `{IPv4, IPv6}/{TCP, UDP}` - * - * @param {Multiaddr} [addr] - Defaults to using `this` instance - * @returns {boolean} isThinWaistAddress - * @example - * const mh1 = Multiaddr('/ip4/127.0.0.1/tcp/4001') - * // - * const mh2 = Multiaddr('/ip4/192.168.2.1/tcp/5001') - * // - * const mh3 = mh1.encapsulate(mh2) - * // - * mh1.isThinWaistAddress() - * // true - * mh2.isThinWaistAddress() - * // true - * mh3.isThinWaistAddress() - * // false - */ -Multiaddr.prototype.isThinWaistAddress = function isThinWaistAddress (addr) { - const protos = (addr || this).protos() + /** + * Gets a Multiaddrs node-friendly address object. Note that protocol information + * is left out: in Node (and most network systems) the protocol is unknowable + * given only the address. + * + * Has to be a ThinWaist Address, otherwise throws error + * + * @returns {{family: number, address: string, port: number}} + * @throws {Error} Throws error if Multiaddr is not a Thin Waist address + * @example + * + * ```js + * new Multiaddr('/ip4/127.0.0.1/tcp/4001').nodeAddress() + * // {family: 4, address: '127.0.0.1', port: '4001'} + * ``` + * + */ + nodeAddress () { + const codes = this.protoCodes() + const names = this.protoNames() + const parts = this.toString().split('/').slice(1) + + if (parts.length < 4) { + throw new Error('multiaddr must have a valid format: "/{ip4, ip6, dns4, dns6}/{address}/{tcp, udp}/{port}".') + } else if (codes[0] !== 4 && codes[0] !== 41 && codes[0] !== 54 && codes[0] !== 55) { + throw new Error(`no protocol with name: "'${names[0]}'". Must have a valid family name: "{ip4, ip6, dns4, dns6}".`) + } else if (parts[2] !== 'tcp' && parts[2] !== 'udp') { + throw new Error(`no protocol with name: "'${names[1]}'". Must have a valid transport protocol: "{tcp, udp}".`) + } - if (protos.length !== 2) { - return false + return { + family: (codes[0] === 41 || codes[0] === 55) ? 6 : 4, + address: parts[1], + port: parseInt(parts[3]) // tcp or udp port + } } - if (protos[0].code !== 4 && protos[0].code !== 41) { - return false + /** + * Returns if a Multiaddr is a Thin Waist address or not. + * + * Thin Waist is if a Multiaddr adheres to the standard combination of: + * + * `{IPv4, IPv6}/{TCP, UDP}` + * + * @param {Multiaddr} [addr] - Defaults to using `this` instance + * @returns {boolean} isThinWaistAddress + * @example + * ```js + * const mh1 = new Multiaddr('/ip4/127.0.0.1/tcp/4001') + * // + * const mh2 = new Multiaddr('/ip4/192.168.2.1/tcp/5001') + * // + * const mh3 = mh1.encapsulate(mh2) + * // + * const mh4 = new Multiaddr('/ip4/127.0.0.1/tcp/2000/wss/p2p-webrtc-star/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo2a') + * // + * mh1.isThinWaistAddress() + * // true + * mh2.isThinWaistAddress() + * // true + * mh3.isThinWaistAddress() + * // false + * mh4.isThinWaistAddress() + * // false + * ``` + */ + isThinWaistAddress (addr) { + 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 } - if (protos[1].code !== 6 && protos[1].code !== 273) { - return false + + /** + * Creates a Multiaddr from a node-friendly address object + * + * @param {{family: string, address: string, port: string}} addr + * @param {string} transport + * @returns {Multiaddr} multiaddr + * @throws {Error} Throws error if addr or transport are not truthy + * @example + * + * ```js + * Multiaddr.fromNodeAddress({address: '127.0.0.1', port: '4001'}, 'tcp') + * // + * ``` + */ + static fromNodeAddress (addr, transport) { + if (!addr) { throw new Error('requires node address object') } + if (!transport) { throw new Error('requires transport protocol') } + let ip + switch (addr.family) { + case 'IPv4': + ip = 'ip4' + break + case 'IPv6': + ip = 'ip6' + break + default: + throw Error(`Invalid addr family. Got '${addr.family}' instead of 'IPv4' or 'IPv6'`) + } + return new Multiaddr('/' + [ip, addr.address, transport, addr.port].join('/')) } - return true -} -/** - * Object containing table, names and codes of all supported protocols. - * To get the protocol values from a Multiaddr, you can use - * [`.protos()`](#multiaddrprotos), - * [`.protoCodes()`](#multiaddrprotocodes) or - * [`.protoNames()`](#multiaddrprotonames) - * - * @instance - * @returns {{table: Array, names: Object, codes: Object}} - * - */ -Multiaddr.protocols = protocols + /** + * Returns if something is a Multiaddr that is a name + * + * @param {Multiaddr} addr + * @returns {boolean} isName + */ + static isName (addr) { + if (!Multiaddr.isMultiaddr(addr)) { + return false + } -/** - * Returns if something is a Multiaddr that is a name - * - * @param {Multiaddr} addr - * @returns {Bool} isName - */ -Multiaddr.isName = function isName (addr) { - if (!Multiaddr.isMultiaddr(addr)) { - return false + // if a part of the multiaddr is resolvable, then return true + return addr.protos().some((proto) => proto.resolvable) } - // if a part of the multiaddr is resolvable, then return true - return addr.protos().some((proto) => proto.resolvable) -} - -/** - * Returns an array of multiaddrs, by resolving the multiaddr that is a name - * - * @async - * @param {Multiaddr} addr - * @returns {Multiaddr[]} - */ -Multiaddr.resolve = function resolve (addr) { - if (!Multiaddr.isMultiaddr(addr) || !Multiaddr.isName(addr)) { - return Promise.reject(Error('not a valid name')) + /** + * Check if object is a CID instance + * + * @param {any} value + * @returns {value is Multiaddr} + */ + static isMultiaddr (value) { + return value instanceof Multiaddr || Boolean(value && value[symbol]) } - /* - * Needs more consideration from spec design: - * - what to return - * - how to achieve it in the browser? + /** + * Static factory method + * + * @param {string | Uint8Array | Multiaddr | null} [addr] - If String or Uint8Array, needs to adhere + * to the address format of a [multiaddr](https://github.com/multiformats/multiaddr#string-format) */ - return Promise.reject(new Error('not implemented yet')) + static multiaddr (addr) { + return new Multiaddr(addr) + } } +Multiaddr.protocols = protocols + Multiaddr.resolvers = resolvers -exports = module.exports = Multiaddr + +module.exports = Multiaddr diff --git a/src/protocols-table.js b/src/protocols-table.js index 4578e436..36d4b92e 100644 --- a/src/protocols-table.js +++ b/src/protocols-table.js @@ -1,5 +1,12 @@ 'use strict' +/** @typedef {import("./types").Protocol} Protocol */ +/** + * Protocols + * + * @param {number | string} proto + * @returns {Protocol} + */ function Protocols (proto) { if (typeof (proto) === 'number') { if (Protocols.codes[proto]) { @@ -7,7 +14,7 @@ function Protocols (proto) { } throw new Error('no protocol with code: ' + proto) - } else if (typeof (proto) === 'string' || proto instanceof String) { + } else if (typeof (proto) === 'string') { if (Protocols.names[proto]) { return Protocols.names[proto] } @@ -22,6 +29,7 @@ const V = -1 Protocols.lengthPrefixedVarSize = V Protocols.V = V +/** @type {Array<[number, number, string, (string|boolean)?, string?]>} */ Protocols.table = [ [4, 32, 'ip4'], [6, 16, 'tcp'], @@ -59,7 +67,9 @@ Protocols.table = [ [777, V, 'memory'] ] +/** @type {Record} */ Protocols.names = {} +/** @type {Record} */ Protocols.codes = {} // populate tables @@ -71,6 +81,17 @@ Protocols.table.map(row => { Protocols.object = p +/** + * + * Create a protocol + * + * @param {number} code + * @param {number} size + * @param {string} name + * @param {any} [resolvable] + * @param {any} [path] + * @returns {Protocol} + */ function p (code, size, name, resolvable, path) { return { code, diff --git a/src/resolvers/dns.js b/src/resolvers/dns.js index e85a5bd2..b54cfb29 100644 --- a/src/resolvers/dns.js +++ b/src/resolvers/dns.js @@ -1,9 +1,10 @@ 'use strict' +/** @type {typeof import('dns').promises.Resolver} */ let dns try { - dns = require('dns').promises + dns = require('dns').promises.Resolver if (!dns) { throw new Error('no dns available') } diff --git a/src/resolvers/index.js b/src/resolvers/index.js index d14c5c99..5c224b73 100644 --- a/src/resolvers/index.js +++ b/src/resolvers/index.js @@ -1,25 +1,24 @@ 'use strict' -const Multiaddr = require('..') // eslint-disable-line no-unused-vars const protocols = require('../protocols-table') const { code: dnsaddrCode } = protocols('dnsaddr') +// TODO `addr` type needs https://github.com/microsoft/TypeScript/issues/41672 /** * Resolver for dnsaddr addresses. * - * @param {Multiaddr} addr + * @param {any} addr * @returns {Promise>} */ async function dnsaddrResolver (addr) { - const { Resolver } = require('./dns') + const Resolver = require('./dns') const resolver = new Resolver() const peerId = addr.getPeerId() const [, hostname] = addr.stringTuples().find(([proto]) => proto === dnsaddrCode) || [] const records = await resolver.resolveTxt(`_dnsaddr.${hostname}`) - // @ts-ignore let addresses = records.flat().map((a) => a.split('=')[1]) if (peerId) { diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..9ae0bef5 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,7 @@ +export type Protocol = { + code: number; + size: number; + name: string; + resolvable?: boolean | undefined; + path?: boolean | undefined; +} diff --git a/test/index.spec.js b/test/index.spec.js index c154eb4a..aa0aeccc 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -2,16 +2,18 @@ /* eslint-env mocha */ 'use strict' -const multiaddr = require('../src') +const Multiaddr = require('../src') const { expect } = require('aegir/utils/chai') const uint8ArrayFromString = require('uint8arrays/from-string') +const multiaddr = Multiaddr.multiaddr + describe('construction', () => { let udpAddr it('create multiaddr', () => { udpAddr = multiaddr('/ip4/127.0.0.1/udp/1234') - expect(udpAddr instanceof multiaddr).to.equal(true) + expect(udpAddr instanceof Multiaddr).to.equal(true) }) it('clone multiaddr', () => { @@ -77,7 +79,7 @@ describe('requiring varint', () => { it('create multiaddr', () => { uTPAddr = multiaddr('/ip4/127.0.0.1/udp/1234/utp') - expect(uTPAddr instanceof multiaddr).to.equal(true) + expect(uTPAddr instanceof Multiaddr).to.equal(true) }) it('clone multiaddr', () => { @@ -116,16 +118,16 @@ describe('manipulation', () => { expect(udpAddr.protoCodes()).to.deep.equal([4, 273]) expect(udpAddr.protoNames()).to.deep.equal(['ip4', 'udp']) - expect(udpAddr.protos()).to.deep.equal([multiaddr.protocols.codes[4], multiaddr.protocols.codes[273]]) - expect(udpAddr.protos()[0] === multiaddr.protocols.codes[4]).to.equal(false) + expect(udpAddr.protos()).to.deep.equal([Multiaddr.protocols.codes[4], Multiaddr.protocols.codes[273]]) + expect(udpAddr.protos()[0] === Multiaddr.protocols.codes[4]).to.equal(false) - const udpAddrbytes2 = udpAddr.encapsulate('/udp/5678') + const udpAddrbytes2 = udpAddr.encapsulate(multiaddr('/udp/5678')) expect(udpAddrbytes2.toString()).to.equal('/ip4/127.0.0.1/udp/1234/udp/5678') - expect(udpAddrbytes2.decapsulate('/udp').toString()).to.equal('/ip4/127.0.0.1/udp/1234') - expect(udpAddrbytes2.decapsulate('/ip4').toString()).to.equal('/') - expect(function () { udpAddr.decapsulate('/').toString() }).to.throw() + expect(udpAddrbytes2.decapsulate(multiaddr('/udp/5678')).toString()).to.equal('/ip4/127.0.0.1/udp/1234') + expect(udpAddrbytes2.decapsulate(multiaddr('/ip4/127.0.0.1')).toString()).to.equal('/') + expect(function () { udpAddr.decapsulate(multiaddr('/')).toString() }).to.throw() expect(multiaddr('/').encapsulate(udpAddr).toString()).to.equal(udpAddr.toString()) - expect(multiaddr('/').decapsulate('/').toString()).to.equal('/') + expect(multiaddr('/').decapsulate(multiaddr('/')).toString()).to.equal('/') }) it('ipfs', () => { @@ -628,7 +630,7 @@ describe('helpers', () => { describe('.decapsulate', () => { it('throws on address with no matching subaddress', () => { expect( - () => multiaddr('/ip4/127.0.0.1').decapsulate('/ip4/198.168.0.0') + () => multiaddr('/ip4/127.0.0.1').decapsulate(multiaddr('/ip4/198.168.0.0')) ).to.throw( /does not contain subaddress/ ) @@ -638,7 +640,7 @@ describe('helpers', () => { describe('.decapsulateCode', () => { it('removes the last occurrence of the code from the multiaddr', () => { const relayTCP = multiaddr('/ip4/0.0.0.0/tcp/8080') - const relay = relayTCP.encapsulate('/p2p/QmZR5a9AAXGqQF2ADqoDdGS8zvqv8n3Pag6TDDnTNMcFW6/p2p-circuit') + const relay = relayTCP.encapsulate(multiaddr('/p2p/QmZR5a9AAXGqQF2ADqoDdGS8zvqv8n3Pag6TDDnTNMcFW6/p2p-circuit')) const target = multiaddr('/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC') const original = relay.encapsulate(target) expect(original.decapsulateCode(421)).to.eql(relay) @@ -725,7 +727,7 @@ describe('helpers', () => { it('throws on missing address object', () => { expect( // @ts-ignore - () => multiaddr.fromNodeAddress() + () => Multiaddr.fromNodeAddress() ).to.throw( /requires node address/ ) @@ -734,7 +736,7 @@ describe('helpers', () => { it('throws on missing transport', () => { expect( // @ts-ignore - () => multiaddr.fromNodeAddress({ address: '0.0.0.0' }) + () => Multiaddr.fromNodeAddress({ address: '0.0.0.0' }) ).to.throw( /requires transport protocol/ ) @@ -742,7 +744,7 @@ describe('helpers', () => { it('parses a node address', () => { expect( - multiaddr.fromNodeAddress({ + Multiaddr.fromNodeAddress({ address: '192.168.0.1', family: 'IPv4', port: '1234' @@ -857,11 +859,11 @@ describe('helpers', () => { describe('multiaddr.isMultiaddr', () => { it('handles different inputs', () => { - expect(multiaddr.isMultiaddr(multiaddr('/'))).to.be.eql(true) - expect(multiaddr.isMultiaddr('/')).to.be.eql(false) - expect(multiaddr.isMultiaddr(123)).to.be.eql(false) + expect(Multiaddr.isMultiaddr(multiaddr('/'))).to.be.eql(true) + expect(Multiaddr.isMultiaddr('/')).to.be.eql(false) + expect(Multiaddr.isMultiaddr(123)).to.be.eql(false) - expect(multiaddr.isMultiaddr(uint8ArrayFromString('/hello'))).to.be.eql(false) + expect(Multiaddr.isMultiaddr(uint8ArrayFromString('/hello'))).to.be.eql(false) }) }) @@ -870,47 +872,31 @@ describe('helpers', () => { it('valid name dns', () => { const str = '/dns/ipfs.io' const addr = multiaddr(str) - expect(multiaddr.isName(addr)).to.equal(true) + expect(Multiaddr.isName(addr)).to.equal(true) }) it('valid name dnsaddr', () => { const str = '/dnsaddr/ipfs.io' const addr = multiaddr(str) - expect(multiaddr.isName(addr)).to.equal(true) + expect(Multiaddr.isName(addr)).to.equal(true) }) it('valid name dns4', () => { const str = '/dns4/ipfs.io' const addr = multiaddr(str) - expect(multiaddr.isName(addr)).to.equal(true) + expect(Multiaddr.isName(addr)).to.equal(true) }) it('valid name dns6', () => { const str = '/dns6/ipfs.io' const addr = multiaddr(str) - expect(multiaddr.isName(addr)).to.equal(true) + expect(Multiaddr.isName(addr)).to.equal(true) }) it('invalid name', () => { const str = '/ip4/127.0.0.1' const addr = multiaddr(str) - expect(multiaddr.isName(addr)).to.equal(false) - }) - }) - - describe('.resolve', () => { - it.skip('valid and active DNS name', (done) => {}) - it.skip('valid but inactive DNS name', () => {}) - it('invalid DNS name', () => { - const str = '/ip4/127.0.0.1' - const addr = multiaddr(str) - - return multiaddr.resolve(addr) - // @ts-ignore - .then(expect.fail, (err) => { - expect(err).to.exist() - expect(err.message).to.eql('not a valid name') - }) + expect(Multiaddr.isName(addr)).to.equal(false) }) }) }) diff --git a/test/protocols.spec.js b/test/protocols.spec.js index b5b53016..a6e12a01 100644 --- a/test/protocols.spec.js +++ b/test/protocols.spec.js @@ -24,6 +24,7 @@ describe('protocols', () => { it('else', () => { expect( + // @ts-expect-error () => protocols({ hi: 34 }) ).to.throw( /invalid protocol id type/ diff --git a/test/resolvers.spec.js b/test/resolvers.spec.js index 747c80de..5ac692e6 100644 --- a/test/resolvers.spec.js +++ b/test/resolvers.spec.js @@ -4,9 +4,10 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') -const multiaddr = require('../src') +const Multiaddr = require('../src') const resolvers = require('../src/resolvers') -const { Resolver } = require('../src/resolvers/dns') +const Resolver = require('../src/resolvers/dns') +const multiaddr = Multiaddr.multiaddr const dnsaddrStub1 = [ ['dnsaddr=/dnsaddr/ams-1.bootstrap.libp2p.io/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd'], @@ -28,7 +29,7 @@ const dnsaddrStub2 = [ describe('multiaddr resolve', () => { it('should throw if no resolver is available', async () => { - const ma = multiaddr('/dnsaddr/bootstrap.libp2p.io') + const ma = new Multiaddr('/dnsaddr/bootstrap.libp2p.io') // Resolve await expect(ma.resolve()).to.eventually.be.rejected() @@ -38,7 +39,7 @@ describe('multiaddr resolve', () => { describe('dnsaddr', () => { before(() => { // Set resolvers - multiaddr.resolvers.set('dnsaddr', resolvers.dnsaddrResolver) + Multiaddr.resolvers.set('dnsaddr', resolvers.dnsaddrResolver) }) afterEach(() => { diff --git a/test/types.spec.ts b/test/types.spec.ts deleted file mode 100644 index ba9129c9..00000000 --- a/test/types.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import Multiaddr from '../src' - -const testStr = '/ip4/127.0.0.1' - -export const maFromFunctionConstructor: Multiaddr = Multiaddr(testStr) - -export const maFromClassConstructor: Multiaddr = new Multiaddr(testStr) - -export const maFromMa: Multiaddr = Multiaddr(new Multiaddr(testStr)) - -export const maFromConstructorFunction: Multiaddr = Multiaddr.fromNodeAddress( - { - family: 'IPv4', - address: '127.0.0.1', - port: '12345' - }, - 'udp' -) - -export function doSthWithMa (ma: Multiaddr): void { - ma.toOptions() -} diff --git a/tsconfig.json b/tsconfig.json index 60e2c5b1..e605b61a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,10 @@ { - "compilerOptions": { - "module": "commonjs", - "lib": ["es6"], - "target": "ES5", - "noImplicitAny": false, - "noImplicitThis": true, - "strictFunctionTypes": true, - "strictNullChecks": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "allowJs": true, - "checkJs": true, - "noEmit": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["src/index.d.ts", "test/**/*.spec.js", "test/**/*.spec.ts"] + "extends": "./node_modules/aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "test", // remove this line if you don't want to type-check tests + "src" + ] }